started refactoring for requirejs

This commit is contained in:
Tankred Hase 2013-06-10 17:57:33 +02:00
parent 16f0c5aca6
commit e8329872ec
54 changed files with 22907 additions and 11914 deletions

View File

@ -1,7 +1,6 @@
{
"strict": true,
"globalstrict": true,
"jquery": true,
"node": true,
"browser": true,
"nonew": true,
@ -18,29 +17,20 @@
"unused": true,
"predef": [
"self",
"console",
"importScripts",
"cryptoLib",
"Backbone",
"forge",
"uuid",
"Lawnchair",
"_",
"process",
"describe",
"it",
"chai",
"QUnit",
"test",
"asyncTest",
"ok",
"equal",
"deepEqual",
"start",
"TestData",
"chrome"
"chrome",
"define"
],
"globals": {
"app": true
}
}

View File

@ -12,7 +12,7 @@
"dependencies": {
"grunt": "~0.4.1",
"grunt-contrib-connect": "~0.3.0",
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/master"
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/requirejs"
},
"devDependencies": {
"grunt-contrib-jshint": "~0.5.3",

View File

@ -6,4 +6,5 @@ echo "--> copying dependencies to src\n"
cd `dirname $0`
cd ..
cp ./node_modules/crypto-lib/src/*.js ./src/js/crypto/
cp ./node_modules/crypto-lib/src/*.js ./src/js/crypto/
cp ./node_modules/crypto-lib/node_modules/node-forge/js/*.js ./src/lib/

View File

@ -8,36 +8,7 @@
<link rel="stylesheet" href="css/styles.css"/>
<!-- The Scripts -->
<script src="lib/jquery-1.8.2.min.js"></script>
<script src="lib/underscore-1.4.4.min.js"></script>
<script src="lib/backbone-1.0.0.min.js"></script>
<script src="lib/lawnchair/lawnchair-git.js"></script>
<script src="lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js"></script>
<script src="lib/lawnchair/lawnchair-adapter-indexed-db-git.js"></script>
<script src="lib/forge/forge.rsa.bundle.js"></script>
<script src="lib/uuid.js"></script>
<script src="js/app-config.js"></script>
<script src="js/model/folder-model.js"></script>
<script src="js/model/account-model.js"></script>
<script src="js/crypto/util.js"></script>
<script src="js/crypto/pbkdf2.js"></script>
<script src="js/crypto/aes-cbc.js"></script>
<script src="js/crypto/rsa.js"></script>
<script src="js/crypto/crypto-batch.js"></script>
<script src="js/crypto/crypto.js"></script>
<script src="js/dao/lawnchair-dao.js"></script>
<script src="js/dao/devicestorage.js"></script>
<script src="js/dao/cloudstorage-dao.js"></script>
<script src="js/dao/keychain-dao.js"></script>
<script src="js/dao/email-dao.js"></script>
<script src="js/app-controller.js"></script>
<script src="js/window-loader.js"></script>
<script data-main="js/window-loader.js" src="lib/require.js"></script>
</head>
<body>

View File

@ -1,12 +1,10 @@
var app; // container for the application namespace
(function() {
'use strict';
/**
* Create the application namespace
*/
app = {
var app = {
model: {},
view: {},
dao: {},
@ -55,4 +53,6 @@ var app; // container for the application namespace
}
};
window.app = app;
}());

View File

@ -1,30 +1,28 @@
/**
* The main application controller
*/
app.Controller = function() {
define(['js/dao/email-dao'], function(emailDao) {
'use strict';
var emailDao; // local variable for the main email data access object
var self = {};
/**
* Initializes modules through dependecy injection
*/
this.init = function(callback) {
var util = new cryptoLib.Util(window, uuid);
var crypto = new app.crypto.Crypto(window, util);
var cloudstorage = new app.dao.CloudStorage(window, $);
var jsonDao = new app.dao.LawnchairDAO(Lawnchair);
var devicestorage = new app.dao.DeviceStorage(util, crypto, jsonDao, null);
var keychain = new app.dao.KeychainDAO(jsonDao, cloudstorage);
emailDao = new app.dao.EmailDAO(jsonDao, crypto, devicestorage, cloudstorage, util, keychain);
self.init = function(callback) {
// var crypto = new app.crypto.Crypto(window, util);
// var cloudstorage = new app.dao.CloudStorage(window, $);
// var jsonDao = new app.dao.LawnchairDAO(Lawnchair);
// var devicestorage = new app.dao.DeviceStorage(util, crypto, jsonDao, null);
// var keychain = new app.dao.KeychainDAO(jsonDao, cloudstorage);
// emailDao = new app.dao.EmailDAO(jsonDao, crypto, devicestorage, cloudstorage, util, keychain);
callback();
};
/**
* Start the application by loading the view templates
*/
this.start = function(callback) {
self.start = function(callback) {
// the views to load
var views = [
'login',
@ -53,7 +51,7 @@ app.Controller = function() {
/**
* Executes a number of commands
*/
this.execute = function(cmd, args, callback) {
self.execute = function(cmd, args, callback) {
if (cmd === 'login') {
// login user
login(args.userId, args.password, function(err) {
@ -119,4 +117,5 @@ app.Controller = function() {
emailDao.init(account, password, callback);
}
};
return self;
});

View File

@ -46,12 +46,14 @@
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = AesCBC;
} else {
var that = (typeof window !== 'undefined') ? window : self;
that.cryptoLib = that.cryptoLib || {};
that.cryptoLib.AesCBC = AesCBC;
if (typeof define !== 'undefined' && define.amd) {
// AMD
define(['forge'], function(forge) {
return new AesCBC(forge);
});
} else if (typeof module !== 'undefined' && module.exports) {
// node.js
module.exports = new AesCBC(require('node-forge'));
}
})();

View File

@ -118,12 +118,14 @@
};
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = CryptoBatch;
} else {
var that = (typeof window !== 'undefined') ? window : self;
that.cryptoLib = that.cryptoLib || {};
that.cryptoLib.CryptoBatch = CryptoBatch;
if (typeof define !== 'undefined' && define.amd) {
// AMD
define(['cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/util', 'underscore'], function(aes, rsa, util, _) {
return new CryptoBatch(aes, rsa, util, _);
});
} else if (typeof module !== 'undefined' && module.exports) {
// node.js
module.exports = new CryptoBatch(require('./aes-cbc'), require('./rsa'), require('./util'), require('underscore'));
}
})();

View File

@ -2,19 +2,16 @@
* High level crypto api that invokes native crypto (if available) and
* gracefully degrades to JS crypto (if unavailable)
*/
app.crypto.Crypto = function(window, util) {
define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypto-batch'], function(util, aes, rsa, cryptoBatch) {
'use strict';
var aes = new cryptoLib.AesCBC(forge); // use AES-CBC mode by default
var rsa = new cryptoLib.RSA(forge, util); // use RSA for asym. crypto
var self = {};
/**
* Initializes the crypto modules by fetching the user's
* encrypted secret key from storage and storing it in memory.
*/
this.init = function(args, callback) {
var self = this;
self.init = function(args, callback) {
// valdiate input
if (!args.emailAddress || !args.keySize || !args.rsaKeySize) {
callback({
@ -109,7 +106,7 @@ app.crypto.Crypto = function(window, util) {
/**
* Do PBKDF2 key derivation in a WebWorker thread
*/
this.deriveKey = function(password, keySize, callback) {
self.deriveKey = function(password, keySize, callback) {
startWorker('/crypto/pbkdf2-worker.js', {
password: password,
keySize: keySize
@ -123,9 +120,7 @@ app.crypto.Crypto = function(window, util) {
// En/Decrypts single item
//
this.aesEncrypt = function(plaintext, key, iv, callback) {
var self = this;
self.aesEncrypt = function(plaintext, key, iv, callback) {
startWorker('/crypto/aes-worker.js', {
type: 'encrypt',
plaintext: plaintext,
@ -136,9 +131,7 @@ app.crypto.Crypto = function(window, util) {
});
};
this.aesDecrypt = function(ciphertext, key, iv, callback) {
var self = this;
self.aesDecrypt = function(ciphertext, key, iv, callback) {
startWorker('/crypto/aes-worker.js', {
type: 'decrypt',
ciphertext: ciphertext,
@ -149,11 +142,11 @@ app.crypto.Crypto = function(window, util) {
});
};
this.aesEncryptSync = function(plaintext, key, iv) {
self.aesEncryptSync = function(plaintext, key, iv) {
return aes.encrypt(plaintext, key, iv);
};
this.aesDecryptSync = function(ciphertext, key, iv) {
self.aesDecryptSync = function(ciphertext, key, iv) {
return aes.decrypt(ciphertext, key, iv);
};
@ -161,23 +154,21 @@ app.crypto.Crypto = function(window, util) {
// En/Decrypt a list of items with AES in a WebWorker thread
//
this.aesEncryptList = function(list, callback) {
self.aesEncryptList = function(list, callback) {
startWorker('/crypto/aes-batch-worker.js', {
type: 'encrypt',
list: list
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes);
return batch.encryptList(list);
return cryptoBatch.encryptList(list);
});
};
this.aesDecryptList = function(list, callback) {
self.aesDecryptList = function(list, callback) {
startWorker('/crypto/aes-batch-worker.js', {
type: 'decrypt',
list: list
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes);
return batch.decryptList(list);
return cryptoBatch.decryptList(list);
});
};
@ -185,9 +176,8 @@ app.crypto.Crypto = function(window, util) {
// En/Decrypt something speficially using the user's secret key
//
this.encryptListForUser = function(list, receiverPubkeys, callback) {
var envelope, envelopes = [],
self = this;
self.encryptListForUser = function(list, receiverPubkeys, callback) {
var envelope, envelopes = [];
if (!receiverPubkeys || receiverPubkeys.length !== 1) {
callback({
@ -220,12 +210,11 @@ app.crypto.Crypto = function(window, util) {
senderPrivkey: senderPrivkey,
receiverPubkeys: receiverPubkeys
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes, rsa, util, _);
return batch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey);
return cryptoBatch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey);
});
};
this.decryptListForUser = function(list, senderPubkeys, callback) {
self.decryptListForUser = function(list, senderPubkeys, callback) {
if (!senderPubkeys || senderPubkeys < 1) {
callback({
errMsg: 'Sender public keys must be set!'
@ -245,8 +234,7 @@ app.crypto.Crypto = function(window, util) {
receiverPrivkey: receiverPrivkey,
senderPubkeys: senderPubkeys
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes, rsa, util, _);
return batch.decryptListForUser(list, senderPubkeys, receiverPrivkey);
return cryptoBatch.decryptListForUser(list, senderPubkeys, receiverPrivkey);
});
};
@ -272,4 +260,5 @@ app.crypto.Crypto = function(window, util) {
}
}
};
return self;
});

View File

@ -33,7 +33,7 @@
this.generateKeypair = function(keySize, callback) {
forge.rsa.generateKeyPair({
bits: keySize,
workerScript: (typeof app !== 'undefined') ? (app.config.workerPath + '/../lib/forge/prime.worker.js') : undefined
workerScript: (typeof app !== 'undefined') ? (app.config.workerPath + '/../lib/prime.worker.js') : undefined
}, function(err, newKeypair) {
if (err || !newKeypair || !newKeypair.publicKey || !newKeypair.privateKey) {
callback({
@ -127,12 +127,14 @@
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = RSA;
} else {
var that = (typeof window !== 'undefined') ? window : self;
that.cryptoLib = that.cryptoLib || {};
that.cryptoLib.RSA = RSA;
if (typeof define !== 'undefined' && define.amd) {
// AMD
define(['forge', 'cryptoLib/util'], function(forge, util) {
return new RSA(forge, util);
});
} else if (typeof module !== 'undefined' && module.exports) {
// node.js
module.exports = new RSA(require('node-forge'), require('./util'));
}
})();

View File

@ -4,7 +4,7 @@
/**
* Various utitity methods for crypto, encoding & decoding
*/
var Util = function(window, uuid, crypt) {
var Util = function(forge, uuid, crypt) {
/**
* Generates a new RFC 4122 version 4 compliant random UUID
@ -26,7 +26,7 @@
keyBuf = crypt.randomBytes(keySize / 8);
keyBase64 = new Buffer(keyBuf).toString('base64');
} else if (window.crypto && window.crypto.getRandomValues) {
} else if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
// browser if secure rng exists
keyBuf = new Uint8Array(keySize / 8);
window.crypto.getRandomValues(keyBuf);
@ -198,12 +198,14 @@
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Util;
} else {
var that = (typeof window !== 'undefined') ? window : self;
that.cryptoLib = that.cryptoLib || {};
that.cryptoLib.Util = Util;
if (typeof define !== 'undefined' && define.amd) {
// AMD
define(['uuid', 'forge'], function(uuid, forge) {
return new Util(forge, uuid, undefined);
});
} else if (typeof module !== 'undefined' && module.exports) {
// node.js
module.exports = new Util(require('node-forge'), require('node-uuid'), require('crypto'));
}
})();

View File

@ -1,42 +1,43 @@
(function() {
require(['../require-config'], function() {
'use strict';
var controller;
// Start the main app logic.
require(['jquery', 'js/app-controller', 'js/app-config'], function($, controller) {
/**
* Load templates and start the application
*/
$(document).ready(function() {
controller = new app.Controller();
controller.init(function() {
controller.start(startApp);
});
});
function startApp() {
// sandboxed ui in iframe
var sandbox = document.getElementById('sandboxFrame').contentWindow;
// set global listener for events from sandbox
window.onmessage = function(e) {
var cmd = e.data.cmd;
var args = e.data.args;
// handle the workload in the main window
controller.execute(cmd, args, function(resArgs) {
// send reponse to sandbox
sandbox.postMessage({
cmd: cmd,
args: resArgs
}, '*');
/**
* Load templates and start the application
*/
$(document).ready(function() {
controller.init(function() {
controller.start(startApp);
});
};
});
// init sandbox ui
sandbox.postMessage({
cmd: 'init',
args: app.util.tpl.templates
}, '*');
}
function startApp() {
// sandboxed ui in iframe
var sandbox = document.getElementById('sandboxFrame').contentWindow;
}());
// set global listener for events from sandbox
window.onmessage = function(e) {
var cmd = e.data.cmd;
var args = e.data.args;
// handle the workload in the main window
controller.execute(cmd, args, function(resArgs) {
// send reponse to sandbox
sandbox.postMessage({
cmd: cmd,
args: resArgs
}, '*');
});
};
// init sandbox ui
sandbox.postMessage({
cmd: 'init',
args: app.util.tpl.templates
}, '*');
}
});
});

1186
src/lib/aes.js Normal file

File diff suppressed because it is too large Load Diff

1055
src/lib/asn1.js Normal file

File diff suppressed because it is too large Load Diff

130
src/lib/debug.js Normal file
View File

@ -0,0 +1,130 @@
/**
* Debugging support for web applications.
*
* @author David I. Lehn <dlehn@digitalbazaar.com>
*
* Copyright 2008-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
/* DEBUG API */
forge.debug = forge.debug || {};
// Private storage for debugging.
// Useful to expose data that is otherwise unviewable behind closures.
// NOTE: remember that this can hold references to data and cause leaks!
// format is "forge._debug.<modulename>.<dataname> = data"
// Example:
// (function() {
// var cat = 'forge.test.Test'; // debugging category
// var sState = {...}; // local state
// forge.debug.set(cat, 'sState', sState);
// })();
forge.debug.storage = {};
/**
* Gets debug data. Omit name for all cat data Omit name and cat for
* all data.
*
* @param cat name of debugging category.
* @param name name of data to get (optional).
* @return object with requested debug data or undefined.
*/
forge.debug.get = function(cat, name) {
var rval;
if(typeof(cat) === 'undefined') {
rval = forge.debug.storage;
}
else if(cat in forge.debug.storage) {
if(typeof(name) === 'undefined') {
rval = forge.debug.storage[cat];
}
else {
rval = forge.debug.storage[cat][name];
}
}
return rval;
};
/**
* Sets debug data.
*
* @param cat name of debugging category.
* @param name name of data to set.
* @param data data to set.
*/
forge.debug.set = function(cat, name, data) {
if(!(cat in forge.debug.storage)) {
forge.debug.storage[cat] = {};
}
forge.debug.storage[cat][name] = data;
};
/**
* Clears debug data. Omit name for all cat data. Omit name and cat for
* all data.
*
* @param cat name of debugging category.
* @param name name of data to clear or omit to clear entire category.
*/
forge.debug.clear = function(cat, name) {
if(typeof(cat) === 'undefined') {
forge.debug.storage = {};
}
else if(cat in forge.debug.storage) {
if(typeof(name) === 'undefined') {
delete forge.debug.storage[cat];
}
else {
delete forge.debug.storage[cat][name];
}
}
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'debug';
var deps = [];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

472
src/lib/des.js Normal file
View File

@ -0,0 +1,472 @@
/**
* DES (Data Encryption Standard) implementation.
*
* This implementation supports DES as well as 3DES-EDE in ECB and CBC mode.
* It is based on the BSD-licensed implementation by Paul Tero:
*
* Paul Tero, July 2001
* http://www.tero.co.uk/des/
*
* Optimised for performance with large blocks by Michael Hayworth, November 2001
* http://www.netdealing.com
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var spfunction1 = [0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004];
var spfunction2 = [-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000];
var spfunction3 = [0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200];
var spfunction4 = [0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080];
var spfunction5 = [0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,0x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100];
var spfunction6 = [0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010];
var spfunction7 = [0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002];
var spfunction8 = [0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000];
/**
* Create necessary sub keys.
*
* @param key The 64-bit or 192-bit key
* @access public
* @return void
*/
function des_createKeys (key) {
var pc2bytes0 = [0,0x4,0x20000000,0x20000004,0x10000,0x10004,0x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204],
pc2bytes1 = [0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101],
pc2bytes2 = [0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808],
pc2bytes3 = [0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000],
pc2bytes4 = [0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010],
pc2bytes5 = [0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420],
pc2bytes6 = [0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002],
pc2bytes7 = [0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800],
pc2bytes8 = [0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002],
pc2bytes9 = [0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408],
pc2bytes10 = [0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020],
pc2bytes11 = [0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200],
pc2bytes12 = [0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010],
pc2bytes13 = [0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105];
//how many iterations (1 for des, 3 for triple des)
var iterations = key.length() > 8 ? 3 : 1; //changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
//stores the return keys
var keys = [];
//now define the left shifts which need to be done
var shifts = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0];
var n = 0, temp;
for(var j = 0; j < iterations; j ++) {
var left = key.getInt32();
var right = key.getInt32();
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);
temp = ((right >>> -16) ^ left) & 0x0000ffff; left ^= temp; right ^= (temp << -16);
temp = ((left >>> 2) ^ right) & 0x33333333; right ^= temp; left ^= (temp << 2);
temp = ((right >>> -16) ^ left) & 0x0000ffff; left ^= temp; right ^= (temp << -16);
temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
//the right side needs to be shifted and to get the last four bits of the left side
temp = (left << 8) | ((right >>> 20) & 0x000000f0);
//left needs to be put upside down
left = (right << 24) | ((right << 8) & 0xff0000) | ((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0);
right = temp;
//now go through and perform these shifts on the left and right keys
for(var i=0; i < shifts.length; i++) {
//shift the keys either one or two bits to the left
if(shifts[i]) {
left = (left << 2) | (left >>> 26); right = (right << 2) | (right >>> 26);
} else {
left = (left << 1) | (left >>> 27); right = (right << 1) | (right >>> 27);
}
left &= -0xf; right &= -0xf;
//now apply PC-2, in such a way that E is easier when encrypting or decrypting
//this conversion will look like PC-2 except only the last 6 bits of each byte are used
//rather than 48 consecutive bits and the order of lines will be according to
//how the S selection functions will be applied: S2, S4, S6, S8, S1, S3, S5, S7
var lefttemp = pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf]
| pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf]
| pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf]
| pc2bytes6[(left >>> 4) & 0xf];
var righttemp = pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf]
| pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf]
| pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf]
| pc2bytes13[(right >>> 4) & 0xf];
temp = ((righttemp >>> 16) ^ lefttemp) & 0x0000ffff;
keys[n++] = lefttemp ^ temp; keys[n++] = righttemp ^ (temp << 16);
}
}
return keys;
}
/**
* Creates an DES cipher object.
*
* @param key the symmetric key to use (64 or 192 bits).
* @param encrypt false for decryption, true for encryption.
*
* @return the cipher.
*/
var _createCipher = function(key, encrypt)
{
if(key.constructor == String && (key.length == 8 || key.length == 24)) {
key = forge.util.createBuffer(key);
}
/* Create the 16 or 48 subkeys we will need. */
var keys = des_createKeys (key);
/**
* Mode of encryption.
*
* 0: ECB (Electronic Codebook)
* 1: CBC (Cipher Block Chaining)
*/
var mode = 1;
var cbcleft = 0, cbcleft2 = 0, cbcright = 0, cbcright2 = 0;
var _finish = false, _input = null, _output = null;
/* Set up the loops for single and triple DES. */
var iterations = keys.length == 32 ? 3 : 9; // single or triple des
var looping;
if(iterations == 3) {
looping = encrypt
? [0, 32, 2]
: [30, -2, -2];
} else {
looping = encrypt
? [0, 32, 2, 62, 30, -2, 64, 96, 2]
: [94, 62, -2, 32, 64, 2, 30, -2, -2];
}
// Create cipher object
var cipher = null;
cipher = {
/**
* Starts or restarts the encryption or decryption process, whichever
* was previously configured.
*
* To use the cipher in CBC mode, iv may be given either as a string
* of bytes, or as a byte buffer. For ECB mode, give null as iv.
*
* @param iv the initialization vector to use, null for ECB mode.
* @param output the output the buffer to write to, null to create one.
*/
start: function(iv, output) {
if(iv) {
if(key.constructor == String && iv.length == 8) {
iv = forge.util.createBuffer(iv);
}
mode = 1; // CBC mode
cbcleft = iv.getInt32();
cbcright = iv.getInt32();
} else {
mode = 0; // ECB mode
}
//store the result here
_finish = false;
_input = forge.util.createBuffer();
_output = output || forge.util.createBuffer();
cipher.output = _output;
},
/**
* Updates the next block.
*
* @param input the buffer to read from.
*/
update: function(input) {
if(!_finish) {
// not finishing, so fill the input buffer with more input
_input.putBuffer(input);
}
while(_input.length() >= 8) {
var temp;
var left = _input.getInt32();
var right = _input.getInt32();
//for Cipher Block Chaining mode, xor the message with the previous result
if(mode == 1) {
if(encrypt) {
left ^= cbcleft;
right ^= cbcright;
} else {
cbcleft2 = cbcleft;
cbcright2 = cbcright;
cbcleft = left;
cbcright = right;
}
}
//first each 64 bit chunk of the message must be permuted according to IP
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);
temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16);
temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2);
temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
left = ((left << 1) | (left >>> 31));
right = ((right << 1) | (right >>> 31));
for(var j = 0; j < iterations; j += 3) {
var endloop = looping[j+1];
var loopinc = looping[j+2];
//now go through and perform the encryption or decryption
for(var i = looping[j]; i != endloop; i += loopinc) {
var right1 = right ^ keys[i];
var right2 = ((right >>> 4) | (right << 28)) ^ keys[i+1];
//the result is attained by passing these bytes through the S selection functions
temp = left;
left = right;
right = temp ^ (spfunction2[(right1 >>> 24) & 0x3f] | spfunction4[(right1 >>> 16) & 0x3f]
| spfunction6[(right1 >>> 8) & 0x3f] | spfunction8[right1 & 0x3f]
| spfunction1[(right2 >>> 24) & 0x3f] | spfunction3[(right2 >>> 16) & 0x3f]
| spfunction5[(right2 >>> 8) & 0x3f] | spfunction7[right2 & 0x3f]);
}
temp = left; left = right; right = temp; //unreverse left and right
}
//move then each one bit to the right
left = ((left >>> 1) | (left << 31));
right = ((right >>> 1) | (right << 31));
//now perform IP-1, which is IP in the opposite direction
temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2);
temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16);
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);
//for Cipher Block Chaining mode, xor the message with the previous result
if(mode == 1) {
if(encrypt) {
cbcleft = left;
cbcright = right;
} else {
left ^= cbcleft2;
right ^= cbcright2;
}
}
_output.putInt32(left);
_output.putInt32(right);
}
},
/**
* Finishes encrypting or decrypting.
*
* @param pad a padding function to use, null for PKCS#7 padding,
* signature(blockSize, buffer, decrypt).
*
* @return true if successful, false on error.
*/
finish: function(pad) {
var rval = true;
if(encrypt) {
if(pad) {
rval = pad(8, _input, !encrypt);
} else {
// add PKCS#7 padding to block (each pad byte is the
// value of the number of pad bytes)
var padding = (_input.length() == 8) ? 8 : (8 - _input.length());
_input.fillWithByte(padding, padding);
}
}
if(rval) {
// do final update
_finish = true;
cipher.update();
}
if(!encrypt) {
// check for error: input data not a multiple of block size
rval = (_input.length() === 0);
if(rval) {
if(pad) {
rval = pad(8, _output, !encrypt);
} else {
// ensure padding byte count is valid
var len = _output.length();
var count = _output.at(len - 1);
if(count > len) {
rval = false;
} else {
// trim off padding bytes
_output.truncate(count);
}
}
}
}
return rval;
}
};
return cipher;
};
/* DES API */
forge.des = forge.des || {};
/**
* Creates a DES cipher object to encrypt data in ECB or CBC mode using the
* given symmetric key. The output will be stored in the 'output' member
* of the returned cipher.
*
* The key and iv may be given as a string of bytes or as a byte buffer.
*
* @param key the symmetric key to use.
* @param iv the initialization vector to use, null for ECB mode.
* @param output the buffer to write to, null to create one.
*
* @return the cipher.
*/
forge.des.startEncrypting = function(key, iv, output)
{
var cipher = _createCipher(key, true);
cipher.start(iv, output);
return cipher;
};
/**
* Creates a DES cipher object to encrypt data in ECB or CBC mode using the
* given symmetric key.
*
* The key may be given as a string of bytes, or as a byte buffer.
*
* To start encrypting call start() on the cipher with an iv and optional
* output buffer.
*
* @param key the symmetric key to use.
*
* @return the cipher.
*/
forge.des.createEncryptionCipher = function(key)
{
return _createCipher(key, true);
};
/**
* Creates a DES cipher object to decrypt data in ECB or CBC mode using the
* given symmetric key. The output will be stored in the 'output' member
* of the returned cipher.
*
* The key and iv may be given as a string of bytes, or as a byte buffer.
*
* @param key the symmetric key to use.
* @param iv the initialization vector to use, null for ECB mode.
* @param output the buffer to write to, null to create one.
*
* @return the cipher.
*/
forge.des.startDecrypting = function(key, iv, output)
{
var cipher = _createCipher(key, false);
cipher.start(iv, output);
return cipher;
};
/**
* Creates a DES cipher object to decrypt data in ECB or CBC mode using the
* given symmetric key.
*
* The key may be given as a string of bytes, or as a byte buffer.
*
* To start decrypting call start() on the cipher with an iv and
* optional output buffer.
*
* @param key the symmetric key to use.
*
* @return the cipher.
*/
forge.des.createDecryptionCipher = function(key)
{
return _createCipher(key, false);
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'des';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

60
src/lib/forge.js Normal file
View File

@ -0,0 +1,60 @@
/**
* Node.js module for Forge.
*
* @author Dave Longley
*
* Copyright 2011-2013 Digital Bazaar, Inc.
*/
(function() {
var deps = [
'./aes',
'./asn1',
'./debug',
'./des',
'./hmac',
'./log',
'./pbkdf2',
'./pkcs7',
'./pkcs12',
'./pki',
'./prng',
'./pss',
'./random',
'./rc2',
'./task',
'./tls',
'./util',
'./md',
'./mgf1'
];
var cjsDefine = null;
if(typeof define !== 'function') {
// CommonJS -> AMD
if(typeof module === 'object' && module.exports) {
cjsDefine = function(ids, factory) {
module.exports = factory.apply(null, ids.map(function(id) {
return require(id);
}));
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(cjsDefine || typeof define === 'function') {
// define module AMD style
(cjsDefine || define)(deps, function() {
var forge = {};
var mods = Array.prototype.slice.call(arguments);
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge;
});
}
})();

File diff suppressed because it is too large Load Diff

162
src/lib/form.js Normal file
View File

@ -0,0 +1,162 @@
/**
* Functions for manipulating web forms.
*
* @author David I. Lehn <dlehn@digitalbazaar.com>
* @author Dave Longley
* @author Mike Johnson
*
* Copyright (c) 2011-2012 Digital Bazaar, Inc. All rights reserved.
*/
(function($) {
/**
* The form namespace.
*/
var form = {};
/**
* Regex for parsing a single name property (handles array brackets).
*/
var _regex = /(.*?)\[(.*?)\]/g;
/**
* Parses a single name property into an array with the name and any
* array indices.
*
* @param name the name to parse.
*
* @return the array of the name and its array indices in order.
*/
var _parseName = function(name) {
var rval = [];
var matches;
while(!!(matches = _regex.exec(name))) {
if(matches[1].length > 0) {
rval.push(matches[1]);
}
if(matches.length >= 2) {
rval.push(matches[2]);
}
}
if(rval.length === 0) {
rval.push(name);
}
return rval;
};
/**
* Adds a field from the given form to the given object.
*
* @param obj the object.
* @param names the field as an array of object property names.
* @param value the value of the field.
* @param dict a dictionary of names to replace.
*/
var _addField = function(obj, names, value, dict) {
// combine array names that fall within square brackets
var tmp = [];
for(var i = 0; i < names.length; ++i) {
// check name for starting square bracket but no ending one
var name = names[i];
if(name.indexOf('[') !== -1 && name.indexOf(']') === -1 &&
i < names.length - 1) {
do {
name += '.' + names[++i];
}
while(i < names.length - 1 && names[i].indexOf(']') === -1);
}
tmp.push(name);
}
names = tmp;
// split out array indexes
var tmp = [];
$.each(names, function(n, name) {
tmp = tmp.concat(_parseName(name));
});
names = tmp;
// iterate over object property names until value is set
$.each(names, function(n, name) {
// do dictionary name replacement
if(dict && name.length !== 0 && name in dict) {
name = dict[name];
}
// blank name indicates appending to an array, set name to
// new last index of array
if(name.length === 0) {
name = obj.length;
}
// value already exists, append value
if(obj[name]) {
// last name in the field
if(n == names.length - 1) {
// more than one value, so convert into an array
if(!$.isArray(obj[name])) {
obj[name] = [obj[name]];
}
obj[name].push(value);
}
// not last name, go deeper into object
else {
obj = obj[name];
}
}
// new value, last name in the field, set value
else if(n == names.length - 1) {
obj[name] = value;
}
// new value, not last name, go deeper
else {
// get next name
var next = names[n + 1];
// blank next value indicates array-appending, so create array
if(next.length === 0) {
obj[name] = [];
}
// if next name is a number create an array, otherwise a map
else {
var isNum = ((next - 0) == next && next.length > 0);
obj[name] = isNum ? [] : {};
}
obj = obj[name];
}
});
};
/**
* Serializes a form to a JSON object. Object properties will be separated
* using the given separator (defaults to '.') and by square brackets.
*
* @param input the jquery form to serialize.
* @param sep the object-property separator (defaults to '.').
* @param dict a dictionary of names to replace (name=replace).
*
* @return the JSON-serialized form.
*/
form.serialize = function(input, sep, dict) {
var rval = {};
// add all fields in the form to the object
sep = sep || '.';
$.each(input.serializeArray(), function() {
_addField(rval, this.name.split(sep), this.value || '', dict);
});
return rval;
};
/**
* The forge namespace and form API.
*/
if(typeof forge === 'undefined') {
forge = {};
}
forge.form = form;
})(jQuery);

196
src/lib/hmac.js Normal file
View File

@ -0,0 +1,196 @@
/**
* Hash-based Message Authentication Code implementation. Requires a message
* digest object that can be obtained, for example, from forge.md.sha1 or
* forge.md.md5.
*
* @author Dave Longley
*
* Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
/* HMAC API */
var hmac = forge.hmac = forge.hmac || {};
/**
* Creates an HMAC object that uses the given message digest object.
*
* @return an HMAC object.
*/
hmac.create = function() {
// the hmac key to use
var _key = null;
// the message digest to use
var _md = null;
// the inner padding
var _ipadding = null;
// the outer padding
var _opadding = null;
// hmac context
var ctx = {};
/**
* Starts or restarts the HMAC with the given key and message digest.
*
* @param md the message digest to use, null to reuse the previous one,
* a string to use builtin 'sha1', 'md5', 'sha256'.
* @param key the key to use as a string, array of bytes, byte buffer,
* or null to reuse the previous key.
*/
ctx.start = function(md, key) {
if(md !== null) {
if(md.constructor == String) {
// create builtin message digest
md = md.toLowerCase();
if(md in forge.md.algorithms) {
_md = forge.md.algorithms[md].create();
}
else {
throw 'Unknown hash algorithm "' + md + '"';
}
}
else {
// store message digest
_md = md;
}
}
if(key === null) {
// reuse previous key
key = _key;
}
else {
// convert string into byte buffer
if(key.constructor == String) {
key = forge.util.createBuffer(key);
}
// convert byte array into byte buffer
else if(key.constructor == Array) {
var tmp = key;
key = forge.util.createBuffer();
for(var i = 0; i < tmp.length; ++i) {
key.putByte(tmp[i]);
}
}
// if key is longer than blocksize, hash it
var keylen = key.length();
if(keylen > _md.blockLength) {
_md.start();
_md.update(key.bytes());
key = _md.digest();
}
// mix key into inner and outer padding
// ipadding = [0x36 * blocksize] ^ key
// opadding = [0x5C * blocksize] ^ key
_ipadding = forge.util.createBuffer();
_opadding = forge.util.createBuffer();
keylen = key.length();
for(var i = 0; i < keylen; ++i) {
var tmp = key.at(i);
_ipadding.putByte(0x36 ^ tmp);
_opadding.putByte(0x5C ^ tmp);
}
// if key is shorter than blocksize, add additional padding
if(keylen < _md.blockLength) {
var tmp = _md.blockLength - keylen;
for(var i = 0; i < tmp; ++i) {
_ipadding.putByte(0x36);
_opadding.putByte(0x5C);
}
}
_key = key;
_ipadding = _ipadding.bytes();
_opadding = _opadding.bytes();
}
// digest is done like so: hash(opadding | hash(ipadding | message))
// prepare to do inner hash
// hash(ipadding | message)
_md.start();
_md.update(_ipadding);
};
/**
* Updates the HMAC with the given message bytes.
*
* @param bytes the bytes to update with.
*/
ctx.update = function(bytes) {
_md.update(bytes);
};
/**
* Produces the Message Authentication Code (MAC).
*
* @return a byte buffer containing the digest value.
*/
ctx.getMac = function() {
// digest is done like so: hash(opadding | hash(ipadding | message))
// here we do the outer hashing
var inner = _md.digest().bytes();
_md.start();
_md.update(_opadding);
_md.update(inner);
return _md.digest();
};
// alias for getMac
ctx.digest = ctx.getMac;
return ctx;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'hmac';
var deps = ['./md', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

1408
src/lib/http.js Normal file

File diff suppressed because it is too large Load Diff

368
src/lib/log.js Normal file
View File

@ -0,0 +1,368 @@
/**
* Cross-browser support for logging in a web application.
*
* @author David I. Lehn <dlehn@digitalbazaar.com>
*
* Copyright (c) 2008-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
/* LOG API */
forge.log = forge.log || {};
/**
* Application logging system.
*
* Each logger level available as it's own function of the form:
* forge.log.level(category, args...)
* The category is an arbitrary string, and the args are the same as
* Firebug's console.log API. By default the call will be output as:
* 'LEVEL [category] <args[0]>, args[1], ...'
* This enables proper % formatting via the first argument.
* Each category is enabled by default but can be enabled or disabled with
* the setCategoryEnabled() function.
*/
// list of known levels
forge.log.levels = [
'none', 'error', 'warning', 'info', 'debug', 'verbose', 'max'];
// info on the levels indexed by name:
// index: level index
// name: uppercased display name
var sLevelInfo = {};
// list of loggers
var sLoggers = [];
/**
* Standard console logger. If no console support is enabled this will
* remain null. Check before using.
*/
var sConsoleLogger = null;
// logger flags
/**
* Lock the level at the current value. Used in cases where user config may
* set the level such that only critical messages are seen but more verbose
* messages are needed for debugging or other purposes.
*/
forge.log.LEVEL_LOCKED = (1 << 1);
/**
* Always call log function. By default, the logging system will check the
* message level against logger.level before calling the log function. This
* flag allows the function to do its own check.
*/
forge.log.NO_LEVEL_CHECK = (1 << 2);
/**
* Perform message interpolation with the passed arguments. "%" style
* fields in log messages will be replaced by arguments as needed. Some
* loggers, such as Firebug, may do this automatically. The original log
* message will be available as 'message' and the interpolated version will
* be available as 'fullMessage'.
*/
forge.log.INTERPOLATE = (1 << 3);
// setup each log level
for(var i = 0; i < forge.log.levels.length; ++i) {
var level = forge.log.levels[i];
sLevelInfo[level] = {
index: i,
name: level.toUpperCase()
};
}
/**
* Message logger. Will dispatch a message to registered loggers as needed.
*
* @param message message object
*/
forge.log.logMessage = function(message) {
var messageLevelIndex = sLevelInfo[message.level].index;
for(var i = 0; i < sLoggers.length; ++i) {
var logger = sLoggers[i];
if(logger.flags & forge.log.NO_LEVEL_CHECK) {
logger.f(message);
}
else {
// get logger level
var loggerLevelIndex = sLevelInfo[logger.level].index;
// check level
if(messageLevelIndex <= loggerLevelIndex) {
// message critical enough, call logger
logger.f(logger, message);
}
}
}
};
/**
* Sets the 'standard' key on a message object to:
* "LEVEL [category] " + message
*
* @param message a message log object
*/
forge.log.prepareStandard = function(message) {
if(!('standard' in message)) {
message.standard =
sLevelInfo[message.level].name +
//' ' + +message.timestamp +
' [' + message.category + '] ' +
message.message;
}
};
/**
* Sets the 'full' key on a message object to the original message
* interpolated via % formatting with the message arguments.
*
* @param message a message log object.
*/
forge.log.prepareFull = function(message) {
if(!('full' in message)) {
// copy args and insert message at the front
var args = [message.message];
args = args.concat([] || message['arguments']);
// format the message
message.full = forge.util.format.apply(this, args);
}
};
/**
* Applies both preparseStandard() and prepareFull() to a message object and
* store result in 'standardFull'.
*
* @param message a message log object.
*/
forge.log.prepareStandardFull = function(message) {
if(!('standardFull' in message)) {
// FIXME implement 'standardFull' logging
forge.log.prepareStandard(message);
message.standardFull = message.standard;
}
};
// create log level functions
if(true) {
// levels for which we want functions
var levels = ['error', 'warning', 'info', 'debug', 'verbose'];
for(var i = 0; i < levels.length; ++i) {
// wrap in a function to ensure proper level var is passed
(function(level) {
// create function for this level
forge.log[level] = function(category, message/*, args...*/) {
// convert arguments to real array, remove category and message
var args = Array.prototype.slice.call(arguments).slice(2);
// create message object
// Note: interpolation and standard formatting is done lazily
var msg = {
timestamp: new Date(),
level: level,
category: category,
message: message,
'arguments': args
/*standard*/
/*full*/
/*fullMessage*/
};
// process this message
forge.log.logMessage(msg);
};
})(levels[i]);
}
}
/**
* Creates a new logger with specified custom logging function.
*
* The logging function has a signature of:
* function(logger, message)
* logger: current logger
* message: object:
* level: level id
* category: category
* message: string message
* arguments: Array of extra arguments
* fullMessage: interpolated message and arguments if INTERPOLATE flag set
*
* @param logFunction a logging function which takes a log message object
* as a parameter.
*
* @return a logger object.
*/
forge.log.makeLogger = function(logFunction) {
var logger = {
flags: 0,
f: logFunction
};
forge.log.setLevel(logger, 'none');
return logger;
};
/**
* Sets the current log level on a logger.
*
* @param logger the target logger.
* @param level the new maximum log level as a string.
*
* @return true if set, false if not.
*/
forge.log.setLevel = function(logger, level) {
var rval = false;
if(logger && !(logger.flags & forge.log.LEVEL_LOCKED)) {
for(var i = 0; i < forge.log.levels.length; ++i) {
var aValidLevel = forge.log.levels[i];
if(level == aValidLevel) {
// set level
logger.level = level;
rval = true;
break;
}
}
}
return rval;
};
/**
* Locks the log level at its current value.
*
* @param logger the target logger.
* @param lock boolean lock value, default to true.
*/
forge.log.lock = function(logger, lock) {
if(typeof lock === 'undefined' || lock) {
logger.flags |= forge.log.LEVEL_LOCKED;
}
else {
logger.flags &= ~forge.log.LEVEL_LOCKED;
}
};
/**
* Adds a logger.
*
* @param logger the logger object.
*/
forge.log.addLogger = function(logger) {
sLoggers.push(logger);
};
// setup the console logger if possible, else create fake console.log
if(typeof(console) !== 'undefined' && 'log' in console) {
var logger;
if(console.error && console.warn && console.info && console.debug) {
// looks like Firebug-style logging is available
// level handlers map
var levelHandlers = {
error: console.error,
warning: console.warn,
info: console.info,
debug: console.debug,
verbose: console.debug
};
var f = function(logger, message) {
forge.log.prepareStandard(message);
var handler = levelHandlers[message.level];
// prepend standard message and concat args
var args = [message.standard];
args = args.concat(message['arguments'].slice());
// apply to low-level console function
handler.apply(console, args);
};
logger = forge.log.makeLogger(f);
}
else {
// only appear to have basic console.log
var f = function(logger, message) {
forge.log.prepareStandardFull(message);
console.log(message.standardFull);
};
logger = forge.log.makeLogger(f);
}
forge.log.setLevel(logger, 'debug');
forge.log.addLogger(logger);
sConsoleLogger = logger;
}
else {
// define fake console.log to avoid potential script errors on
// browsers that do not have console logging
console = {
log: function() {}
};
}
/*
* Check for logging control query vars.
*
* console.level=<level-name>
* Set's the console log level by name. Useful to override defaults and
* allow more verbose logging before a user config is loaded.
*
* console.lock=<true|false>
* Lock the console log level at whatever level it is set at. This is run
* after console.level is processed. Useful to force a level of verbosity
* that could otherwise be limited by a user config.
*/
if(sConsoleLogger !== null) {
var query = forge.util.getQueryVariables();
if('console.level' in query) {
// set with last value
forge.log.setLevel(
sConsoleLogger, query['console.level'].slice(-1)[0]);
}
if('console.lock' in query) {
// set with last value
var lock = query['console.lock'].slice(-1)[0];
if(lock == 'true') {
forge.log.lock(sConsoleLogger);
}
}
}
// provide public access to console logger
forge.log.consoleLogger = sConsoleLogger;
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'log';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

66
src/lib/md.js Normal file
View File

@ -0,0 +1,66 @@
/**
* Node.js module for Forge message digests.
*
* @author Dave Longley
*
* Copyright 2011-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
forge.md = forge.md || {};
forge.md.algorithms = {
md5: forge.md5,
sha1: forge.sha1,
sha256: forge.sha256
};
forge.md.md5 = forge.md5;
forge.md.sha1 = forge.sha1;
forge.md.sha256 = forge.sha256;
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'md';
var deps = ['./md5', './sha1', './sha256'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

303
src/lib/md5.js Normal file
View File

@ -0,0 +1,303 @@
/**
* Message Digest Algorithm 5 with 128-bit digest (MD5) implementation.
*
* This implementation is currently limited to message lengths (in bytes) that
* are up to 32-bits in size.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var md5 = forge.md5 = forge.md5 || {};
forge.md = forge.md || {};
forge.md.algorithms = forge.md.algorithms || {};
forge.md.md5 = forge.md.algorithms['md5'] = md5;
// padding, constant tables for calculating md5
var _padding = null;
var _g = null;
var _r = null;
var _k = null;
var _initialized = false;
/**
* Initializes the constant tables.
*/
var _init = function() {
// create padding
_padding = String.fromCharCode(128);
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
// g values
_g = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9];
// rounds table
_r = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21];
// get the result of abs(sin(i + 1)) as a 32-bit integer
_k = new Array(64);
for(var i = 0; i < 64; ++i) {
_k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
}
// now initialized
_initialized = true;
};
/**
* Updates an MD5 state with the given byte buffer.
*
* @param s the MD5 state to update.
* @param w the array to use to store words.
* @param bytes the byte buffer to update with.
*/
var _update = function(s, w, bytes) {
// consume 512 bit (64 byte) chunks
var t, a, b, c, d, f, r, i;
var len = bytes.length();
while(len >= 64) {
// initialize hash value for this chunk
a = s.h0;
b = s.h1;
c = s.h2;
d = s.h3;
// round 1
for(i = 0; i < 16; ++i) {
w[i] = bytes.getInt32Le();
f = d ^ (b & (c ^ d));
t = (a + f + _k[i] + w[i]);
r = _r[i];
a = d;
d = c;
c = b;
b += (t << r) | (t >>> (32 - r));
}
// round 2
for(; i < 32; ++i) {
f = c ^ (d & (b ^ c));
t = (a + f + _k[i] + w[_g[i]]);
r = _r[i];
a = d;
d = c;
c = b;
b += (t << r) | (t >>> (32 - r));
}
// round 3
for(; i < 48; ++i) {
f = b ^ c ^ d;
t = (a + f + _k[i] + w[_g[i]]);
r = _r[i];
a = d;
d = c;
c = b;
b += (t << r) | (t >>> (32 - r));
}
// round 4
for(; i < 64; ++i) {
f = c ^ (b | ~d);
t = (a + f + _k[i] + w[_g[i]]);
r = _r[i];
a = d;
d = c;
c = b;
b += (t << r) | (t >>> (32 - r));
}
// update hash state
s.h0 = (s.h0 + a) & 0xFFFFFFFF;
s.h1 = (s.h1 + b) & 0xFFFFFFFF;
s.h2 = (s.h2 + c) & 0xFFFFFFFF;
s.h3 = (s.h3 + d) & 0xFFFFFFFF;
len -= 64;
}
};
/**
* Creates an MD5 message digest object.
*
* @return a message digest object.
*/
md5.create = function() {
// do initialization as necessary
if(!_initialized) {
_init();
}
// MD5 state contains four 32-bit integers
var _state = null;
// input buffer
var _input = forge.util.createBuffer();
// used for word storage
var _w = new Array(16);
// message digest object
var md = {
algorithm: 'md5',
blockLength: 64,
digestLength: 16,
// length of message so far (does not including padding)
messageLength: 0
};
/**
* Starts the digest.
*/
md.start = function() {
md.messageLength = 0;
_input = forge.util.createBuffer();
_state = {
h0: 0x67452301,
h1: 0xEFCDAB89,
h2: 0x98BADCFE,
h3: 0x10325476
};
};
// start digest automatically for first time
md.start();
/**
* Updates the digest with the given message input. The given input can
* treated as raw input (no encoding will be applied) or an encoding of
* 'utf8' maybe given to encode the input using UTF-8.
*
* @param msg the message input to update with.
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
*/
md.update = function(msg, encoding) {
if(encoding === 'utf8') {
msg = forge.util.encodeUtf8(msg);
}
// update message length
md.messageLength += msg.length;
// add bytes to input buffer
_input.putBytes(msg);
// process bytes
_update(_state, _w, _input);
// compact input buffer every 2K or if empty
if(_input.read > 2048 || _input.length() === 0) {
_input.compact();
}
};
/**
* Produces the digest.
*
* @return a byte buffer containing the digest value.
*/
md.digest = function() {
/* Note: Here we copy the remaining bytes in the input buffer and
add the appropriate MD5 padding. Then we do the final update
on a copy of the state so that if the user wants to get
intermediate digests they can do so. */
/* Determine the number of bytes that must be added to the message
to ensure its length is congruent to 448 mod 512. In other words,
a 64-bit integer that gives the length of the message will be
appended to the message and whatever the length of the message is
plus 64 bits must be a multiple of 512. So the length of the
message must be congruent to 448 mod 512 because 512 - 64 = 448.
In order to fill up the message length it must be filled with
padding that begins with 1 bit followed by all 0 bits. Padding
must *always* be present, so if the message length is already
congruent to 448 mod 512, then 512 padding bits must be added. */
// 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
// _padding starts with 1 byte with first bit is set in it which
// is byte value 128, then there may be up to 63 other pad bytes
var len = md.messageLength;
var padBytes = forge.util.createBuffer();
padBytes.putBytes(_input.bytes());
padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64)));
/* Now append length of the message. The length is appended in bits
as a 64-bit number in little-endian format. Since we store the
length in bytes, we must multiply it by 8 (or left shift by 3). So
here store the high 3 bits in the high end of the second 32-bits of
the 64-bit number and the lower 5 bits in the low end of the
second 32-bits. */
padBytes.putInt32Le((len << 3) & 0xFFFFFFFF);
padBytes.putInt32Le((len >>> 29) & 0xFF);
var s2 = {
h0: _state.h0,
h1: _state.h1,
h2: _state.h2,
h3: _state.h3
};
_update(s2, _w, padBytes);
var rval = forge.util.createBuffer();
rval.putInt32Le(s2.h0);
rval.putInt32Le(s2.h1);
rval.putInt32Le(s2.h2);
rval.putInt32Le(s2.h3);
return rval;
};
return md;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'md5';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

59
src/lib/mgf.js Normal file
View File

@ -0,0 +1,59 @@
/**
* Node.js module for Forge mask generation functions.
*
* @author Stefan Siegl
*
* Copyright 2012 Stefan Siegl <stesie@brokenpipe.de>
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
forge.mgf = forge.mgf || {};
forge.mgf.mgf1 = forge.mgf1;
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'mgf';
var deps = ['./mgf1'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

100
src/lib/mgf1.js Normal file
View File

@ -0,0 +1,100 @@
/**
* Javascript implementation of mask generation function MGF1.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
forge.mgf = forge.mgf || {};
var mgf1 = forge.mgf.mgf1 = forge.mgf1 = forge.mgf1 || {};
/**
* Creates a MGF1 mask generation function object.
*
* @return a mask generation function object.
*/
mgf1.create = function(md) {
var mgf = {
/**
* Generate mask of specified length.
*
* @param {String} seed The seed for mask generation.
* @param maskLen Number of bytes to generate.
* @return {String} The generated mask.
*/
generate: function(seed, maskLen) {
/* 2. Let T be the empty octet string. */
var t = new forge.util.ByteBuffer();
/* 3. For counter from 0 to ceil(maskLen / hLen), do the following: */
var len = Math.ceil(maskLen / md.digestLength);
for(var i = 0; i < len; i++) {
/* a. Convert counter to an octet string C of length 4 octets */
var c = new forge.util.ByteBuffer();
c.putInt32(i);
/* b. Concatenate the hash of the seed mgfSeed and C to the octet
* string T: */
md.start();
md.update(seed + c.getBytes());
t.putBuffer(md.digest());
}
/* Output the leading maskLen octets of T as the octet string mask. */
t.truncate(t.length() - maskLen);
return t.getBytes();
}
};
return mgf;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'mgf1';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

234
src/lib/oids.js Normal file
View File

@ -0,0 +1,234 @@
/**
* Object IDs for ASN.1.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
forge.pki = forge.pki || {};
var oids = forge.pki.oids = forge.oids = forge.oids || {};
// algorithm OIDs
oids['1.2.840.113549.1.1.1'] = 'rsaEncryption';
oids['rsaEncryption'] = '1.2.840.113549.1.1.1';
// Note: md2 & md4 not implemented
//oids['1.2.840.113549.1.1.2'] = 'md2withRSAEncryption';
//oids['md2withRSAEncryption'] = '1.2.840.113549.1.1.2';
//oids['1.2.840.113549.1.1.3'] = 'md4withRSAEncryption';
//oids['md4withRSAEncryption'] = '1.2.840.113549.1.1.3';
oids['1.2.840.113549.1.1.4'] = 'md5withRSAEncryption';
oids['md5withRSAEncryption'] = '1.2.840.113549.1.1.4';
oids['1.2.840.113549.1.1.5'] = 'sha1withRSAEncryption';
oids['sha1withRSAEncryption'] = '1.2.840.113549.1.1.5';
oids['1.2.840.113549.1.1.7'] = 'RSAES-OAEP';
oids['RSAES-OAEP'] = '1.2.840.113549.1.1.7';
oids['1.2.840.113549.1.1.8'] = 'mgf1';
oids['mgf1'] = '1.2.840.113549.1.1.8';
oids['1.2.840.113549.1.1.9'] = 'pSpecified';
oids['pSpecified'] = '1.2.840.113549.1.1.9';
oids['1.2.840.113549.1.1.10'] = 'RSASSA-PSS';
oids['RSASSA-PSS'] = '1.2.840.113549.1.1.10';
oids['1.2.840.113549.1.1.11'] = 'sha256WithRSAEncryption';
oids['sha256WithRSAEncryption'] = '1.2.840.113549.1.1.11';
oids['1.2.840.113549.1.1.12'] = 'sha384WithRSAEncryption';
oids['sha384WithRSAEncryption'] = '1.2.840.113549.1.1.12';
oids['1.2.840.113549.1.1.13'] = 'sha512WithRSAEncryption';
oids['sha512WithRSAEncryption'] = '1.2.840.113549.1.1.13';
oids['1.3.14.3.2.26'] = 'sha1';
oids['sha1'] = '1.3.14.3.2.26';
oids['2.16.840.1.101.3.4.2.1'] = 'sha256';
oids['sha256'] = '2.16.840.1.101.3.4.2.1';
oids['2.16.840.1.101.3.4.2.2'] = 'sha384';
oids['sha384'] = '2.16.840.1.101.3.4.2.2';
oids['2.16.840.1.101.3.4.2.3'] = 'sha512';
oids['sha512'] = '2.16.840.1.101.3.4.2.3';
oids['1.2.840.113549.2.5'] = 'md5';
oids['md5'] = '1.2.840.113549.2.5';
// pkcs#7 content types
oids['1.2.840.113549.1.7.1'] = 'data';
oids['data'] = '1.2.840.113549.1.7.1';
oids['1.2.840.113549.1.7.2'] = 'signedData';
oids['signedData'] = '1.2.840.113549.1.7.2';
oids['1.2.840.113549.1.7.3'] = 'envelopedData';
oids['envelopedData'] = '1.2.840.113549.1.7.3';
oids['1.2.840.113549.1.7.4'] = 'signedAndEnvelopedData';
oids['signedAndEnvelopedData'] = '1.2.840.113549.1.7.4';
oids['1.2.840.113549.1.7.5'] = 'digestedData';
oids['digestedData'] = '1.2.840.113549.1.7.5';
oids['1.2.840.113549.1.7.6'] = 'encryptedData';
oids['encryptedData'] = '1.2.840.113549.1.7.6';
// pkcs#9 oids
oids['1.2.840.113549.1.9.20'] = 'friendlyName';
oids['friendlyName'] = '1.2.840.113549.1.9.20';
oids['1.2.840.113549.1.9.21'] = 'localKeyId';
oids['localKeyId'] = '1.2.840.113549.1.9.21';
oids['1.2.840.113549.1.9.22.1'] = 'x509Certificate';
oids['x509Certificate'] = '1.2.840.113549.1.9.22.1';
oids['1.2.840.113549.1.9.4'] = 'messageDigest';
oids['messageDigest'] = '1.2.840.113549.1.9.4';
oids['1.2.840.113549.1.9.3'] = 'contentType';
oids['contentType'] = '1.2.840.113549.1.9.3';
oids['1.2.840.113549.1.9.5'] = 'signingTime';
oids['signingTime'] = '1.2.840.113549.1.9.5';
// pkcs#12 safe bags
oids['1.2.840.113549.1.12.10.1.1'] = 'keyBag';
oids['keyBag'] = '1.2.840.113549.1.12.10.1.1';
oids['1.2.840.113549.1.12.10.1.2'] = 'pkcs8ShroudedKeyBag';
oids['pkcs8ShroudedKeyBag'] = '1.2.840.113549.1.12.10.1.2';
oids['1.2.840.113549.1.12.10.1.3'] = 'certBag';
oids['certBag'] = '1.2.840.113549.1.12.10.1.3';
oids['1.2.840.113549.1.12.10.1.4'] = 'crlBag';
oids['crlBag'] = '1.2.840.113549.1.12.10.1.4';
oids['1.2.840.113549.1.12.10.1.5'] = 'secretBag';
oids['secretBag'] = '1.2.840.113549.1.12.10.1.5';
oids['1.2.840.113549.1.12.10.1.6'] = 'safeContentsBag';
oids['safeContentsBag'] = '1.2.840.113549.1.12.10.1.6';
// password-based-encryption for pkcs#12
oids['1.2.840.113549.1.5.13'] = 'pkcs5PBES2';
oids['pkcs5PBES2'] = '1.2.840.113549.1.5.13';
oids['1.2.840.113549.1.5.12'] = 'pkcs5PBKDF2';
oids['pkcs5PBKDF2'] = '1.2.840.113549.1.5.12';
oids['1.2.840.113549.1.12.1.1'] = 'pbeWithSHAAnd128BitRC4';
oids['pbeWithSHAAnd128BitRC4'] = '1.2.840.113549.1.12.1.1';
oids['1.2.840.113549.1.12.1.2'] = 'pbeWithSHAAnd40BitRC4';
oids['pbeWithSHAAnd40BitRC4'] = '1.2.840.113549.1.12.1.2';
oids['1.2.840.113549.1.12.1.3'] = 'pbeWithSHAAnd3-KeyTripleDES-CBC';
oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.3';
oids['1.2.840.113549.1.12.1.4'] = 'pbeWithSHAAnd2-KeyTripleDES-CBC';
oids['pbeWithSHAAnd2-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.4';
oids['1.2.840.113549.1.12.1.5'] = 'pbeWithSHAAnd128BitRC2-CBC';
oids['pbeWithSHAAnd128BitRC2-CBC'] = '1.2.840.113549.1.12.1.5';
oids['1.2.840.113549.1.12.1.6'] = 'pbewithSHAAnd40BitRC2-CBC';
oids['pbewithSHAAnd40BitRC2-CBC'] = '1.2.840.113549.1.12.1.6';
// symmetric key algorithm oids
oids['1.2.840.113549.3.7'] = 'des-EDE3-CBC';
oids['des-EDE3-CBC'] = '1.2.840.113549.3.7';
oids['2.16.840.1.101.3.4.1.2'] = 'aes128-CBC';
oids['aes128-CBC'] = '2.16.840.1.101.3.4.1.2';
oids['2.16.840.1.101.3.4.1.22'] = 'aes192-CBC';
oids['aes192-CBC'] = '2.16.840.1.101.3.4.1.22';
oids['2.16.840.1.101.3.4.1.42'] = 'aes256-CBC';
oids['aes256-CBC'] = '2.16.840.1.101.3.4.1.42';
// certificate issuer/subject OIDs
oids['2.5.4.3'] = 'commonName';
oids['commonName'] = '2.5.4.3';
oids['2.5.4.5'] = 'serialName';
oids['serialName'] = '2.5.4.5';
oids['2.5.4.6'] = 'countryName';
oids['countryName'] = '2.5.4.6';
oids['2.5.4.7'] = 'localityName';
oids['localityName'] = '2.5.4.7';
oids['2.5.4.8'] = 'stateOrProvinceName';
oids['stateOrProvinceName'] = '2.5.4.8';
oids['2.5.4.10'] = 'organizationName';
oids['organizationName'] = '2.5.4.10';
oids['2.5.4.11'] = 'organizationalUnitName';
oids['organizationalUnitName'] = '2.5.4.11';
oids['1.2.840.113549.1.9.1'] = 'emailAddress';
oids['emailAddress'] = '1.2.840.113549.1.9.1';
// X.509 extension OIDs
oids['2.5.29.1'] = 'authorityKeyIdentifier'; // deprecated, use .35
oids['2.5.29.2'] = 'keyAttributes'; // obsolete use .37 or .15
oids['2.5.29.3'] = 'certificatePolicies'; // deprecated, use .32
oids['2.5.29.4'] = 'keyUsageRestriction'; // obsolete use .37 or .15
oids['2.5.29.5'] = 'policyMapping'; // deprecated use .33
oids['2.5.29.6'] = 'subtreesConstraint'; // obsolete use .30
oids['2.5.29.7'] = 'subjectAltName'; // deprecated use .17
oids['2.5.29.8'] = 'issuerAltName'; // deprecated use .18
oids['2.5.29.9'] = 'subjectDirectoryAttributes';
oids['2.5.29.10'] = 'basicConstraints'; // deprecated use .19
oids['2.5.29.11'] = 'nameConstraints'; // deprecated use .30
oids['2.5.29.12'] = 'policyConstraints'; // deprecated use .36
oids['2.5.29.13'] = 'basicConstraints'; // deprecated use .19
oids['2.5.29.14'] = 'subjectKeyIdentifier';
oids['subjectKeyIdentifier'] = '2.5.29.14';
oids['2.5.29.15'] = 'keyUsage';
oids['keyUsage'] = '2.5.29.15';
oids['2.5.29.16'] = 'privateKeyUsagePeriod';
oids['2.5.29.17'] = 'subjectAltName';
oids['subjectAltName'] = '2.5.29.17';
oids['2.5.29.18'] = 'issuerAltName';
oids['issuerAltName'] = '2.5.29.18';
oids['2.5.29.19'] = 'basicConstraints';
oids['basicConstraints'] = '2.5.29.19';
oids['2.5.29.20'] = 'cRLNumber';
oids['2.5.29.21'] = 'cRLReason';
oids['2.5.29.22'] = 'expirationDate';
oids['2.5.29.23'] = 'instructionCode';
oids['2.5.29.24'] = 'invalidityDate';
oids['2.5.29.25'] = 'cRLDistributionPoints'; // deprecated use .31
oids['2.5.29.26'] = 'issuingDistributionPoint'; // deprecated use .28
oids['2.5.29.27'] = 'deltaCRLIndicator';
oids['2.5.29.28'] = 'issuingDistributionPoint';
oids['2.5.29.29'] = 'certificateIssuer';
oids['2.5.29.30'] = 'nameConstraints';
oids['2.5.29.31'] = 'cRLDistributionPoints';
oids['2.5.29.32'] = 'certificatePolicies';
oids['2.5.29.33'] = 'policyMappings';
oids['2.5.29.34'] = 'policyConstraints'; // deprecated use .36
oids['2.5.29.35'] = 'authorityKeyIdentifier';
oids['2.5.29.36'] = 'policyConstraints';
oids['2.5.29.37'] = 'extKeyUsage';
oids['extKeyUsage'] = '2.5.29.37';
oids['2.5.29.46'] = 'freshestCRL';
oids['2.5.29.54'] = 'inhibitAnyPolicy';
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'oids';
var deps = [];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

153
src/lib/pbkdf2.js Normal file
View File

@ -0,0 +1,153 @@
/**
* Password-Based Key-Derivation Function #2 implementation.
*
* See RFC 2898 for details.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var pkcs5 = forge.pkcs5 = forge.pkcs5 || {};
/**
* Derives a key from a password.
*
* @param p the password as a string of bytes.
* @param s the salt as a string of bytes.
* @param c the iteration count, a positive integer.
* @param dkLen the intended length, in bytes, of the derived key,
* (max: 2^32 - 1) * hash length of the PRF.
* @param md the message digest to use in the PRF, defaults to SHA-1.
*
* @return the derived key, as a string of bytes.
*/
pkcs5.pbkdf2 = function(p, s, c, dkLen, md) {
// default prf to SHA-1
if(typeof(md) === 'undefined' || md === null) {
md = forge.md.sha1.create();
}
var hLen = md.digestLength;
/* 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
stop. */
if(dkLen > (0xFFFFFFFF * hLen)) {
throw {
message: 'Derived key is too long.'
};
}
/* 2. Let len be the number of hLen-octet blocks in the derived key,
rounding up, and let r be the number of octets in the last
block:
len = CEIL(dkLen / hLen),
r = dkLen - (len - 1) * hLen. */
var len = Math.ceil(dkLen / hLen);
var r = dkLen - (len - 1) * hLen;
/* 3. For each block of the derived key apply the function F defined
below to the password P, the salt S, the iteration count c, and
the block index to compute the block:
T_1 = F(P, S, c, 1),
T_2 = F(P, S, c, 2),
...
T_len = F(P, S, c, len),
where the function F is defined as the exclusive-or sum of the
first c iterates of the underlying pseudorandom function PRF
applied to the password P and the concatenation of the salt S
and the block index i:
F(P, S, c, i) = u_1 XOR u_2 XOR ... XOR u_c
where
u_1 = PRF(P, S || INT(i)),
u_2 = PRF(P, u_1),
...
u_c = PRF(P, u_{c-1}).
Here, INT(i) is a four-octet encoding of the integer i, most
significant octet first. */
var prf = forge.hmac.create();
prf.start(md, p);
var dk = '';
var xor, u_c, u_c1;
for(var i = 1; i <= len; ++i) {
// PRF(P, S || INT(i)) (first iteration)
prf.update(s);
prf.update(forge.util.int32ToBytes(i));
xor = u_c1 = prf.digest().getBytes();
// PRF(P, u_{c-1}) (other iterations)
for(var j = 2; j <= c; ++j) {
prf.start(null, null);
prf.update(u_c1);
u_c = prf.digest().getBytes();
// F(p, s, c, i)
xor = forge.util.xorBytes(xor, u_c, hLen);
u_c1 = u_c;
}
/* 4. Concatenate the blocks and extract the first dkLen octets to
produce a derived key DK:
DK = T_1 || T_2 || ... || T_len<0..r-1> */
dk += (i < len) ? xor : xor.substr(0, r);
}
/* 5. Output the derived key DK. */
return dk;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'pbkdf2';
var deps = ['./hmac', './md', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

1110
src/lib/pkcs12.js Normal file

File diff suppressed because it is too large Load Diff

710
src/lib/pkcs7.js Normal file
View File

@ -0,0 +1,710 @@
/**
* Javascript implementation of PKCS#7 v1.5. Currently only certain parts of
* PKCS#7 are implemented, especially the enveloped-data content type.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*
* Currently this implementation only supports ContentType of either
* EnvelopedData or EncryptedData on root level. The top level elements may
* contain only a ContentInfo of ContentType Data, i.e. plain data. Further
* nesting is not (yet) supported.
*
* The Forge validators for PKCS #7's ASN.1 structures are available from
* a seperate file pkcs7asn1.js, since those are referenced from other
* PKCS standards like PKCS #12.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// shortcut for ASN.1 API
var asn1 = forge.asn1;
// shortcut for PKCS#7 API
var p7 = forge.pkcs7 = forge.pkcs7 || {};
/**
* Converts a PKCS#7 message from PEM format.
*
* @param pem the PEM-formatted PKCS#7 message.
*
* @return the PKCS#7 message.
*/
p7.messageFromPem = function(pem) {
var der = forge.pki.pemToDer(pem);
var obj = asn1.fromDer(der);
return p7.messageFromAsn1(obj);
};
/**
* Converts a PKCS#7 message to PEM format.
*
* @param msg The PKCS#7 message object
* @param maxline The maximum characters per line, defaults to 64.
*
* @return The PEM-formatted PKCS#7 message.
*/
p7.messageToPem = function(msg, maxline) {
var out = asn1.toDer(msg.toAsn1());
out = forge.util.encode64(out.getBytes(), maxline || 64);
return (
'-----BEGIN PKCS7-----\r\n' +
out +
'\r\n-----END PKCS7-----');
};
/**
* Converts a PKCS#7 message from an ASN.1 object.
*
* @param obj the ASN.1 representation of a ContentInfo.
*
* @return the PKCS#7 message.
*/
p7.messageFromAsn1 = function(obj) {
// validate root level ContentInfo and capture data
var capture = {};
var errors = [];
if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors))
{
throw {
message: 'Cannot read PKCS#7 message. ' +
'ASN.1 object is not an PKCS#7 ContentInfo.',
errors: errors
};
}
var contentType = asn1.derToOid(capture.contentType);
var msg;
switch(contentType) {
case forge.pki.oids.envelopedData:
msg = p7.createEnvelopedData();
break;
case forge.pki.oids.encryptedData:
msg = p7.createEncryptedData();
break;
case forge.pki.oids.signedData:
msg = p7.createSignedData();
break;
default:
throw {
message: 'Cannot read PKCS#7 message. ContentType with OID ' +
contentType + ' is not (yet) supported.'
};
}
msg.fromAsn1(capture.content.value[0]);
return msg;
};
/**
* Converts a single RecipientInfo from an ASN.1 object.
*
* @param obj The ASN.1 representation of a RecipientInfo.
*
* @return The recipientInfo object.
*/
var _recipientInfoFromAsn1 = function(obj) {
// Validate EnvelopedData content block and capture data.
var capture = {};
var errors = [];
if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors))
{
throw {
message: 'Cannot read PKCS#7 message. ' +
'ASN.1 object is not an PKCS#7 EnvelopedData.',
errors: errors
};
}
return {
version: capture.version.charCodeAt(0),
issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
serialNumber: forge.util.createBuffer(capture.serial).toHex(),
encContent: {
algorithm: asn1.derToOid(capture.encAlgorithm),
parameter: capture.encParameter.value,
content: capture.encKey
}
};
};
/**
* Converts a single recipientInfo object to an ASN.1 object.
*
* @param obj The recipientInfo object.
*
* @return The ASN.1 representation of a RecipientInfo.
*/
var _recipientInfoToAsn1 = function(obj) {
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// Version
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
String.fromCharCode(obj.version)),
// IssuerAndSerialNumber
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// Name
forge.pki.distinguishedNameToAsn1({ attributes: obj.issuer }),
// Serial
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
forge.util.hexToBytes(obj.serialNumber))
]),
// KeyEncryptionAlgorithmIdentifier
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// Algorithm
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(obj.encContent.algorithm).getBytes()),
// Parameter, force NULL, only RSA supported for now.
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
]),
// EncryptedKey
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
obj.encContent.content)
]);
};
/**
* Map a set of RecipientInfo ASN.1 objects to recipientInfo objects.
*
* @param objArr Array of ASN.1 representations RecipientInfo (i.e. SET OF).
*
* @return array of recipientInfo objects.
*/
var _recipientInfosFromAsn1 = function(objArr) {
var ret = [];
for(var i = 0; i < objArr.length; i ++) {
ret.push(_recipientInfoFromAsn1(objArr[i]));
}
return ret;
};
/**
* Map an array of recipientInfo objects to ASN.1 objects.
*
* @param recipientsArr Array of recipientInfo objects.
*
* @return Array of ASN.1 representations RecipientInfo.
*/
var _recipientInfosToAsn1 = function(recipientsArr) {
var ret = [];
for(var i = 0; i < recipientsArr.length; i ++) {
ret.push(_recipientInfoToAsn1(recipientsArr[i]));
}
return ret;
};
/**
* Map messages encrypted content to ASN.1 objects.
*
* @param ec The encContent object of the message.
*
* @return ASN.1 representation of the encContent object (SEQUENCE).
*/
var _encContentToAsn1 = function(ec) {
return [
// ContentType, always Data for the moment
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(forge.pki.oids.data).getBytes()),
// ContentEncryptionAlgorithmIdentifier
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// Algorithm
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(ec.algorithm).getBytes()),
// Parameters (IV)
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
ec.parameter.getBytes())
]),
// [0] EncryptedContent
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
ec.content.getBytes())
])
];
};
/**
* Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
*
* This function reads the "common part" of the PKCS#7 content blocks
* EncryptedData and EnvelopedData, i.e. version number and symmetrically
* encrypted content block.
*
* The result of the ASN.1 validate and capture process is returned
* to allow the caller to extract further data, e.g. the list of recipients
* in case of a EnvelopedData object.
*
* @param msg The PKCS#7 object to read the data to
* @param obj The ASN.1 representation of the content block
* @param validator The ASN.1 structure validator object to use
* @return Map with values captured by validator object
*/
var _fromAsn1 = function(msg, obj, validator) {
var capture = {};
var errors = [];
if(!asn1.validate(obj, validator, capture, errors))
{
throw {
message: 'Cannot read PKCS#7 message. ' +
'ASN.1 object is not an PKCS#7 EnvelopedData.',
errors: errors
};
}
// Check contentType, so far we only support (raw) Data.
var contentType = asn1.derToOid(capture.contentType);
if(contentType !== forge.pki.oids.data) {
throw {
message: 'Unsupported PKCS#7 message. ' +
'Only contentType Data supported within EnvelopedData.'
};
}
if(capture.encContent) {
var content = '';
if(capture.encContent.constructor === Array) {
for(var i = 0; i < capture.encContent.length; ++i) {
if(capture.encContent[i].type !== asn1.Type.OCTETSTRING) {
throw {
message: 'Malformed PKCS#7 message, expecting encrypted ' +
'content constructed of only OCTET STRING objects.'
};
}
content += capture.encContent[i].value;
}
}
else {
content = capture.encContent;
}
msg.encContent = {
algorithm: asn1.derToOid(capture.encAlgorithm),
parameter: forge.util.createBuffer(capture.encParameter.value),
content: forge.util.createBuffer(content)
};
}
if(capture.content) {
var content = '';
if(capture.content.constructor === Array) {
for(var i = 0; i < capture.content.length; ++i) {
if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
throw {
message: 'Malformed PKCS#7 message, expecting ' +
'content constructed of only OCTET STRING objects.'
};
}
content += capture.content[i].value;
}
}
else {
content = capture.content;
}
msg.content = forge.util.createBuffer(content);
}
msg.version = capture.version.charCodeAt(0);
msg.rawCapture = capture;
return capture;
};
/**
* Decrypt the symmetrically encrypted content block of the PKCS#7 message.
*
* Decryption is skipped in case the PKCS#7 message object already has a
* (decrypted) content attribute. The algorithm, key and cipher parameters
* (probably the iv) are taken from the encContent attribute of the message
* object.
*
* @param The PKCS#7 message object.
*/
var _decryptContent = function (msg) {
if(msg.encContent.key === undefined) {
throw {
message: 'Symmetric key not available.'
};
}
if(msg.content === undefined) {
var ciph;
switch(msg.encContent.algorithm) {
case forge.pki.oids['aes128-CBC']:
case forge.pki.oids['aes192-CBC']:
case forge.pki.oids['aes256-CBC']:
ciph = forge.aes.createDecryptionCipher(msg.encContent.key);
break;
case forge.pki.oids['des-EDE3-CBC']:
ciph = forge.des.createDecryptionCipher(msg.encContent.key);
break;
default:
throw {
message: 'Unsupported symmetric cipher, '
+ 'OID ' + msg.encContent.algorithm
};
}
ciph.start(msg.encContent.parameter);
ciph.update(msg.encContent.content);
if(!ciph.finish()) {
throw {
message: 'Symmetric decryption failed.'
};
}
msg.content = ciph.output;
}
};
p7.createSignedData = function() {
var msg = null;
msg = {
type: forge.pki.oids.signedData,
version: 1,
fromAsn1: function(obj) {
// validate SignedData content block and capture data.
_fromAsn1(msg, obj, p7.asn1.signedDataValidator);
}
};
return msg;
};
/**
* Creates an empty PKCS#7 message of type EncryptedData.
*
* @return the message.
*/
p7.createEncryptedData = function() {
var msg = null;
msg = {
type: forge.pki.oids.encryptedData,
version: 0,
encContent: {
algorithm: forge.pki.oids['aes256-CBC']
},
/**
* Reads an EncryptedData content block (in ASN.1 format)
*
* @param obj The ASN.1 representation of the EncryptedData content block
*/
fromAsn1: function(obj) {
// Validate EncryptedData content block and capture data.
_fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
},
/**
* Decrypt encrypted content
*
* @param key The (symmetric) key as a byte buffer
*/
decrypt: function(key) {
if(key !== undefined) {
msg.encContent.key = key;
}
_decryptContent(msg);
}
};
return msg;
};
/**
* Creates an empty PKCS#7 message of type EnvelopedData.
*
* @return the message.
*/
p7.createEnvelopedData = function() {
var msg = null;
msg = {
type: forge.pki.oids.envelopedData,
version: 0,
recipients: [],
encContent: {
algorithm: forge.pki.oids['aes256-CBC']
},
/**
* Reads an EnvelopedData content block (in ASN.1 format)
*
* @param obj The ASN.1 representation of the EnvelopedData content block
*/
fromAsn1: function(obj) {
// Validate EnvelopedData content block and capture data.
var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
msg.recipients = _recipientInfosFromAsn1(capture.recipientInfos.value);
},
toAsn1: function() {
// ContentInfo
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// ContentType
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(msg.type).getBytes()),
// [0] EnvelopedData
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// Version
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
String.fromCharCode(msg.version)),
// RecipientInfos
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
_recipientInfosToAsn1(msg.recipients)),
// EncryptedContentInfo
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
_encContentToAsn1(msg.encContent))
])
])
]);
},
/**
* Find recipient by X.509 certificate's issuer.
*
* @param cert the certificate with the issuer to look for.
*
* @return the recipient object.
*/
findRecipient: function(cert) {
var sAttr = cert.issuer.attributes;
for(var i = 0; i < msg.recipients.length; ++i) {
var r = msg.recipients[i];
var rAttr = r.issuer;
if(r.serialNumber !== cert.serialNumber) {
continue;
}
if(rAttr.length !== sAttr.length) {
continue;
}
var match = true;
for(var j = 0; j < sAttr.length; ++j) {
if(rAttr[j].type !== sAttr[j].type ||
rAttr[j].value !== sAttr[j].value) {
match = false;
break;
}
}
if(match) {
return r;
}
}
},
/**
* Decrypt enveloped content
*
* @param recipient The recipient object related to the private key
* @param privKey The (RSA) private key object
*/
decrypt: function(recipient, privKey) {
if(msg.encContent.key === undefined && recipient !== undefined
&& privKey !== undefined) {
switch(recipient.encContent.algorithm) {
case forge.pki.oids.rsaEncryption:
var key = privKey.decrypt(recipient.encContent.content);
msg.encContent.key = forge.util.createBuffer(key);
break;
default:
throw {
message: 'Unsupported asymmetric cipher, '
+ 'OID ' + recipient.encContent.algorithm
};
}
}
_decryptContent(msg);
},
/**
* Add (another) entity to list of recipients.
*
* @param cert The certificate of the entity to add.
*/
addRecipient: function(cert) {
msg.recipients.push({
version: 0,
issuer: cert.subject.attributes,
serialNumber: cert.serialNumber,
encContent: {
// We simply assume rsaEncryption here, since forge.pki only
// supports RSA so far. If the PKI module supports other
// ciphers one day, we need to modify this one as well.
algorithm: forge.pki.oids.rsaEncryption,
key: cert.publicKey
}
});
},
/**
* Encrypt enveloped content.
*
* This function supports two optional arguments, cipher and key, which
* can be used to influence symmetric encryption. Unless cipher is
* provided, the cipher specified in encContent.algorithm is used
* (defaults to AES-256-CBC). If no key is provided, encContent.key
* is (re-)used. If that one's not set, a random key will be generated
* automatically.
*
* @param [key] The key to be used for symmetric encryption.
* @param [cipher] The OID of the symmetric cipher to use.
*/
encrypt: function(key, cipher) {
// Part 1: Symmetric encryption
if(msg.encContent.content === undefined) {
cipher = cipher || msg.encContent.algorithm;
key = key || msg.encContent.key;
var keyLen, ivLen, ciphFn;
switch(cipher) {
case forge.pki.oids['aes128-CBC']:
keyLen = 16;
ivLen = 16;
ciphFn = forge.aes.createEncryptionCipher;
break;
case forge.pki.oids['aes192-CBC']:
keyLen = 24;
ivLen = 16;
ciphFn = forge.aes.createEncryptionCipher;
break;
case forge.pki.oids['aes256-CBC']:
keyLen = 32;
ivLen = 16;
ciphFn = forge.aes.createEncryptionCipher;
break;
case forge.pki.oids['des-EDE3-CBC']:
keyLen = 24;
ivLen = 8;
ciphFn = forge.des.createEncryptionCipher;
break;
default:
throw {
message: 'Unsupported symmetric cipher, OID ' + cipher
};
}
if(key === undefined) {
key = forge.util.createBuffer(forge.random.getBytes(keyLen));
} else if(key.length() != keyLen) {
throw {
message: 'Symmetric key has wrong length, '
+ 'got ' + key.length() + ' bytes, expected ' + keyLen
};
}
// Keep a copy of the key & IV in the object, so the caller can
// use it for whatever reason.
msg.encContent.algorithm = cipher;
msg.encContent.key = key;
msg.encContent.parameter
= forge.util.createBuffer(forge.random.getBytes(ivLen));
var ciph = ciphFn(key);
ciph.start(msg.encContent.parameter.copy());
ciph.update(msg.content);
// The finish function does PKCS#7 padding by default, therefore
// no action required by us.
if(!ciph.finish()) {
throw {
message: 'Symmetric encryption failed.'
};
}
msg.encContent.content = ciph.output;
}
// Part 2: asymmetric encryption for each recipient
for(var i = 0; i < msg.recipients.length; i ++) {
var recipient = msg.recipients[i];
if(recipient.encContent.content !== undefined) {
continue; // Nothing to do, encryption already done.
}
switch(recipient.encContent.algorithm) {
case forge.pki.oids.rsaEncryption:
recipient.encContent.content =
recipient.encContent.key.encrypt(msg.encContent.key.data);
break;
default:
throw {
message: 'Unsupported asymmetric cipher, OID '
+ recipient.encContent.algorithm
};
}
}
}
};
return msg;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'pkcs7';
var deps = [
'./aes',
'./asn1',
'./des',
'./pkcs7asn1',
'./pki',
'./random',
'./util'
];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

389
src/lib/pkcs7asn1.js Normal file
View File

@ -0,0 +1,389 @@
/**
* Javascript implementation of PKCS#7 v1.5. Currently only certain parts of
* PKCS#7 are implemented, especially the enveloped-data content type.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*
* The ASN.1 representation of PKCS#7 is as follows
* (see RFC #2315 for details, http://www.ietf.org/rfc/rfc2315.txt):
*
* A PKCS#7 message consists of a ContentInfo on root level, which may
* contain any number of further ContentInfo nested into it.
*
* ContentInfo ::= SEQUENCE {
* contentType ContentType,
* content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
* }
*
* ContentType ::= OBJECT IDENTIFIER
*
* EnvelopedData ::= SEQUENCE {
* version Version,
* recipientInfos RecipientInfos,
* encryptedContentInfo EncryptedContentInfo
* }
*
* EncryptedData ::= SEQUENCE {
* version Version,
* encryptedContentInfo EncryptedContentInfo
* }
*
* Version ::= INTEGER
*
* RecipientInfos ::= SET OF RecipientInfo
*
* EncryptedContentInfo ::= SEQUENCE {
* contentType ContentType,
* contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
* encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
* }
*
* ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
*
* The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
* for the algorithm, if any. In the case of AES and DES3, there is only one,
* the IV.
*
* AlgorithmIdentifer ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
*
* EncryptedContent ::= OCTET STRING
*
* RecipientInfo ::= SEQUENCE {
* version Version,
* issuerAndSerialNumber IssuerAndSerialNumber,
* keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
* encryptedKey EncryptedKey
* }
*
* IssuerAndSerialNumber ::= SEQUENCE {
* issuer Name,
* serialNumber CertificateSerialNumber
* }
*
* CertificateSerialNumber ::= INTEGER
*
* KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
*
* EncryptedKey ::= OCTET STRING
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// shortcut for ASN.1 API
var asn1 = forge.asn1;
// shortcut for PKCS#7 API
var p7v = forge.pkcs7asn1 = forge.pkcs7asn1 || {};
forge.pkcs7 = forge.pkcs7 || {};
forge.pkcs7.asn1 = p7v;
var contentInfoValidator = {
name: 'ContentInfo',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'ContentInfo.ContentType',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.OID,
constructed: false,
capture: 'contentType'
}, {
name: 'ContentInfo.content',
tagClass: asn1.Class.CONTEXT_SPECIFIC,
type: 0,
constructed: true,
optional: true,
captureAsn1: 'content'
}]
};
p7v.contentInfoValidator = contentInfoValidator;
var encryptedContentInfoValidator = {
name: 'EncryptedContentInfo',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'EncryptedContentInfo.contentType',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.OID,
constructed: false,
capture: 'contentType'
}, {
name: 'EncryptedContentInfo.contentEncryptionAlgorithm',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'EncryptedContentInfo.contentEncryptionAlgorithm.algorithm',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.OID,
constructed: false,
capture: 'encAlgorithm'
}, {
name: 'EncryptedContentInfo.contentEncryptionAlgorithm.parameter',
tagClass: asn1.Class.UNIVERSAL,
captureAsn1: 'encParameter'
}]
}, {
name: 'EncryptedContentInfo.encryptedContent',
tagClass: asn1.Class.CONTEXT_SPECIFIC,
type: 0,
/* The PKCS#7 structure output by OpenSSL somewhat differs from what
* other implementations do generate.
*
* OpenSSL generates a structure like this:
* SEQUENCE {
* ...
* [0]
* 26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
* C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
* ...
* }
*
* Whereas other implementations (and this PKCS#7 module) generate:
* SEQUENCE {
* ...
* [0] {
* OCTET STRING
* 26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
* C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
* ...
* }
* }
*
* In order to support both, we just capture the context specific
* field here. The OCTET STRING bit is removed below.
*/
capture: 'encContent'
}]
};
p7v.envelopedDataValidator = {
name: 'EnvelopedData',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'EnvelopedData.Version',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.INTEGER,
constructed: false,
capture: 'version'
}, {
name: 'EnvelopedData.RecipientInfos',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SET,
constructed: true,
captureAsn1: 'recipientInfos'
}].concat(encryptedContentInfoValidator)
};
p7v.encryptedDataValidator = {
name: 'EncryptedData',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'EncryptedData.Version',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.INTEGER,
constructed: false,
capture: 'version'
}].concat(encryptedContentInfoValidator)
};
var signerValidator = {
name: 'SignerInfo',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'SignerInfo.Version',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.INTEGER,
constructed: false
}, {
name: 'SignerInfo.IssuerAndSerialNumber',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true
}, {
name: 'SignerInfo.DigestAlgorithm',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true
}, {
name: 'SignerInfo.AuthenticatedAttributes',
tagClass: asn1.Class.CONTEXT_SPECIFIC,
type: 0,
constructed: true,
optional: true,
capture: 'authenticatedAttributes'
}, {
name: 'SignerInfo.DigestEncryptionAlgorithm',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true
}, {
name: 'SignerInfo.EncryptedDigest',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.OCTETSTRING,
constructed: false,
capture: 'signature'
}, {
name: 'SignerInfo.UnauthenticatedAttributes',
tagClass: asn1.Class.CONTEXT_SPECIFIC,
type: 1,
constructed: true,
optional: true
}]
};
p7v.signedDataValidator = {
name: 'SignedData',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'SignedData.Version',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.INTEGER,
constructed: false,
capture: 'version'
}, {
name: 'SignedData.DigestAlgorithms',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SET,
constructed: true,
captureAsn1: 'digestAlgorithms'
},
contentInfoValidator,
{
name: 'SignedData.Certificates',
tagClass: asn1.Class.CONTEXT_SPECIFIC,
type: 0,
optional: true,
captureAsn1: 'certificates'
}, {
name: 'SignedData.CertificateRevocationLists',
tagClass: asn1.Class.CONTEXT_SPECIFIC,
type: 1,
optional: true,
captureAsn1: 'crls'
}, {
name: 'SignedData.SignerInfos',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SET,
capture: 'signerInfos',
value: [signerValidator]
}]
};
p7v.recipientInfoValidator = {
name: 'RecipientInfo',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'RecipientInfo.version',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.INTEGER,
constructed: false,
capture: 'version'
}, {
name: 'RecipientInfo.issuerAndSerial',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'RecipientInfo.issuerAndSerial.issuer',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
captureAsn1: 'issuer'
}, {
name: 'RecipientInfo.issuerAndSerial.serialNumber',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.INTEGER,
constructed: false,
capture: 'serial'
}]
}, {
name: 'RecipientInfo.keyEncryptionAlgorithm',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.SEQUENCE,
constructed: true,
value: [{
name: 'RecipientInfo.keyEncryptionAlgorithm.algorithm',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.OID,
constructed: false,
capture: 'encAlgorithm'
}, {
name: 'RecipientInfo.keyEncryptionAlgorithm.parameter',
tagClass: asn1.Class.UNIVERSAL,
constructed: false,
captureAsn1: 'encParameter'
}]
}, {
name: 'RecipientInfo.encryptedKey',
tagClass: asn1.Class.UNIVERSAL,
type: asn1.Type.OCTETSTRING,
constructed: false,
capture: 'encKey'
}]
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'pkcs7asn1';
var deps = ['./asn1', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

3229
src/lib/pki.js Normal file

File diff suppressed because it is too large Load Diff

436
src/lib/prng.js Normal file
View File

@ -0,0 +1,436 @@
/**
* A javascript implementation of a cryptographically-secure
* Pseudo Random Number Generator (PRNG). The Fortuna algorithm is mostly
* followed here. SHA-1 is used instead of SHA-256.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var _nodejs = (typeof module === 'object' && module.exports);
var crypto = null;
if(_nodejs) {
crypto = require('crypto');
}
/* PRNG API */
var prng = forge.prng = forge.prng || {};
/**
* Creates a new PRNG context.
*
* A PRNG plugin must be passed in that will provide:
*
* 1. A function that initializes the key and seed of a PRNG context. It
* will be given a 16 byte key and a 16 byte seed. Any key expansion
* or transformation of the seed from a byte string into an array of
* integers (or similar) should be performed.
* 2. The cryptographic function used by the generator. It takes a key and
* a seed.
* 3. A seed increment function. It takes the seed and return seed + 1.
* 4. An api to create a message digest.
*
* For an example, see random.js.
*
* @param plugin the PRNG plugin to use.
*/
prng.create = function(plugin) {
var ctx = {
plugin: plugin,
key: null,
seed: null,
time: null,
// number of reseeds so far
reseeds: 0,
// amount of data generated so far
generated: 0
};
// create 32 entropy pools (each is a message digest)
var md = plugin.md;
var pools = new Array(32);
for(var i = 0; i < 32; ++i) {
pools[i] = md.create();
}
ctx.pools = pools;
// entropy pools are written to cyclically, starting at index 0
ctx.pool = 0;
/**
* Generates random bytes. The bytes may be generated synchronously or
* asynchronously. Web workers must use the asynchronous interface or
* else the behavior is undefined.
*
* @param count the number of random bytes to generate.
* @param [callback(err, bytes)] called once the operation completes.
*
* @return count random bytes as a string.
*/
ctx.generate = function(count, callback) {
// do synchronously
if(!callback) {
return ctx.generateSync(count);
}
// simple generator using counter-based CBC
var cipher = ctx.plugin.cipher;
var increment = ctx.plugin.increment;
var formatKey = ctx.plugin.formatKey;
var formatSeed = ctx.plugin.formatSeed;
var b = forge.util.createBuffer();
generate();
function generate(err) {
if(err) {
return callback(err);
}
// sufficient bytes generated
if(b.length() >= count) {
return callback(null, b.getBytes(count));
}
// if amount of data generated is greater than 1 MiB, trigger reseed
if(ctx.generated >= 1048576) {
// only do reseed at most every 100 ms
var now = +new Date();
if(ctx.time === null || (now - ctx.time > 100)) {
ctx.key = null;
}
}
if(ctx.key === null) {
return _reseed(generate);
}
// generate the random bytes
var bytes = cipher(ctx.key, ctx.seed);
ctx.generated += bytes.length;
b.putBytes(bytes);
// generate bytes for a new key and seed
ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
forge.util.setImmediate(generate);
}
};
/**
* Generates random bytes synchronously.
*
* @param count the number of random bytes to generate.
*
* @return count random bytes as a string.
*/
ctx.generateSync = function(count) {
// simple generator using counter-based CBC
var cipher = ctx.plugin.cipher;
var increment = ctx.plugin.increment;
var formatKey = ctx.plugin.formatKey;
var formatSeed = ctx.plugin.formatSeed;
var b = forge.util.createBuffer();
while(b.length() < count) {
// if amount of data generated is greater than 1 MiB, trigger reseed
if(ctx.generated >= 1048576) {
// only do reseed at most every 100 ms
var now = +new Date();
if(ctx.time === null || (now - ctx.time > 100)) {
ctx.key = null;
}
}
if(ctx.key === null) {
_reseedSync();
}
// generate the random bytes
var bytes = cipher(ctx.key, ctx.seed);
ctx.generated += bytes.length;
b.putBytes(bytes);
// generate bytes for a new key and seed
ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
}
return b.getBytes(count);
};
/**
* Private function that asynchronously reseeds a generator.
*
* @param callback(err) called once the operation completes.
*/
function _reseed(callback) {
if(ctx.pools[0].messageLength >= 32) {
_seed();
return callback();
}
// not enough seed data...
var needed = (32 - ctx.pools[0].messageLength) << 5;
ctx.seedFile(needed, function(err, bytes) {
if(err) {
return callback(err);
}
ctx.collect(bytes);
_seed();
callback();
});
}
/**
* Private function that synchronously reseeds a generator.
*/
function _reseedSync() {
if(ctx.pools[0].messageLength >= 32) {
return _seed();
}
// not enough seed data...
var needed = (32 - ctx.pools[0].messageLength) << 5;
ctx.collect(ctx.seedFileSync(needed));
_seed();
}
/**
* Private function that seeds a generator once enough bytes are available.
*/
function _seed() {
// create a SHA-1 message digest
var md = forge.md.sha1.create();
// digest pool 0's entropy and restart it
md.update(ctx.pools[0].digest().getBytes());
ctx.pools[0].start();
// digest the entropy of other pools whose index k meet the
// condition '2^k mod n == 0' where n is the number of reseeds
var k = 1;
for(var i = 1; i < 32; ++i) {
// prevent signed numbers from being used
k = (k === 31) ? 0x80000000 : (k << 2);
if(k % ctx.reseeds === 0) {
md.update(ctx.pools[i].digest().getBytes());
ctx.pools[i].start();
}
}
// get digest for key bytes and iterate again for seed bytes
var keyBytes = md.digest().getBytes();
md.start();
md.update(keyBytes);
var seedBytes = md.digest().getBytes();
// update
ctx.key = ctx.plugin.formatKey(keyBytes);
ctx.seed = ctx.plugin.formatSeed(seedBytes);
++ctx.reseeds;
ctx.generated = 0;
ctx.time = +new Date();
}
/**
* The built-in default seedFile. This seedFile is used when entropy
* is needed immediately.
*
* @param needed the number of bytes that are needed.
*
* @return the random bytes.
*/
function defaultSeedFile(needed) {
// use window.crypto.getRandomValues strong source of entropy if
// available
var b = forge.util.createBuffer();
if(typeof window !== 'undefined' &&
window.crypto && window.crypto.getRandomValues) {
var entropy = new Uint32Array(needed / 4);
try {
window.crypto.getRandomValues(entropy);
for(var i = 0; i < entropy.length; ++i) {
b.putInt32(entropy[i]);
}
}
catch(e) {
/* Mozilla claims getRandomValues can throw QuotaExceededError, so
ignore errors. In this case, weak entropy will be added, but
hopefully this never happens.
https://developer.mozilla.org/en-US/docs/DOM/window.crypto.getRandomValues
However I've never observed this exception --@evanj */
}
}
// be sad and add some weak random data
if(b.length() < needed) {
/* Draws from Park-Miller "minimal standard" 31 bit PRNG,
implemented with David G. Carta's optimization: with 32 bit math
and without division (Public Domain). */
var hi, lo, next;
var seed = Math.floor(Math.random() * 0xFFFF);
while(b.length() < needed) {
lo = 16807 * (seed & 0xFFFF);
hi = 16807 * (seed >> 16);
lo += (hi & 0x7FFF) << 16;
lo += hi >> 15;
lo = (lo & 0x7FFFFFFF) + (lo >> 31);
seed = lo & 0xFFFFFFFF;
// consume lower 3 bytes of seed
for(var i = 0; i < 3; ++i) {
// throw in more pseudo random
next = seed >>> (i << 3);
next ^= Math.floor(Math.random() * 0xFF);
b.putByte(String.fromCharCode(next & 0xFF));
}
}
}
return b.getBytes();
}
// initialize seed file APIs
if(crypto) {
// use nodejs async API
ctx.seedFile = function(needed, callback) {
crypto.randomBytes(needed, function(err, bytes) {
if(err) {
return callback(err);
}
callback(null, bytes.toString());
});
};
// use nodejs sync API
ctx.seedFileSync = function(needed) {
return crypto.randomBytes(needed).toString();
};
}
else {
ctx.seedFile = function(needed, callback) {
try {
callback(null, defaultSeedFile(needed));
}
catch(e) {
callback(e);
}
};
ctx.seedFileSync = defaultSeedFile;
}
/**
* Adds entropy to a prng ctx's accumulator.
*
* @param bytes the bytes of entropy as a string.
*/
ctx.collect = function(bytes) {
// iterate over pools distributing entropy cyclically
var count = bytes.length;
for(var i = 0; i < count; ++i) {
ctx.pools[ctx.pool].update(bytes.substr(i, 1));
ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
}
};
/**
* Collects an integer of n bits.
*
* @param i the integer entropy.
* @param n the number of bits in the integer.
*/
ctx.collectInt = function(i, n) {
var bytes = '';
for(var x = 0; x < n; x += 8) {
bytes += String.fromCharCode((i >> x) & 0xFF);
}
ctx.collect(bytes);
};
/**
* Registers a Web Worker to receive immediate entropy from the main thread.
* This method is required until Web Workers can access the native crypto
* API. This method should be called twice for each created worker, once in
* the main thread, and once in the worker itself.
*
* @param worker the worker to register.
*/
ctx.registerWorker = function(worker) {
// worker receives random bytes
if(worker === self) {
ctx.seedFile = function(needed, callback) {
function listener(e) {
var data = e.data;
if(data.forge && data.forge.prng) {
self.removeEventListener('message', listener);
callback(data.forge.prng.err, data.forge.prng.bytes);
}
}
self.addEventListener('message', listener);
self.postMessage({forge: {prng: {needed: needed}}});
};
}
// main thread sends random bytes upon request
else {
function listener(e) {
var data = e.data;
if(data.forge && data.forge.prng) {
ctx.seedFile(data.forge.prng.needed, function(err, bytes) {
worker.postMessage({forge: {prng: {err: err, bytes: bytes}}});
});
}
}
// TODO: do we need to remove the event listener when the worker dies?
worker.addEventListener('message', listener);
}
};
return ctx;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'prng';
var deps = ['./md', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

246
src/lib/pss.js Normal file
View File

@ -0,0 +1,246 @@
/**
* Javascript implementation of PKCS#1 PSS signature padding.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// shortcut for PSS API
var pss = forge.pss = forge.pss || {};
/**
* Creates a PSS signature scheme object.
*
* @param hash hash function to use, a Forge md instance
* @param mgf mask generation function to use, a Forge mgf instance
* @param sLen length of the salt in octets
* @return a signature scheme object.
*/
pss.create = function(hash, mgf, sLen) {
var hLen = hash.digestLength;
var pssobj = {};
/**
* Verify PSS signature
*
* This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2
*
* @param {String} mHash The message digest hash to compare against
* the signature.
* @param {String} em The encoded message (RSA decryption result).
* @param modsBits Length of the RSA modulus in bits.
* @return true if the signature was verified, false if not.
*/
pssobj.verify = function(mHash, em, modBits) {
var i;
var emBits = modBits - 1;
var emLen = Math.ceil(emBits / 8);
/* c. Convert the message representative m to an encoded message EM
* of length emLen = ceil((modBits - 1) / 8) octets, where modBits
* is the length in bits of the RSA modulus n */
em = em.substr(-emLen);
/* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
if(emLen < hLen + sLen + 2) {
throw {
message: 'Inconsistent parameters to PSS signature verification.'
};
}
/* 4. If the rightmost octet of EM does not have hexadecimal value
* 0xbc, output "inconsistent" and stop. */
if(em.charCodeAt(emLen - 1) !== 0xbc) {
throw {
message: 'Encoded message does not end in 0xBC.'
};
}
/* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
* let H be the next hLen octets. */
var maskLen = emLen - hLen - 1;
var maskedDB = em.substr(0, maskLen);
var h = em.substr(maskLen, hLen);
/* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
* maskedDB are not all equal to zero, output "inconsistent" and stop. */
var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
if((maskedDB.charCodeAt(0) & mask) !== 0) {
throw {
message: 'Bits beyond keysize not zero as expected.'
};
}
/* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
var dbMask = mgf.generate(h, maskLen);
/* 8. Let DB = maskedDB \xor dbMask. */
var db = '';
for(i = 0; i < maskLen; i ++) {
db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
}
/* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
* in DB to zero. */
db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
/* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
* or if the octet at position emLen - hLen - sLen - 1 (the leftmost
* position is "position 1") does not have hexadecimal value 0x01,
* output "inconsistent" and stop. */
var checkLen = emLen - hLen - sLen - 2;
for(i = 0; i < checkLen; i ++) {
if(db.charCodeAt(i) !== 0x00) {
throw {
message: 'Leftmost octets not zero as expected'
};
}
}
if(db.charCodeAt(checkLen) !== 0x01) {
throw {
message: 'Inconsistent PSS signature, 0x01 marker not found'
};
}
/* 11. Let salt be the last sLen octets of DB. */
var salt = db.substr(-sLen);
/* 12. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
var m_ = new forge.util.ByteBuffer();
m_.fillWithByte(0, 8);
m_.putBytes(mHash);
m_.putBytes(salt);
/* 13. Let H' = Hash(M'), an octet string of length hLen. */
hash.start();
hash.update(m_.getBytes());
var h_ = hash.digest().getBytes();
/* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
return h === h_;
};
/**
* Encode PSS signature.
*
* This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1
*
* @param md the message digest object with the hash to sign.
* @param modsBits Length of the RSA modulus in bits.
* @return the encoded message, string of length ceil((modBits - 1) / 8)
*/
pssobj.encode = function(md, modBits) {
var i;
var emBits = modBits - 1;
var emLen = Math.ceil(emBits / 8);
/* 2. Let mHash = Hash(M), an octet string of length hLen. */
var mHash = md.digest().getBytes();
/* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
if(emLen < hLen + sLen + 2) {
throw {
message: 'Message is too long to encrypt'
};
}
/* 4. Generate a random octet string salt of length sLen; if sLen = 0,
* then salt is the empty string. */
var salt = forge.random.getBytes(sLen);
/* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
var m_ = new forge.util.ByteBuffer();
m_.fillWithByte(0, 8);
m_.putBytes(mHash);
m_.putBytes(salt);
/* 6. Let H = Hash(M'), an octet string of length hLen. */
hash.start();
hash.update(m_.getBytes());
var h = hash.digest().getBytes();
/* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
* zero octets. The length of PS may be 0. */
var ps = new forge.util.ByteBuffer();
ps.fillWithByte(0, emLen - sLen - hLen - 2);
/* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
* emLen - hLen - 1. */
ps.putByte(0x01);
ps.putBytes(salt);
var db = ps.getBytes();
/* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
var maskLen = emLen - hLen - 1;
var dbMask = mgf.generate(h, maskLen);
/* 10. Let maskedDB = DB \xor dbMask. */
var maskedDB = '';
for(i = 0; i < maskLen; i ++) {
maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
}
/* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
* maskedDB to zero. */
var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
maskedDB.substr(1);
/* 12. Let EM = maskedDB || H || 0xbc.
* 13. Output EM. */
return maskedDB + h + String.fromCharCode(0xbc);
};
return pssobj;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'pss';
var deps = ['./random', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

211
src/lib/random.js Normal file
View File

@ -0,0 +1,211 @@
/**
* An API for getting cryptographically-secure random bytes. The bytes are
* generated using the Fortuna algorithm devised by Bruce Schneier and
* Niels Ferguson.
*
* Getting strong random bytes is not yet easy to do in javascript. The only
* truish random entropy that can be collected is from the mouse, keyboard, or
* from timing with respect to page loads, etc. This generator makes a poor
* attempt at providing random bytes when those sources haven't yet provided
* enough entropy to initially seed or to reseed the PRNG.
*
* @author Dave Longley
*
* Copyright (c) 2009-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// forge.random already defined
if(forge.random && forge.random.getBytes) {
return;
}
(function(jQuery) {
// the default prng plugin, uses AES-128
var prng_aes = {};
var _prng_aes_output = new Array(4);
var _prng_aes_buffer = forge.util.createBuffer();
prng_aes.formatKey = function(key) {
// convert the key into 32-bit integers
var tmp = forge.util.createBuffer(key);
key = new Array(4);
key[0] = tmp.getInt32();
key[1] = tmp.getInt32();
key[2] = tmp.getInt32();
key[3] = tmp.getInt32();
// return the expanded key
return forge.aes._expandKey(key, false);
};
prng_aes.formatSeed = function(seed) {
// convert seed into 32-bit integers
var tmp = forge.util.createBuffer(seed);
seed = new Array(4);
seed[0] = tmp.getInt32();
seed[1] = tmp.getInt32();
seed[2] = tmp.getInt32();
seed[3] = tmp.getInt32();
return seed;
};
prng_aes.cipher = function(key, seed) {
forge.aes._updateBlock(key, seed, _prng_aes_output, false);
_prng_aes_buffer.putInt32(_prng_aes_output[0]);
_prng_aes_buffer.putInt32(_prng_aes_output[1]);
_prng_aes_buffer.putInt32(_prng_aes_output[2]);
_prng_aes_buffer.putInt32(_prng_aes_output[3]);
return _prng_aes_buffer.getBytes();
};
prng_aes.increment = function(seed) {
// FIXME: do we care about carry or signed issues?
++seed[3];
return seed;
};
prng_aes.md = forge.md.sha1;
// create default prng context
var _ctx = forge.prng.create(prng_aes);
// add other sources of entropy only if window.crypto.getRandomValues is not
// available -- otherwise this source will be automatically used by the prng
var _nodejs = (typeof module === 'object' && module.exports);
if(!_nodejs && !(typeof window !== 'undefined' &&
window.crypto && window.crypto.getRandomValues)) {
// if this is a web worker, do not use weak entropy, instead register to
// receive strong entropy asynchronously from the main thread
if(typeof window === 'undefined' || window.document === undefined) {
// FIXME:
}
// get load time entropy
_ctx.collectInt(+new Date(), 32);
// add some entropy from navigator object
if(typeof(navigator) !== 'undefined') {
var _navBytes = '';
for(var key in navigator) {
try {
if(typeof(navigator[key]) == 'string') {
_navBytes += navigator[key];
}
}
catch(e) {
/* Some navigator keys might not be accessible, e.g. the geolocation
attribute throws an exception if touched in Mozilla chrome://
context.
Silently ignore this and just don't use this as a source of
entropy. */
}
}
_ctx.collect(_navBytes);
_navBytes = null;
}
// add mouse and keyboard collectors if jquery is available
if(jQuery) {
// set up mouse entropy capture
jQuery().mousemove(function(e) {
// add mouse coords
_ctx.collectInt(e.clientX, 16);
_ctx.collectInt(e.clientY, 16);
});
// set up keyboard entropy capture
jQuery().keypress(function(e) {
_ctx.collectInt(e.charCode, 8);
});
}
}
/* Random API */
if(!forge.random) {
forge.random = _ctx;
}
else {
// extend forge.random with _ctx
for(var key in _ctx) {
forge.random[key] = _ctx[key];
}
}
/**
* Gets random bytes. If a native secure crypto API is unavailable, this
* method tries to make the bytes more unpredictable by drawing from data that
* can be collected from the user of the browser, eg: mouse movement.
*
* If a callback is given, this method will be called asynchronously.
*
* @param count the number of random bytes to get.
* @param [callback(err, bytes)] called once the operation completes.
*
* @return the random bytes in a string.
*/
forge.random.getBytes = function(count, callback) {
return forge.random.generate(count, callback);
};
/**
* Gets random bytes asynchronously. If a native secure crypto API is
* unavailable, this method tries to make the bytes more unpredictable by
* drawing from data that can be collected from the user of the browser,
* eg: mouse movement.
*
* @param count the number of random bytes to get.
*
* @return the random bytes in a string.
*/
forge.random.getBytesSync = function(count) {
return forge.random.generate(count);
};
})(typeof(jQuery) !== 'undefined' ? jQuery : null);
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'random';
var deps = ['./aes', './md', './prng', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

463
src/lib/rc2.js Normal file
View File

@ -0,0 +1,463 @@
/**
* RC2 implementation.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*
* Information on the RC2 cipher is available from RFC #2268,
* http://www.ietf.org/rfc/rfc2268.txt
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var piTable = [
0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
];
var s = [1, 2, 3, 5];
/**
* Rotate a word left by given number of bits.
*
* Bits that are shifted out on the left are put back in on the right
* hand side.
*
* @param word The word to shift left.
* @param bits The number of bits to shift by.
* @return The rotated word.
*/
var rol = function(word, bits) {
return ((word << bits) & 0xffff) | ((word & 0xffff) >> (16 - bits));
};
/**
* Rotate a word right by given number of bits.
*
* Bits that are shifted out on the right are put back in on the left
* hand side.
*
* @param word The word to shift right.
* @param bits The number of bits to shift by.
* @return The rotated word.
*/
var ror = function(word, bits) {
return ((word & 0xffff) >> bits) | ((word << (16 - bits)) & 0xffff);
};
/* RC2 API */
forge.rc2 = forge.rc2 || {};
/**
* Perform RC2 key expansion as per RFC #2268, section 2.
*
* @param key variable-length user key (between 1 and 128 bytes)
* @param effKeyBits number of effective key bits (default: 128)
* @return the expanded RC2 key (ByteBuffer of 128 bytes)
*/
forge.rc2.expandKey = function(key, effKeyBits) {
if(key.constructor == String) {
key = forge.util.createBuffer(key);
}
effKeyBits = effKeyBits || 128;
/* introduce variables that match the names used in RFC #2268 */
var L = key;
var T = key.length();
var T1 = effKeyBits;
var T8 = Math.ceil(T1 / 8);
var TM = 0xff >> (T1 & 0x07);
var i;
for(i = T; i < 128; i ++) {
L.putByte(piTable[(L.at(i - 1) + L.at(i - T)) & 0xff]);
}
L.setAt(128 - T8, piTable[L.at(128 - T8) & TM]);
for(i = 127 - T8; i >= 0; i --) {
L.setAt(i, piTable[L.at(i + 1) ^ L.at(i + T8)]);
}
return L;
};
/**
* Creates a RC2 cipher object.
*
* @param key the symmetric key to use (as base for key generation).
* @param bits the number of effective key bits.
* @param encrypt false for decryption, true for encryption.
*
* @return the cipher.
*/
var createCipher = function(key, bits, encrypt)
{
var _finish = false, _input = null, _output = null, _iv = null;
var mixRound, mashRound;
var i, j, K = [];
/* Expand key and fill into K[] Array */
key = forge.rc2.expandKey(key, bits);
for(i = 0; i < 64; i ++) {
K.push(key.getInt16Le());
}
if(encrypt) {
/**
* Perform one mixing round "in place".
*
* @param R Array of four words to perform mixing on.
*/
mixRound = function(R) {
for(i = 0; i < 4; i++) {
R[i] += K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
R[i] = rol(R[i], s[i]);
j ++;
}
};
/**
* Perform one mashing round "in place".
*
* @param R Array of four words to perform mashing on.
*/
mashRound = function(R) {
for(i = 0; i < 4; i ++) {
R[i] += K[R[(i + 3) % 4] & 63];
}
};
} else {
/**
* Perform one r-mixing round "in place".
*
* @param R Array of four words to perform mixing on.
*/
mixRound = function(R) {
for(i = 3; i >= 0; i--) {
R[i] = ror(R[i], s[i]);
R[i] -= K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
j --;
}
};
/**
* Perform one r-mashing round "in place".
*
* @param R Array of four words to perform mashing on.
*/
mashRound = function(R) {
for(i = 3; i >= 0; i--) {
R[i] -= K[R[(i + 3) % 4] & 63];
}
};
}
/**
* Run the specified cipher execution plan.
*
* This function takes four words from the input buffer, applies the IV on
* it (if requested) and runs the provided execution plan.
*
* The plan must be put together in form of a array of arrays. Where the
* outer one is simply a list of steps to perform and the inner one needs
* to have two elements: the first one telling how many rounds to perform,
* the second one telling what to do (i.e. the function to call).
*
* @param {Array} plan The plan to execute.
*/
var runPlan = function(plan) {
var R = [];
/* Get data from input buffer and fill the four words into R */
for(i = 0; i < 4; i ++) {
var val = _input.getInt16Le();
if(_iv !== null) {
if(encrypt) {
/* We're encrypting, apply the IV first. */
val ^= _iv.getInt16Le();
} else {
/* We're decryption, keep cipher text for next block. */
_iv.putInt16Le(val);
}
}
R.push(val & 0xffff);
}
/* Reset global "j" variable as per spec. */
j = encrypt ? 0 : 63;
/* Run execution plan. */
for(var ptr = 0; ptr < plan.length; ptr ++) {
for(var ctr = 0; ctr < plan[ptr][0]; ctr ++) {
plan[ptr][1](R);
}
}
/* Write back result to output buffer. */
for(i = 0; i < 4; i ++) {
if(_iv !== null) {
if(encrypt) {
/* We're encrypting in CBC-mode, feed back encrypted bytes into
IV buffer to carry it forward to next block. */
_iv.putInt16Le(R[i]);
} else {
R[i] ^= _iv.getInt16Le();
}
}
_output.putInt16Le(R[i]);
}
};
/* Create cipher object */
var cipher = null;
cipher = {
/**
* Starts or restarts the encryption or decryption process, whichever
* was previously configured.
*
* To use the cipher in CBC mode, iv may be given either as a string
* of bytes, or as a byte buffer. For ECB mode, give null as iv.
*
* @param iv the initialization vector to use, null for ECB mode.
* @param output the output the buffer to write to, null to create one.
*/
start: function(iv, output) {
if(iv) {
/* CBC mode */
if(key.constructor == String && iv.length == 8) {
iv = forge.util.createBuffer(iv);
}
}
_finish = false;
_input = forge.util.createBuffer();
_output = output || new forge.util.createBuffer();
_iv = iv;
cipher.output = _output;
},
/**
* Updates the next block.
*
* @param input the buffer to read from.
*/
update: function(input) {
if(!_finish) {
// not finishing, so fill the input buffer with more input
_input.putBuffer(input);
}
while(_input.length() >= 8) {
runPlan([
[ 5, mixRound ],
[ 1, mashRound ],
[ 6, mixRound ],
[ 1, mashRound ],
[ 5, mixRound ]
]);
}
},
/**
* Finishes encrypting or decrypting.
*
* @param pad a padding function to use, null for PKCS#7 padding,
* signature(blockSize, buffer, decrypt).
*
* @return true if successful, false on error.
*/
finish: function(pad) {
var rval = true;
if(encrypt) {
if(pad) {
rval = pad(8, _input, !encrypt);
} else {
// add PKCS#7 padding to block (each pad byte is the
// value of the number of pad bytes)
var padding = (_input.length() == 8) ? 8 : (8 - _input.length());
_input.fillWithByte(padding, padding);
}
}
if(rval) {
// do final update
_finish = true;
cipher.update();
}
if(!encrypt) {
// check for error: input data not a multiple of block size
rval = (_input.length() === 0);
if(rval) {
if(pad) {
rval = pad(8, _output, !encrypt);
} else {
// ensure padding byte count is valid
var len = _output.length();
var count = _output.at(len - 1);
if(count > len) {
rval = false;
} else {
// trim off padding bytes
_output.truncate(count);
}
}
}
}
return rval;
}
};
return cipher;
};
/**
* Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
* given symmetric key. The output will be stored in the 'output' member
* of the returned cipher.
*
* The key and iv may be given as a string of bytes or a byte buffer.
* The cipher is initialized to use 128 effective key bits.
*
* @param key the symmetric key to use.
* @param iv the initialization vector to use.
* @param output the buffer to write to, null to create one.
*
* @return the cipher.
*/
forge.rc2.startEncrypting = function(key, iv, output) {
var cipher = forge.rc2.createEncryptionCipher(key, 128);
cipher.start(iv, output);
return cipher;
};
/**
* Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
* given symmetric key.
*
* The key may be given as a string of bytes or a byte buffer.
*
* To start encrypting call start() on the cipher with an iv and optional
* output buffer.
*
* @param key the symmetric key to use.
*
* @return the cipher.
*/
forge.rc2.createEncryptionCipher = function(key, bits) {
return createCipher(key, bits, true);
};
/**
* Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
* given symmetric key. The output will be stored in the 'output' member
* of the returned cipher.
*
* The key and iv may be given as a string of bytes or a byte buffer.
* The cipher is initialized to use 128 effective key bits.
*
* @param key the symmetric key to use.
* @param iv the initialization vector to use.
* @param output the buffer to write to, null to create one.
*
* @return the cipher.
*/
forge.rc2.startDecrypting = function(key, iv, output) {
var cipher = forge.rc2.createDecryptionCipher(key, 128);
cipher.start(iv, output);
return cipher;
};
/**
* Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
* given symmetric key.
*
* The key may be given as a string of bytes or a byte buffer.
*
* To start decrypting call start() on the cipher with an iv and optional
* output buffer.
*
* @param key the symmetric key to use.
*
* @return the cipher.
*/
forge.rc2.createDecryptionCipher = function(key, bits) {
return createCipher(key, bits, false);
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'rc2';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

36
src/lib/require.js Normal file
View File

@ -0,0 +1,36 @@
/*
RequireJS 2.1.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(ba){function J(b){return"[object Function]"===N.call(b)}function K(b){return"[object Array]"===N.call(b)}function z(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function O(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function t(b,c){return ha.call(b,c)}function m(b,c){return t(b,c)&&b[c]}function H(b,c){for(var d in b)if(t(b,d)&&c(b[d],d))break}function S(b,c,d,m){c&&H(c,function(c,l){if(d||!t(b,l))m&&"string"!==typeof c?(b[l]||(b[l]={}),S(b[l],
c,d,m)):b[l]=c});return b}function v(b,c){return function(){return c.apply(b,arguments)}}function ca(b){throw b;}function da(b){if(!b)return b;var c=ba;z(b.split("."),function(b){c=c[b]});return c}function B(b,c,d,m){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=m;d&&(c.originalError=d);return c}function ia(b){function c(a,f,C){var e,n,b,c,d,T,k,g=f&&f.split("/");e=g;var l=j.map,h=l&&l["*"];if(a&&"."===a.charAt(0))if(f){e=m(j.pkgs,f)?g=[f]:g.slice(0,g.length-
1);f=a=e.concat(a.split("/"));for(e=0;f[e];e+=1)if(n=f[e],"."===n)f.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=m(j.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(C&&l&&(g||h)){f=a.split("/");for(e=f.length;0<e;e-=1){b=f.slice(0,e).join("/");if(g)for(n=g.length;0<n;n-=1)if(C=m(l,g.slice(0,n).join("/")))if(C=m(C,b)){c=C;d=e;break}if(c)break;!T&&(h&&m(h,b))&&(T=m(h,b),k=e)}!c&&
T&&(c=T,d=k);c&&(f.splice(0,d,c),a=f.join("/"))}return a}function d(a){A&&z(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===k.contextName)return f.parentNode.removeChild(f),!0})}function p(a){var f=m(j.paths,a);if(f&&K(f)&&1<f.length)return d(a),f.shift(),k.require.undef(a),k.require([a]),!0}function g(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function l(a,
f,b,e){var n,D,i=null,d=f?f.name:null,l=a,h=!0,j="";a||(h=!1,a="_@r"+(N+=1));a=g(a);i=a[0];a=a[1];i&&(i=c(i,d,e),D=m(r,i));a&&(i?j=D&&D.normalize?D.normalize(a,function(a){return c(a,d,e)}):c(a,d,e):(j=c(a,d,e),a=g(j),i=a[0],j=a[1],b=!0,n=k.nameToUrl(j)));b=i&&!D&&!b?"_unnormalized"+(O+=1):"";return{prefix:i,name:j,parentMap:f,unnormalized:!!b,url:n,originalName:l,isDefine:h,id:(i?i+"!"+j:j)+b}}function s(a){var f=a.id,b=m(q,f);b||(b=q[f]=new k.Module(a));return b}function u(a,f,b){var e=a.id,n=m(q,
e);if(t(r,e)&&(!n||n.defineEmitComplete))"defined"===f&&b(r[e]);else if(n=s(a),n.error&&"error"===f)b(n.error);else n.on(f,b)}function w(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(z(b,function(f){if(f=m(q,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)h.onError(a)}function x(){U.length&&(ja.apply(I,[I.length-1,0].concat(U)),U=[])}function y(a){delete q[a];delete W[a]}function G(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,z(a.depMaps,function(e,c){var d=e.id,
g=m(q,d);g&&(!a.depMatched[c]&&!b[d])&&(m(f,d)?(a.defineDep(c,r[d]),a.check()):G(g,f,b))}),b[e]=!0)}function E(){var a,f,b,e,n=(b=1E3*j.waitSeconds)&&k.startTime+b<(new Date).getTime(),c=[],i=[],g=!1,l=!0;if(!X){X=!0;H(W,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||i.push(b),!b.error))if(!b.inited&&n)p(f)?g=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(g=!0,!a.prefix))return l=!1});if(n&&c.length)return b=B("timeout","Load timeout for modules: "+c,null,c),b.contextName=
k.contextName,w(b);l&&z(i,function(a){G(a,{},{})});if((!n||e)&&g)if((A||ea)&&!Y)Y=setTimeout(function(){Y=0;E()},50);X=!1}}function F(a){t(r,a[0])||s(l(a[0],null,!0)).init(a[1],a[2])}function L(a){var a=a.currentTarget||a.srcElement,b=k.onScriptLoad;a.detachEvent&&!Z?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=k.onScriptError;(!a.detachEvent||Z)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function M(){var a;for(x();I.length;){a=
I.shift();if(null===a[0])return w(B("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));F(a)}}var X,$,k,P,Y,j={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},config:{}},q={},W={},aa={},I=[],r={},V={},N=1,O=1;P={require:function(a){return a.require?a.require:a.require=k.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=r[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){var b=
m(j.pkgs,a.map.id);return(b?m(j.config,a.map.id+"/"+b.main):m(j.config,a.map.id))||{}},exports:r[a.map.id]}}};$=function(a){this.events=m(aa,a.id)||{};this.map=a;this.shim=m(j.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};$.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=v(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=
!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;k.startTime=(new Date).getTime();var a=this.map;if(this.shim)k.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],v(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=
this.map.url;V[a]||(V[a]=!0,k.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,n=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(J(n)){if(this.events.error&&this.map.isDefine||h.onError!==ca)try{e=k.execCb(c,n,b,e)}catch(d){a=d}else e=k.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==
this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(r[c]=e,h.onResourceLoad))h.onResourceLoad(k,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=l(a.prefix);this.depMaps.push(d);u(d,"defined",v(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,C=k.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=l(a.prefix+"!"+d,this.map.parentMap),u(e,"defined",v(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),
d=m(q,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",v(this,function(a){this.emit("error",a)}));d.enable()}}else n=v(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=v(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];H(q,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),n.fromText=v(this,function(e,c){var d=a.name,g=l(d),i=Q;c&&(e=c);i&&(Q=!1);s(g);t(j.config,b)&&(j.config[d]=j.config[b]);try{h.exec(e)}catch(D){return w(B("fromtexteval",
"fromText eval for "+b+" failed: "+D,D,[b]))}i&&(Q=!0);this.depMaps.push(g);k.completeLoad(d);C([d],n)}),e.load(a.name,C,n,j)}));k.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){W[this.map.id]=this;this.enabling=this.enabled=!0;z(this.depMaps,v(this,function(a,b){var c,e;if("string"===typeof a){a=l(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(P,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;u(a,"defined",v(this,function(a){this.defineDep(b,
a);this.check()}));this.errback&&u(a,"error",v(this,this.errback))}c=a.id;e=q[c];!t(P,c)&&(e&&!e.enabled)&&k.enable(a,this)}));H(this.pluginMaps,v(this,function(a){var b=m(q,a.id);b&&!b.enabled&&k.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){z(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};k={config:j,contextName:b,registry:q,defined:r,urlFetched:V,defQueue:I,Module:$,makeModuleMap:l,
nextTick:h.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.pkgs,c=j.shim,e={paths:!0,config:!0,map:!0};H(a,function(a,b){e[b]?"map"===b?(j.map||(j.map={}),S(j[b],a,!0,!0)):S(j[b],a,!0):j[b]=a});a.shim&&(H(a.shim,function(a,b){K(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=k.makeShimExports(a);c[b]=a}),j.shim=c);a.packages&&(z(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,
location:a.location||a.name,main:(a.main||"main").replace(ka,"").replace(fa,"")}}),j.pkgs=b);H(q,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=l(b))});if(a.deps||a.callback)k.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,f){function d(e,c,g){var i,j;f.enableBuildCallback&&(c&&J(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(J(c))return w(B("requireargs",
"Invalid require call"),g);if(a&&t(P,e))return P[e](q[a.id]);if(h.get)return h.get(k,e,a,d);i=l(e,a,!1,!0);i=i.id;return!t(r,i)?w(B("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[i]}M();k.nextTick(function(){M();j=s(l(null,a));j.skipMap=f.skipMap;j.init(e,c,g,{enabled:!0});E()});return d}f=f||{};S(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1<f))d=b.substring(f,b.length),b=
b.substring(0,f);return k.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return t(r,l(b,a,!1,!0).id)},specified:function(b){b=l(b,a,!1,!0).id;return t(r,b)||t(q,b)}});a||(d.undef=function(b){x();var c=l(b,a,!0),d=m(q,b);delete r[b];delete V[c.url];delete aa[b];d&&(d.events.defined&&(aa[b]=d.events),y(b))});return d},enable:function(a){m(q,a.id)&&s(a).enable()},completeLoad:function(a){var b,c,e=m(j.shim,a)||{},d=e.exports;for(x();I.length;){c=I.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===
a&&(b=!0);F(c)}c=m(q,a);if(!b&&!t(r,a)&&c&&!c.inited){if(j.enforceDefine&&(!d||!da(d)))return p(a)?void 0:w(B("nodefine","No define call for "+a,null,[a]));F([a,e.deps||[],e.exportsFn])}E()},nameToUrl:function(a,b,c){var d,g,l,i,k,p;if(h.jsExtRegExp.test(a))i=a+(b||"");else{d=j.paths;g=j.pkgs;i=a.split("/");for(k=i.length;0<k;k-=1)if(p=i.slice(0,k).join("/"),l=m(g,p),p=m(d,p)){K(p)&&(p=p[0]);i.splice(0,k,p);break}else if(l){a=a===l.name?l.location+"/"+l.main:l.location;i.splice(0,k,a);break}i=i.join("/");
i+=b||(/\?/.test(i)||c?"":".js");i=("/"===i.charAt(0)||i.match(/^[\w\+\.\-]+:/)?"":j.baseUrl)+i}return j.urlArgs?i+((-1===i.indexOf("?")?"?":"&")+j.urlArgs):i},load:function(a,b){h.load(k,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||la.test((a.currentTarget||a.srcElement).readyState))R=null,a=L(a),k.completeLoad(a.id)},onScriptError:function(a){var b=L(a);if(!p(b.id))return w(B("scripterror","Script error for: "+b.id,a,[b.id]))}};k.require=k.makeRequire();
return k}var h,x,y,E,L,F,R,M,s,ga,ma=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,na=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,fa=/\.js$/,ka=/^\.\//;x=Object.prototype;var N=x.toString,ha=x.hasOwnProperty,ja=Array.prototype.splice,A=!!("undefined"!==typeof window&&navigator&&window.document),ea=!A&&"undefined"!==typeof importScripts,la=A&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,Z="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),G={},u={},U=[],Q=
!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(J(requirejs))return;u=requirejs;requirejs=void 0}"undefined"!==typeof require&&!J(require)&&(u=require,require=void 0);h=requirejs=function(b,c,d,p){var g,l="_";!K(b)&&"string"!==typeof b&&(g=b,K(c)?(b=c,c=d,d=p):b=[]);g&&g.context&&(l=g.context);(p=m(G,l))||(p=G[l]=h.s.newContext(l));g&&p.configure(g);return p.require(b,c,d)};h.config=function(b){return h(b)};h.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,
4)}:function(b){b()};require||(require=h);h.version="2.1.6";h.jsExtRegExp=/^\/|:|\?|\.js$/;h.isBrowser=A;x=h.s={contexts:G,newContext:ia};h({});z(["toUrl","undef","defined","specified"],function(b){h[b]=function(){var c=G._;return c.require[b].apply(c,arguments)}});if(A&&(y=x.head=document.getElementsByTagName("head")[0],E=document.getElementsByTagName("base")[0]))y=x.head=E.parentNode;h.onError=ca;h.load=function(b,c,d){var h=b&&b.config||{},g;if(A)return g=h.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml",
"html:script"):document.createElement("script"),g.type=h.scriptType||"text/javascript",g.charset="utf-8",g.async=!0,g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&0>g.attachEvent.toString().indexOf("[native code"))&&!Z?(Q=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,M=g,E?y.insertBefore(g,E):y.appendChild(g),
M=null,g;if(ea)try{importScripts(d),b.completeLoad(c)}catch(l){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,l,[c]))}};A&&O(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(L=b.getAttribute("data-main"))return s=L,u.baseUrl||(F=s.split("/"),s=F.pop(),ga=F.length?F.join("/")+"/":"./",u.baseUrl=ga),s=s.replace(fa,""),h.jsExtRegExp.test(s)&&(s=L),u.deps=u.deps?u.deps.concat(s):[s],!0});define=function(b,c,d){var h,g;"string"!==typeof b&&(d=c,c=b,b=null);
K(c)||(d=c,c=null);!c&&J(d)&&(c=[],d.length&&(d.toString().replace(ma,"").replace(na,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(Q){if(!(h=M))R&&"interactive"===R.readyState||O(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return R=b}),h=R;h&&(b||(b=h.getAttribute("data-requiremodule")),g=G[h.getAttribute("data-requirecontext")])}(g?g.defQueue:U).push([b,c,d])};define.amd={jQuery:!0};h.exec=function(b){return eval(b)};
h(u)}})(this);

1107
src/lib/rsa.js Normal file

File diff suppressed because it is too large Load Diff

323
src/lib/sha1.js Normal file
View File

@ -0,0 +1,323 @@
/**
* Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
*
* This implementation is currently limited to message lengths (in bytes) that
* are up to 32-bits in size.
*
* @author Dave Longley
*
* Copyright (c) 2010-2012 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var sha1 = forge.sha1 = forge.sha1 || {};
forge.md = forge.md || {};
forge.md.algorithms = forge.md.algorithms || {};
forge.md.sha1 = forge.md.algorithms['sha1'] = sha1;
// sha-1 padding bytes not initialized yet
var _padding = null;
var _initialized = false;
/**
* Initializes the constant tables.
*/
var _init = function() {
// create padding
_padding = String.fromCharCode(128);
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
// now initialized
_initialized = true;
};
/**
* Updates a SHA-1 state with the given byte buffer.
*
* @param s the SHA-1 state to update.
* @param w the array to use to store words.
* @param bytes the byte buffer to update with.
*/
var _update = function(s, w, bytes) {
// consume 512 bit (64 byte) chunks
var t, a, b, c, d, e, f, i;
var len = bytes.length();
while(len >= 64) {
// the w array will be populated with sixteen 32-bit big-endian words
// and then extended into 80 32-bit words according to SHA-1 algorithm
// and for 32-79 using Max Locktyukhin's optimization
// initialize hash value for this chunk
a = s.h0;
b = s.h1;
c = s.h2;
d = s.h3;
e = s.h4;
// round 1
for(i = 0; i < 16; ++i) {
t = bytes.getInt32();
w[i] = t;
f = d ^ (b & (c ^ d));
t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
e = d;
d = c;
c = (b << 30) | (b >>> 2);
b = a;
a = t;
}
for(; i < 20; ++i) {
t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
t = (t << 1) | (t >>> 31);
w[i] = t;
f = d ^ (b & (c ^ d));
t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
e = d;
d = c;
c = (b << 30) | (b >>> 2);
b = a;
a = t;
}
// round 2
for(; i < 32; ++i) {
t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
t = (t << 1) | (t >>> 31);
w[i] = t;
f = b ^ c ^ d;
t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
e = d;
d = c;
c = (b << 30) | (b >>> 2);
b = a;
a = t;
}
for(; i < 40; ++i) {
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
t = (t << 2) | (t >>> 30);
w[i] = t;
f = b ^ c ^ d;
t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
e = d;
d = c;
c = (b << 30) | (b >>> 2);
b = a;
a = t;
}
// round 3
for(; i < 60; ++i) {
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
t = (t << 2) | (t >>> 30);
w[i] = t;
f = (b & c) | (d & (b ^ c));
t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
e = d;
d = c;
c = (b << 30) | (b >>> 2);
b = a;
a = t;
}
// round 4
for(; i < 80; ++i) {
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
t = (t << 2) | (t >>> 30);
w[i] = t;
f = b ^ c ^ d;
t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
e = d;
d = c;
c = (b << 30) | (b >>> 2);
b = a;
a = t;
}
// update hash state
s.h0 += a;
s.h1 += b;
s.h2 += c;
s.h3 += d;
s.h4 += e;
len -= 64;
}
};
/**
* Creates a SHA-1 message digest object.
*
* @return a message digest object.
*/
sha1.create = function() {
// do initialization as necessary
if(!_initialized) {
_init();
}
// SHA-1 state contains five 32-bit integers
var _state = null;
// input buffer
var _input = forge.util.createBuffer();
// used for word storage
var _w = new Array(80);
// message digest object
var md = {
algorithm: 'sha1',
blockLength: 64,
digestLength: 20,
// length of message so far (does not including padding)
messageLength: 0
};
/**
* Starts the digest.
*/
md.start = function() {
md.messageLength = 0;
_input = forge.util.createBuffer();
_state = {
h0: 0x67452301,
h1: 0xEFCDAB89,
h2: 0x98BADCFE,
h3: 0x10325476,
h4: 0xC3D2E1F0
};
};
// start digest automatically for first time
md.start();
/**
* Updates the digest with the given message input. The given input can
* treated as raw input (no encoding will be applied) or an encoding of
* 'utf8' maybe given to encode the input using UTF-8.
*
* @param msg the message input to update with.
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
*/
md.update = function(msg, encoding) {
if(encoding === 'utf8') {
msg = forge.util.encodeUtf8(msg);
}
// update message length
md.messageLength += msg.length;
// add bytes to input buffer
_input.putBytes(msg);
// process bytes
_update(_state, _w, _input);
// compact input buffer every 2K or if empty
if(_input.read > 2048 || _input.length() === 0) {
_input.compact();
}
};
/**
* Produces the digest.
*
* @return a byte buffer containing the digest value.
*/
md.digest = function() {
/* Note: Here we copy the remaining bytes in the input buffer and
add the appropriate SHA-1 padding. Then we do the final update
on a copy of the state so that if the user wants to get
intermediate digests they can do so. */
/* Determine the number of bytes that must be added to the message
to ensure its length is congruent to 448 mod 512. In other words,
a 64-bit integer that gives the length of the message will be
appended to the message and whatever the length of the message is
plus 64 bits must be a multiple of 512. So the length of the
message must be congruent to 448 mod 512 because 512 - 64 = 448.
In order to fill up the message length it must be filled with
padding that begins with 1 bit followed by all 0 bits. Padding
must *always* be present, so if the message length is already
congruent to 448 mod 512, then 512 padding bits must be added. */
// 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
// _padding starts with 1 byte with first bit is set in it which
// is byte value 128, then there may be up to 63 other pad bytes
var len = md.messageLength;
var padBytes = forge.util.createBuffer();
padBytes.putBytes(_input.bytes());
padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64)));
/* Now append length of the message. The length is appended in bits
as a 64-bit number in big-endian order. Since we store the length
in bytes, we must multiply it by 8 (or left shift by 3). So here
store the high 3 bits in the low end of the first 32-bits of the
64-bit number and the lower 5 bits in the high end of the second
32-bits. */
padBytes.putInt32((len >>> 29) & 0xFF);
padBytes.putInt32((len << 3) & 0xFFFFFFFF);
var s2 = {
h0: _state.h0,
h1: _state.h1,
h2: _state.h2,
h3: _state.h3,
h4: _state.h4
};
_update(s2, _w, padBytes);
var rval = forge.util.createBuffer();
rval.putInt32(s2.h0);
rval.putInt32(s2.h1);
rval.putInt32(s2.h2);
rval.putInt32(s2.h3);
rval.putInt32(s2.h4);
return rval;
};
return md;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'sha1';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

333
src/lib/sha256.js Normal file
View File

@ -0,0 +1,333 @@
/**
* Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
*
* See FIPS 180-2 for details.
*
* This implementation is currently limited to message lengths (in bytes) that
* are up to 32-bits in size.
*
* @author Dave Longley
*
* Copyright (c) 2010-2012 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var sha256 = forge.sha256 = forge.sha256 || {};
forge.md = forge.md || {};
forge.md.algorithms = forge.md.algorithms || {};
forge.md.sha256 = forge.md.algorithms['sha256'] = sha256;
// sha-256 padding bytes not initialized yet
var _padding = null;
var _initialized = false;
// table of constants
var _k = null;
/**
* Initializes the constant tables.
*/
var _init = function() {
// create padding
_padding = String.fromCharCode(128);
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
// create K table for SHA-256
_k = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
// now initialized
_initialized = true;
};
/**
* Updates a SHA-256 state with the given byte buffer.
*
* @param s the SHA-256 state to update.
* @param w the array to use to store words.
* @param bytes the byte buffer to update with.
*/
var _update = function(s, w, bytes) {
// consume 512 bit (64 byte) chunks
var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
var len = bytes.length();
while(len >= 64) {
// the w array will be populated with sixteen 32-bit big-endian words
// and then extended into 64 32-bit words according to SHA-256
for(i = 0; i < 16; ++i) {
w[i] = bytes.getInt32();
}
for(; i < 64; ++i) {
// XOR word 2 words ago rot right 17, rot right 19, shft right 10
t1 = w[i - 2];
t1 =
((t1 >>> 17) | (t1 << 15)) ^
((t1 >>> 19) | (t1 << 13)) ^
(t1 >>> 10);
// XOR word 15 words ago rot right 7, rot right 18, shft right 3
t2 = w[i - 15];
t2 =
((t2 >>> 7) | (t2 << 25)) ^
((t2 >>> 18) | (t2 << 14)) ^
(t2 >>> 3);
// sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) & 0xFFFFFFFF;
}
// initialize hash value for this chunk
a = s.h0;
b = s.h1;
c = s.h2;
d = s.h3;
e = s.h4;
f = s.h5;
g = s.h6;
h = s.h7;
// round function
for(i = 0; i < 64; ++i) {
// Sum1(e)
s1 =
((e >>> 6) | (e << 26)) ^
((e >>> 11) | (e << 21)) ^
((e >>> 25) | (e << 7));
// Ch(e, f, g) (optimized the same way as SHA-1)
ch = g ^ (e & (f ^ g));
// Sum0(a)
s0 =
((a >>> 2) | (a << 30)) ^
((a >>> 13) | (a << 19)) ^
((a >>> 22) | (a << 10));
// Maj(a, b, c) (optimized the same way as SHA-1)
maj = (a & b) | (c & (a ^ b));
// main algorithm
t1 = h + s1 + ch + _k[i] + w[i];
t2 = s0 + maj;
h = g;
g = f;
f = e;
e = (d + t1) & 0xFFFFFFFF;
d = c;
c = b;
b = a;
a = (t1 + t2) & 0xFFFFFFFF;
}
// update hash state
s.h0 = (s.h0 + a) & 0xFFFFFFFF;
s.h1 = (s.h1 + b) & 0xFFFFFFFF;
s.h2 = (s.h2 + c) & 0xFFFFFFFF;
s.h3 = (s.h3 + d) & 0xFFFFFFFF;
s.h4 = (s.h4 + e) & 0xFFFFFFFF;
s.h5 = (s.h5 + f) & 0xFFFFFFFF;
s.h6 = (s.h6 + g) & 0xFFFFFFFF;
s.h7 = (s.h7 + h) & 0xFFFFFFFF;
len -= 64;
}
};
/**
* Creates a SHA-256 message digest object.
*
* @return a message digest object.
*/
sha256.create = function() {
// do initialization as necessary
if(!_initialized) {
_init();
}
// SHA-256 state contains eight 32-bit integers
var _state = null;
// input buffer
var _input = forge.util.createBuffer();
// used for word storage
var _w = new Array(64);
// message digest object
var md = {
algorithm: 'sha256',
blockLength: 64,
digestLength: 32,
// length of message so far (does not including padding)
messageLength: 0
};
/**
* Starts the digest.
*/
md.start = function() {
md.messageLength = 0;
_input = forge.util.createBuffer();
_state = {
h0: 0x6A09E667,
h1: 0xBB67AE85,
h2: 0x3C6EF372,
h3: 0xA54FF53A,
h4: 0x510E527F,
h5: 0x9B05688C,
h6: 0x1F83D9AB,
h7: 0x5BE0CD19
};
};
// start digest automatically for first time
md.start();
/**
* Updates the digest with the given message input. The given input can
* treated as raw input (no encoding will be applied) or an encoding of
* 'utf8' maybe given to encode the input using UTF-8.
*
* @param msg the message input to update with.
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
*/
md.update = function(msg, encoding) {
if(encoding === 'utf8') {
msg = forge.util.encodeUtf8(msg);
}
// update message length
md.messageLength += msg.length;
// add bytes to input buffer
_input.putBytes(msg);
// process bytes
_update(_state, _w, _input);
// compact input buffer every 2K or if empty
if(_input.read > 2048 || _input.length() === 0) {
_input.compact();
}
};
/**
* Produces the digest.
*
* @return a byte buffer containing the digest value.
*/
md.digest = function() {
/* Note: Here we copy the remaining bytes in the input buffer and
add the appropriate SHA-256 padding. Then we do the final update
on a copy of the state so that if the user wants to get
intermediate digests they can do so. */
/* Determine the number of bytes that must be added to the message
to ensure its length is congruent to 448 mod 512. In other words,
a 64-bit integer that gives the length of the message will be
appended to the message and whatever the length of the message is
plus 64 bits must be a multiple of 512. So the length of the
message must be congruent to 448 mod 512 because 512 - 64 = 448.
In order to fill up the message length it must be filled with
padding that begins with 1 bit followed by all 0 bits. Padding
must *always* be present, so if the message length is already
congruent to 448 mod 512, then 512 padding bits must be added. */
// 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
// _padding starts with 1 byte with first bit is set in it which
// is byte value 128, then there may be up to 63 other pad bytes
var len = md.messageLength;
var padBytes = forge.util.createBuffer();
padBytes.putBytes(_input.bytes());
padBytes.putBytes(_padding.substr(0, 64 - ((len + 8) % 64)));
/* Now append length of the message. The length is appended in bits
as a 64-bit number in big-endian order. Since we store the length
in bytes, we must multiply it by 8 (or left shift by 3). So here
store the high 3 bits in the low end of the first 32-bits of the
64-bit number and the lower 5 bits in the high end of the second
32-bits. */
padBytes.putInt32((len >>> 29) & 0xFF);
padBytes.putInt32((len << 3) & 0xFFFFFFFF);
var s2 = {
h0: _state.h0,
h1: _state.h1,
h2: _state.h2,
h3: _state.h3,
h4: _state.h4,
h5: _state.h5,
h6: _state.h6,
h7: _state.h7
};
_update(s2, _w, padBytes);
var rval = forge.util.createBuffer();
rval.putInt32(s2.h0);
rval.putInt32(s2.h1);
rval.putInt32(s2.h2);
rval.putInt32(s2.h3);
rval.putInt32(s2.h4);
rval.putInt32(s2.h5);
rval.putInt32(s2.h6);
rval.putInt32(s2.h7);
return rval;
};
return md;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'sha256';
var deps = ['./util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

335
src/lib/socket.js Normal file
View File

@ -0,0 +1,335 @@
/**
* Socket implementation that uses flash SocketPool class as a backend.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// define net namespace
var net = forge.net = forge.net || {};
// map of flash ID to socket pool
net.socketPools = {};
/**
* Creates a flash socket pool.
*
* @param options:
* flashId: the dom ID for the flash object element.
* policyPort: the default policy port for sockets, 0 to use the
* flash default.
* policyUrl: the default policy file URL for sockets (if provided
* used instead of a policy port).
* msie: true if the browser is msie, false if not.
*
* @return the created socket pool.
*/
net.createSocketPool = function(options) {
// set default
options.msie = options.msie || false;
// initialize the flash interface
var spId = options.flashId;
var api = document.getElementById(spId);
api.init({marshallExceptions: !options.msie});
// create socket pool entry
var sp = {
// ID of the socket pool
id: spId,
// flash interface
flashApi: api,
// map of socket ID to sockets
sockets: {},
// default policy port
policyPort: options.policyPort || 0,
// default policy URL
policyUrl: options.policyUrl || null
};
net.socketPools[spId] = sp;
// create event handler, subscribe to flash events
if(options.msie === true) {
sp.handler = function(e) {
if(e.id in sp.sockets) {
// get handler function
var f;
switch(e.type) {
case 'connect':
f = 'connected';
break;
case 'close':
f = 'closed';
break;
case 'socketData':
f = 'data';
break;
default:
f = 'error';
break;
}
/* IE calls javascript on the thread of the external object
that triggered the event (in this case flash) ... which will
either run concurrently with other javascript or pre-empt any
running javascript in the middle of its execution (BAD!) ...
calling setTimeout() will schedule the javascript to run on
the javascript thread and solve this EVIL problem. */
setTimeout(function(){sp.sockets[e.id][f](e);}, 0);
}
};
}
else {
sp.handler = function(e) {
if(e.id in sp.sockets) {
// get handler function
var f;
switch(e.type) {
case 'connect':
f = 'connected';
break;
case 'close':
f = 'closed';
break;
case 'socketData':
f = 'data';
break;
default:
f = 'error';
break;
}
sp.sockets[e.id][f](e);
}
};
}
var handler = 'forge.net.socketPools[\'' + spId + '\'].handler';
api.subscribe('connect', handler);
api.subscribe('close', handler);
api.subscribe('socketData', handler);
api.subscribe('ioError', handler);
api.subscribe('securityError', handler);
/**
* Destroys a socket pool. The socket pool still needs to be cleaned
* up via net.cleanup().
*/
sp.destroy = function() {
delete net.socketPools[options.flashId];
for(var id in sp.sockets) {
sp.sockets[id].destroy();
}
sp.sockets = {};
api.cleanup();
};
/**
* Creates a new socket.
*
* @param options:
* connected: function(event) called when the socket connects.
* closed: function(event) called when the socket closes.
* data: function(event) called when socket data has arrived,
* it can be read from the socket using receive().
* error: function(event) called when a socket error occurs.
*/
sp.createSocket = function(options) {
// default to empty options
options = options || {};
// create flash socket
var id = api.create();
// create javascript socket wrapper
var socket = {
id: id,
// set handlers
connected: options.connected || function(e){},
closed: options.closed || function(e){},
data: options.data || function(e){},
error: options.error || function(e){}
};
/**
* Destroys this socket.
*/
socket.destroy = function() {
api.destroy(id);
delete sp.sockets[id];
};
/**
* Connects this socket.
*
* @param options:
* host: the host to connect to.
* port: the port to connect to.
* policyPort: the policy port to use (if non-default), 0 to
* use the flash default.
* policyUrl: the policy file URL to use (instead of port).
*/
socket.connect = function(options) {
// give precedence to policy URL over policy port
// if no policy URL and passed port isn't 0, use default port,
// otherwise use 0 for the port
var policyUrl = options.policyUrl || null;
var policyPort = 0;
if(policyUrl === null && options.policyPort !== 0) {
policyPort = options.policyPort || sp.policyPort;
}
api.connect(id, options.host, options.port, policyPort, policyUrl);
};
/**
* Closes this socket.
*/
socket.close = function() {
api.close(id);
socket.closed({
id: socket.id,
type: 'close',
bytesAvailable: 0
});
};
/**
* Determines if the socket is connected or not.
*
* @return true if connected, false if not.
*/
socket.isConnected = function() {
return api.isConnected(id);
};
/**
* Writes bytes to this socket.
*
* @param bytes the bytes (as a string) to write.
*
* @return true on success, false on failure.
*/
socket.send = function(bytes) {
return api.send(id, forge.util.encode64(bytes));
};
/**
* Reads bytes from this socket (non-blocking). Fewer than the number
* of bytes requested may be read if enough bytes are not available.
*
* This method should be called from the data handler if there are
* enough bytes available. To see how many bytes are available, check
* the 'bytesAvailable' property on the event in the data handler or
* call the bytesAvailable() function on the socket. If the browser is
* msie, then the bytesAvailable() function should be used to avoid
* race conditions. Otherwise, using the property on the data handler's
* event may be quicker.
*
* @param count the maximum number of bytes to read.
*
* @return the bytes read (as a string) or null on error.
*/
socket.receive = function(count) {
var rval = api.receive(id, count).rval;
return (rval === null) ? null : forge.util.decode64(rval);
};
/**
* Gets the number of bytes available for receiving on the socket.
*
* @return the number of bytes available for receiving.
*/
socket.bytesAvailable = function() {
return api.getBytesAvailable(id);
};
// store and return socket
sp.sockets[id] = socket;
return socket;
};
return sp;
};
/**
* Destroys a flash socket pool.
*
* @param options:
* flashId: the dom ID for the flash object element.
*/
net.destroySocketPool = function(options) {
if(options.flashId in net.socketPools) {
var sp = net.socketPools[options.flashId];
sp.destroy();
}
};
/**
* Creates a new socket.
*
* @param options:
* flashId: the dom ID for the flash object element.
* connected: function(event) called when the socket connects.
* closed: function(event) called when the socket closes.
* data: function(event) called when socket data has arrived, it
* can be read from the socket using receive().
* error: function(event) called when a socket error occurs.
*
* @return the created socket.
*/
net.createSocket = function(options) {
var socket = null;
if(options.flashId in net.socketPools) {
// get related socket pool
var sp = net.socketPools[options.flashId];
socket = sp.createSocket(options);
}
return socket;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'net';
var deps = [];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

781
src/lib/task.js Normal file
View File

@ -0,0 +1,781 @@
/**
* Support for concurrent task management and synchronization in web
* applications.
*
* @author Dave Longley
* @author David I. Lehn <dlehn@digitalbazaar.com>
*
* Copyright (c) 2009-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// logging category
var cat = 'forge.task';
// verbose level
// 0: off, 1: a little, 2: a whole lot
// Verbose debug logging is surrounded by a level check to avoid the
// performance issues with even calling the logging code regardless if it
// is actually logged. For performance reasons this should not be set to 2
// for production use.
// ex: if(sVL >= 2) forge.log.verbose(....)
var sVL = 0;
// track tasks for debugging
var sTasks = {};
var sNextTaskId = 0;
// debug access
forge.debug.set(cat, 'tasks', sTasks);
// a map of task type to task queue
var sTaskQueues = {};
// debug access
forge.debug.set(cat, 'queues', sTaskQueues);
// name for unnamed tasks
var sNoTaskName = '?';
// maximum number of doNext() recursions before a context swap occurs
// FIXME: might need to tweak this based on the browser
var sMaxRecursions = 30;
// time slice for doing tasks before a context swap occurs
// FIXME: might need to tweak this based on the browser
var sTimeSlice = 20;
/**
* Task states.
*
* READY: ready to start processing
* RUNNING: task or a subtask is running
* BLOCKED: task is waiting to acquire N permits to continue
* SLEEPING: task is sleeping for a period of time
* DONE: task is done
* ERROR: task has an error
*/
var READY = 'ready';
var RUNNING = 'running';
var BLOCKED = 'blocked';
var SLEEPING = 'sleeping';
var DONE = 'done';
var ERROR = 'error';
/**
* Task actions. Used to control state transitions.
*
* STOP: stop processing
* START: start processing tasks
* BLOCK: block task from continuing until 1 or more permits are released
* UNBLOCK: release one or more permits
* SLEEP: sleep for a period of time
* WAKEUP: wakeup early from SLEEPING state
* CANCEL: cancel further tasks
* FAIL: a failure occured
*/
var STOP = 'stop';
var START = 'start';
var BLOCK = 'block';
var UNBLOCK = 'unblock';
var SLEEP = 'sleep';
var WAKEUP = 'wakeup';
var CANCEL = 'cancel';
var FAIL = 'fail';
/**
* State transition table.
*
* nextState = sStateTable[currentState][action]
*/
var sStateTable = {};
sStateTable[READY] = {};
sStateTable[READY][STOP] = READY;
sStateTable[READY][START] = RUNNING;
sStateTable[READY][CANCEL] = DONE;
sStateTable[READY][FAIL] = ERROR;
sStateTable[RUNNING] = {};
sStateTable[RUNNING][STOP] = READY;
sStateTable[RUNNING][START] = RUNNING;
sStateTable[RUNNING][BLOCK] = BLOCKED;
sStateTable[RUNNING][UNBLOCK] = RUNNING;
sStateTable[RUNNING][SLEEP] = SLEEPING;
sStateTable[RUNNING][WAKEUP] = RUNNING;
sStateTable[RUNNING][CANCEL] = DONE;
sStateTable[RUNNING][FAIL] = ERROR;
sStateTable[BLOCKED] = {};
sStateTable[BLOCKED][STOP] = BLOCKED;
sStateTable[BLOCKED][START] = BLOCKED;
sStateTable[BLOCKED][BLOCK] = BLOCKED;
sStateTable[BLOCKED][UNBLOCK] = BLOCKED;
sStateTable[BLOCKED][SLEEP] = BLOCKED;
sStateTable[BLOCKED][WAKEUP] = BLOCKED;
sStateTable[BLOCKED][CANCEL] = DONE;
sStateTable[BLOCKED][FAIL] = ERROR;
sStateTable[SLEEPING] = {};
sStateTable[SLEEPING][STOP] = SLEEPING;
sStateTable[SLEEPING][START] = SLEEPING;
sStateTable[SLEEPING][BLOCK] = SLEEPING;
sStateTable[SLEEPING][UNBLOCK] = SLEEPING;
sStateTable[SLEEPING][SLEEP] = SLEEPING;
sStateTable[SLEEPING][WAKEUP] = SLEEPING;
sStateTable[SLEEPING][CANCEL] = DONE;
sStateTable[SLEEPING][FAIL] = ERROR;
sStateTable[DONE] = {};
sStateTable[DONE][STOP] = DONE;
sStateTable[DONE][START] = DONE;
sStateTable[DONE][BLOCK] = DONE;
sStateTable[DONE][UNBLOCK] = DONE;
sStateTable[DONE][SLEEP] = DONE;
sStateTable[DONE][WAKEUP] = DONE;
sStateTable[DONE][CANCEL] = DONE;
sStateTable[DONE][FAIL] = ERROR;
sStateTable[ERROR] = {};
sStateTable[ERROR][STOP] = ERROR;
sStateTable[ERROR][START] = ERROR;
sStateTable[ERROR][BLOCK] = ERROR;
sStateTable[ERROR][UNBLOCK] = ERROR;
sStateTable[ERROR][SLEEP] = ERROR;
sStateTable[ERROR][WAKEUP] = ERROR;
sStateTable[ERROR][CANCEL] = ERROR;
sStateTable[ERROR][FAIL] = ERROR;
/**
* Creates a new task.
*
* @param options options for this task
* run: the run function for the task (required)
* name: the run function for the task (optional)
* parent: parent of this task (optional)
*
* @return the empty task.
*/
var Task = function(options) {
// task id
this.id = -1;
// task name
this.name = options.name || sNoTaskName;
// task has no parent
this.parent = options.parent || null;
// save run function
this.run = options.run;
// create a queue of subtasks to run
this.subtasks = [];
// error flag
this.error = false;
// state of the task
this.state = READY;
// number of times the task has been blocked (also the number
// of permits needed to be released to continue running)
this.blocks = 0;
// timeout id when sleeping
this.timeoutId = null;
// no swap time yet
this.swapTime = null;
// no user data
this.userData = null;
// initialize task
// FIXME: deal with overflow
this.id = sNextTaskId++;
sTasks[this.id] = this;
if(sVL >= 1) {
forge.log.verbose(cat, '[%s][%s] init', this.id, this.name, this);
}
};
/**
* Logs debug information on this task and the system state.
*/
Task.prototype.debug = function(msg) {
msg = msg || '';
forge.log.debug(cat, msg,
'[%s][%s] task:', this.id, this.name, this,
'subtasks:', this.subtasks.length,
'queue:', sTaskQueues);
};
/**
* Adds a subtask to run after task.doNext() or task.fail() is called.
*
* @param name human readable name for this task (optional).
* @param subrun a function to run that takes the current task as
* its first parameter.
*
* @return the current task (useful for chaining next() calls).
*/
Task.prototype.next = function(name, subrun) {
// juggle parameters if it looks like no name is given
if(typeof(name) === 'function') {
subrun = name;
// inherit parent's name
name = this.name;
}
// create subtask, set parent to this task, propagate callbacks
var subtask = new Task({
run: subrun,
name: name,
parent: this
});
// start subtasks running
subtask.state = RUNNING;
subtask.type = this.type;
subtask.successCallback = this.successCallback || null;
subtask.failureCallback = this.failureCallback || null;
// queue a new subtask
this.subtasks.push(subtask);
return this;
};
/**
* Adds subtasks to run in parallel after task.doNext() or task.fail()
* is called.
*
* @param name human readable name for this task (optional).
* @param subrun functions to run that take the current task as
* their first parameter.
*
* @return the current task (useful for chaining next() calls).
*/
Task.prototype.parallel = function(name, subrun) {
// juggle parameters if it looks like no name is given
if(name.constructor == Array) {
subrun = name;
// inherit parent's name
name = this.name;
}
// Wrap parallel tasks in a regular task so they are started at the
// proper time.
return this.next(name, function(task) {
// block waiting for subtasks
var ptask = task;
ptask.block(subrun.length);
// we pass the iterator from the loop below as a parameter
// to a function because it is otherwise included in the
// closure and changes as the loop changes -- causing i
// to always be set to its highest value
var startParallelTask = function(pname, pi) {
forge.task.start({
type: pname,
run: function(task) {
subrun[pi](task);
},
success: function(task) {
ptask.unblock();
},
failure: function(task) {
ptask.unblock();
}
});
};
for(var i = 0; i < subrun.length; i++) {
// Type must be unique so task starts in parallel:
// name + private string + task id + sub-task index
// start tasks in parallel and unblock when the finish
var pname = name + '__parallel-' + task.id + '-' + i;
var pi = i;
startParallelTask(pname, pi);
}
});
};
/**
* Stops a running task.
*/
Task.prototype.stop = function() {
this.state = sStateTable[this.state][STOP];
};
/**
* Starts running a task.
*/
Task.prototype.start = function() {
this.error = false;
this.state = sStateTable[this.state][START];
// try to restart
if(this.state === RUNNING) {
this.start = new Date();
this.run(this);
runNext(this, 0);
}
};
/**
* Blocks a task until it one or more permits have been released. The
* task will not resume until the requested number of permits have
* been released with call(s) to unblock().
*
* @param n number of permits to wait for(default: 1).
*/
Task.prototype.block = function(n) {
n = typeof(n) === 'undefined' ? 1 : n;
this.blocks += n;
if(this.blocks > 0) {
this.state = sStateTable[this.state][BLOCK];
}
};
/**
* Releases a permit to unblock a task. If a task was blocked by
* requesting N permits via block(), then it will only continue
* running once enough permits have been released via unblock() calls.
*
* If multiple processes need to synchronize with a single task then
* use a condition variable (see forge.task.createCondition). It is
* an error to unblock a task more times than it has been blocked.
*
* @param n number of permits to release (default: 1).
*
* @return the current block count (task is unblocked when count is 0)
*/
Task.prototype.unblock = function(n) {
n = typeof(n) === 'undefined' ? 1 : n;
this.blocks -= n;
if(this.blocks === 0 && this.state !== DONE) {
this.state = RUNNING;
runNext(this, 0);
}
return this.blocks;
};
/**
* Sleep for a period of time before resuming tasks.
*
* @param n number of milliseconds to sleep (default: 0).
*/
Task.prototype.sleep = function(n) {
n = typeof(n) === 'undefined' ? 0 : n;
this.state = sStateTable[this.state][SLEEP];
var task = this;
this.timeoutId = setTimeout(function() {
task.timeoutId = null;
task.state = RUNNING;
runNext(task, 0);
}, n);
};
/**
* Waits on a condition variable until notified. The next task will
* not be scheduled until notification. A condition variable can be
* created with forge.task.createCondition().
*
* Once cond.notify() is called, the task will continue.
*
* @param cond the condition variable to wait on.
*/
Task.prototype.wait = function(cond) {
cond.wait(this);
};
/**
* If sleeping, wakeup and continue running tasks.
*/
Task.prototype.wakeup = function() {
if(this.state === SLEEPING) {
cancelTimeout(this.timeoutId);
this.timeoutId = null;
this.state = RUNNING;
runNext(this, 0);
}
};
/**
* Cancel all remaining subtasks of this task.
*/
Task.prototype.cancel = function() {
this.state = sStateTable[this.state][CANCEL];
// remove permits needed
this.permitsNeeded = 0;
// cancel timeouts
if(this.timeoutId !== null) {
cancelTimeout(this.timeoutId);
this.timeoutId = null;
}
// remove subtasks
this.subtasks = [];
};
/**
* Finishes this task with failure and sets error flag. The entire
* task will be aborted unless the next task that should execute
* is passed as a parameter. This allows levels of subtasks to be
* skipped. For instance, to abort only this tasks's subtasks, then
* call fail(task.parent). To abort this task's subtasks and its
* parent's subtasks, call fail(task.parent.parent). To abort
* all tasks and simply call the task callback, call fail() or
* fail(null).
*
* The task callback (success or failure) will always, eventually, be
* called.
*
* @param next the task to continue at, or null to abort entirely.
*/
Task.prototype.fail = function(next) {
// set error flag
this.error = true;
// finish task
finish(this, true);
if(next) {
// propagate task info
next.error = this.error;
next.swapTime = this.swapTime;
next.userData = this.userData;
// do next task as specified
runNext(next, 0);
}
else {
if(this.parent !== null) {
// finish root task (ensures it is removed from task queue)
var parent = this.parent;
while(parent.parent !== null) {
// propagate task info
parent.error = this.error;
parent.swapTime = this.swapTime;
parent.userData = this.userData;
parent = parent.parent;
}
finish(parent, true);
}
// call failure callback if one exists
if(this.failureCallback) {
this.failureCallback(this);
}
}
};
/**
* Asynchronously start a task.
*
* @param task the task to start.
*/
var start = function(task) {
task.error = false;
task.state = sStateTable[task.state][START];
setTimeout(function() {
if(task.state === RUNNING) {
task.swapTime = +new Date();
task.run(task);
runNext(task, 0);
}
}, 0);
};
/**
* Run the next subtask or finish this task.
*
* @param task the task to process.
* @param recurse the recursion count.
*/
var runNext = function(task, recurse) {
// get time since last context swap (ms), if enough time has passed set
// swap to true to indicate that doNext was performed asynchronously
// also, if recurse is too high do asynchronously
var swap =
(recurse > sMaxRecursions) ||
(+new Date() - task.swapTime) > sTimeSlice;
var doNext = function(recurse) {
recurse++;
if(task.state === RUNNING) {
if(swap) {
// update swap time
task.swapTime = +new Date();
}
if(task.subtasks.length > 0) {
// run next subtask
var subtask = task.subtasks.shift();
subtask.error = task.error;
subtask.swapTime = task.swapTime;
subtask.userData = task.userData;
subtask.run(subtask);
if(!subtask.error)
{
runNext(subtask, recurse);
}
}
else {
finish(task);
if(!task.error) {
// chain back up and run parent
if(task.parent !== null) {
// propagate task info
task.parent.error = task.error;
task.parent.swapTime = task.swapTime;
task.parent.userData = task.userData;
// no subtasks left, call run next subtask on parent
runNext(task.parent, recurse);
}
}
}
}
};
if(swap) {
// we're swapping, so run asynchronously
setTimeout(doNext, 0);
}
else {
// not swapping, so run synchronously
doNext(recurse);
}
};
/**
* Finishes a task and looks for the next task in the queue to start.
*
* @param task the task to finish.
* @param suppressCallbacks true to suppress callbacks.
*/
var finish = function(task, suppressCallbacks) {
// subtask is now done
task.state = DONE;
delete sTasks[task.id];
if(sVL >= 1) {
forge.log.verbose(cat, '[%s][%s] finish',
task.id, task.name, task);
}
// only do queue processing for root tasks
if(task.parent === null) {
// report error if queue is missing
if(!(task.type in sTaskQueues)) {
forge.log.error(cat,
'[%s][%s] task queue missing [%s]',
task.id, task.name, task.type);
}
// report error if queue is empty
else if(sTaskQueues[task.type].length === 0) {
forge.log.error(cat,
'[%s][%s] task queue empty [%s]',
task.id, task.name, task.type);
}
// report error if this task isn't the first in the queue
else if(sTaskQueues[task.type][0] !== task) {
forge.log.error(cat,
'[%s][%s] task not first in queue [%s]',
task.id, task.name, task.type);
}
else {
// remove ourselves from the queue
sTaskQueues[task.type].shift();
// clean up queue if it is empty
if(sTaskQueues[task.type].length === 0) {
if(sVL >= 1) {
forge.log.verbose(cat, '[%s][%s] delete queue [%s]',
task.id, task.name, task.type);
}
/* Note: Only a task can delete a queue of its own type. This
is used as a way to synchronize tasks. If a queue for a certain
task type exists, then a task of that type is running.
*/
delete sTaskQueues[task.type];
}
// dequeue the next task and start it
else {
if(sVL >= 1) {
forge.log.verbose(cat,
'[%s][%s] queue start next [%s] remain:%s',
task.id, task.name, task.type,
sTaskQueues[task.type].length);
}
sTaskQueues[task.type][0].start();
}
}
if(!suppressCallbacks) {
// call final callback if one exists
if(task.error && task.failureCallback) {
task.failureCallback(task);
}
else if(!task.error && task.successCallback) {
task.successCallback(task);
}
}
}
};
/* Tasks API */
forge.task = forge.task || {};
/**
* Starts a new task that will run the passed function asynchronously.
*
* In order to finish the task, either task.doNext() or task.fail()
* *must* be called.
*
* The task must have a type (a string identifier) that can be used to
* synchronize it with other tasks of the same type. That type can also
* be used to cancel tasks that haven't started yet.
*
* To start a task, the following object must be provided as a parameter
* (each function takes a task object as its first parameter):
*
* {
* type: the type of task.
* run: the function to run to execute the task.
* success: a callback to call when the task succeeds (optional).
* failure: a callback to call when the task fails (optional).
* }
*
* @param options the object as described above.
*/
forge.task.start = function(options) {
// create a new task
var task = new Task({
run: options.run,
name: options.name || sNoTaskName
});
task.type = options.type;
task.successCallback = options.success || null;
task.failureCallback = options.failure || null;
// append the task onto the appropriate queue
if(!(task.type in sTaskQueues)) {
if(sVL >= 1) {
forge.log.verbose(cat, '[%s][%s] create queue [%s]',
task.id, task.name, task.type);
}
// create the queue with the new task
sTaskQueues[task.type] = [task];
start(task);
}
else {
// push the task onto the queue, it will be run after a task
// with the same type completes
sTaskQueues[options.type].push(task);
}
};
/**
* Cancels all tasks of the given type that haven't started yet.
*
* @param type the type of task to cancel.
*/
forge.task.cancel = function(type) {
// find the task queue
if(type in sTaskQueues) {
// empty all but the current task from the queue
sTaskQueues[type] = [sTaskQueues[type][0]];
}
};
/**
* Creates a condition variable to synchronize tasks. To make a task wait
* on the condition variable, call task.wait(condition). To notify all
* tasks that are waiting, call condition.notify().
*
* @return the condition variable.
*/
forge.task.createCondition = function() {
var cond = {
// all tasks that are blocked
tasks: {}
};
/**
* Causes the given task to block until notify is called. If the task
* is already waiting on this condition then this is a no-op.
*
* @param task the task to cause to wait.
*/
cond.wait = function(task) {
// only block once
if(!(task.id in cond.tasks))
{
task.block();
cond.tasks[task.id] = task;
}
};
/**
* Notifies all waiting tasks to wake up.
*/
cond.notify = function() {
// since unblock() will run the next task from here, make sure to
// clear the condition's blocked task list before unblocking
var tmp = cond.tasks;
cond.tasks = {};
for(var id in tmp) {
tmp[id].unblock();
}
};
return cond;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'task';
var deps = ['./debug', './log', './util'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

4364
src/lib/tls.js Normal file

File diff suppressed because it is too large Load Diff

297
src/lib/tlssocket.js Normal file
View File

@ -0,0 +1,297 @@
/**
* Socket wrapping functions for TLS.
*
* @author Dave Longley
*
* Copyright (c) 2009-2012 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
/**
* Wraps a forge.net socket with a TLS layer.
*
* @param options:
* sessionId: a session ID to reuse, null for a new connection if no session
* cache is provided or it is empty.
* caStore: an array of certificates to trust.
* sessionCache: a session cache to use.
* cipherSuites: an optional array of cipher suites to use, see
* tls.CipherSuites.
* socket: the socket to wrap.
* virtualHost: the virtual server name to use in a TLS SNI extension.
* verify: a handler used to custom verify certificates in the chain.
* getCertificate: an optional callback used to get a certificate.
* getPrivateKey: an optional callback used to get a private key.
* getSignature: an optional callback used to get a signature.
* deflate: function(inBytes) if provided, will deflate TLS records using
* the deflate algorithm if the server supports it.
* inflate: function(inBytes) if provided, will inflate TLS records using
* the deflate algorithm if the server supports it.
*
* @return the TLS-wrapped socket.
*/
forge.tls.wrapSocket = function(options) {
// get raw socket
var socket = options.socket;
// create TLS socket
var tlsSocket = {
id: socket.id,
// set handlers
connected: socket.connected || function(e){},
closed: socket.closed || function(e){},
data: socket.data || function(e){},
error: socket.error || function(e){}
};
// create TLS connection
var c = forge.tls.createConnection({
server: false,
sessionId: options.sessionId || null,
caStore: options.caStore || [],
sessionCache: options.sessionCache || null,
cipherSuites: options.cipherSuites || null,
virtualHost: options.virtualHost,
verify: options.verify,
getCertificate: options.getCertificate,
getPrivateKey: options.getPrivateKey,
getSignature: options.getSignature,
deflate: options.deflate,
inflate: options.inflate,
connected: function(c) {
// first handshake complete, call handler
if(c.handshakes === 1) {
tlsSocket.connected({
id: socket.id,
type: 'connect',
bytesAvailable: c.data.length()
});
}
},
tlsDataReady: function(c) {
// send TLS data over socket
return socket.send(c.tlsData.getBytes());
},
dataReady: function(c) {
// indicate application data is ready
tlsSocket.data({
id: socket.id,
type: 'socketData',
bytesAvailable: c.data.length()
});
},
closed: function(c) {
// close socket
socket.close();
},
error: function(c, e) {
// send error, close socket
tlsSocket.error({
id: socket.id,
type: 'tlsError',
message: e.message,
bytesAvailable: 0,
error: e
});
socket.close();
}
});
// handle doing handshake after connecting
socket.connected = function(e) {
c.handshake(options.sessionId);
};
// handle closing TLS connection
socket.closed = function(e) {
if(c.open && c.handshaking) {
// error
tlsSocket.error({
id: socket.id,
type: 'ioError',
message: 'Connection closed during handshake.',
bytesAvailable: 0
});
}
c.close();
// call socket handler
tlsSocket.closed({
id: socket.id,
type: 'close',
bytesAvailable: 0
});
};
// handle error on socket
socket.error = function(e) {
// error
tlsSocket.error({
id: socket.id,
type: e.type,
message: e.message,
bytesAvailable: 0
});
c.close();
};
// handle receiving raw TLS data from socket
var _requiredBytes = 0;
socket.data = function(e) {
// drop data if connection not open
if(!c.open) {
socket.receive(e.bytesAvailable);
}
else {
// only receive if there are enough bytes available to
// process a record
if(e.bytesAvailable >= _requiredBytes) {
var count = Math.max(e.bytesAvailable, _requiredBytes);
var data = socket.receive(count);
if(data !== null) {
_requiredBytes = c.process(data);
}
}
}
};
/**
* Destroys this socket.
*/
tlsSocket.destroy = function() {
socket.destroy();
};
/**
* Sets this socket's TLS session cache. This should be called before
* the socket is connected or after it is closed.
*
* The cache is an object mapping session IDs to internal opaque state.
* An application might need to change the cache used by a particular
* tlsSocket between connections if it accesses multiple TLS hosts.
*
* @param cache the session cache to use.
*/
tlsSocket.setSessionCache = function(cache) {
c.sessionCache = tls.createSessionCache(cache);
};
/**
* Connects this socket.
*
* @param options:
* host: the host to connect to.
* port: the port to connect to.
* policyPort: the policy port to use (if non-default), 0 to
* use the flash default.
* policyUrl: the policy file URL to use (instead of port).
*/
tlsSocket.connect = function(options) {
socket.connect(options);
};
/**
* Closes this socket.
*/
tlsSocket.close = function() {
c.close();
};
/**
* Determines if the socket is connected or not.
*
* @return true if connected, false if not.
*/
tlsSocket.isConnected = function() {
return c.isConnected && socket.isConnected();
};
/**
* Writes bytes to this socket.
*
* @param bytes the bytes (as a string) to write.
*
* @return true on success, false on failure.
*/
tlsSocket.send = function(bytes) {
return c.prepare(bytes);
};
/**
* Reads bytes from this socket (non-blocking). Fewer than the number of
* bytes requested may be read if enough bytes are not available.
*
* This method should be called from the data handler if there are enough
* bytes available. To see how many bytes are available, check the
* 'bytesAvailable' property on the event in the data handler or call the
* bytesAvailable() function on the socket. If the browser is msie, then the
* bytesAvailable() function should be used to avoid race conditions.
* Otherwise, using the property on the data handler's event may be quicker.
*
* @param count the maximum number of bytes to read.
*
* @return the bytes read (as a string) or null on error.
*/
tlsSocket.receive = function(count) {
return c.data.getBytes(count);
};
/**
* Gets the number of bytes available for receiving on the socket.
*
* @return the number of bytes available for receiving.
*/
tlsSocket.bytesAvailable = function() {
return c.data.length();
};
return tlsSocket;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'tlssocket';
var deps = ['./tls'];
var nodeDefine = null;
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
nodeDefine = function(ids, factory) {
factory(require, module);
};
}
// <script>
else {
if(typeof forge === 'undefined') {
forge = {};
}
initModule(forge);
}
}
// AMD
if(nodeDefine || typeof define === 'function') {
// define module AMD style
(nodeDefine || define)(['require', 'module'].concat(deps),
function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
});
}
})();

1578
src/lib/util.js Normal file

File diff suppressed because it is too large Load Diff

758
src/lib/xhr.js Normal file
View File

@ -0,0 +1,758 @@
/**
* XmlHttpRequest implementation that uses TLS and flash SocketPool.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function($) {
// logging category
var cat = 'forge.xhr';
/*
XMLHttpRequest interface definition from:
http://www.w3.org/TR/XMLHttpRequest
interface XMLHttpRequest {
// event handler
attribute EventListener onreadystatechange;
// state
const unsigned short UNSENT = 0;
const unsigned short OPENED = 1;
const unsigned short HEADERS_RECEIVED = 2;
const unsigned short LOADING = 3;
const unsigned short DONE = 4;
readonly attribute unsigned short readyState;
// request
void open(in DOMString method, in DOMString url);
void open(in DOMString method, in DOMString url, in boolean async);
void open(in DOMString method, in DOMString url,
in boolean async, in DOMString user);
void open(in DOMString method, in DOMString url,
in boolean async, in DOMString user, in DOMString password);
void setRequestHeader(in DOMString header, in DOMString value);
void send();
void send(in DOMString data);
void send(in Document data);
void abort();
// response
DOMString getAllResponseHeaders();
DOMString getResponseHeader(in DOMString header);
readonly attribute DOMString responseText;
readonly attribute Document responseXML;
readonly attribute unsigned short status;
readonly attribute DOMString statusText;
};
*/
// readyStates
var UNSENT = 0;
var OPENED = 1;
var HEADERS_RECEIVED = 2;
var LOADING = 3;
var DONE = 4;
// exceptions
var INVALID_STATE_ERR = 11;
var SYNTAX_ERR = 12;
var SECURITY_ERR = 18;
var NETWORK_ERR = 19;
var ABORT_ERR = 20;
// private flash socket pool vars
var _sp = null;
var _policyPort = 0;
var _policyUrl = null;
// default client (used if no special URL provided when creating an XHR)
var _client = null;
// all clients including the default, key'd by full base url
// (multiple cross-domain http clients are permitted so there may be more
// than one client in this map)
// TODO: provide optional clean up API for non-default clients
var _clients = {};
// the default maximum number of concurrents connections per client
var _maxConnections = 10;
// local aliases
if(typeof forge === 'undefined') {
forge = {};
}
var net = forge.net;
var http = forge.http;
// define the xhr interface
var xhrApi = {};
/**
* Initializes flash XHR support.
*
* @param options:
* url: the default base URL to connect to if xhr URLs are relative,
* ie: https://myserver.com.
* flashId: the dom ID of the flash SocketPool.
* policyPort: the port that provides the server's flash policy, 0 to use
* the flash default.
* policyUrl: the policy file URL to use instead of a policy port.
* msie: true if browser is internet explorer, false if not.
* connections: the maximum number of concurrent connections.
* caCerts: a list of PEM-formatted certificates to trust.
* cipherSuites: an optional array of cipher suites to use,
* see forge.tls.CipherSuites.
* verify: optional TLS certificate verify callback to use (see forge.tls
* for details).
* getCertificate: an optional callback used to get a client-side
* certificate (see forge.tls for details).
* getPrivateKey: an optional callback used to get a client-side private
* key (see forge.tls for details).
* getSignature: an optional callback used to get a client-side signature
* (see forge.tls for details).
* persistCookies: true to use persistent cookies via flash local storage,
* false to only keep cookies in javascript.
* primeTlsSockets: true to immediately connect TLS sockets on their
* creation so that they will cache TLS sessions for reuse.
*/
xhrApi.init = function(options) {
forge.log.debug(cat, 'initializing', options);
// update default policy port and max connections
_policyPort = options.policyPort || _policyPort;
_policyUrl = options.policyUrl || _policyUrl;
_maxConnections = options.connections || _maxConnections;
// create the flash socket pool
_sp = net.createSocketPool({
flashId: options.flashId,
policyPort: _policyPort,
policyUrl: _policyUrl,
msie: options.msie || false
});
// create default http client
_client = http.createClient({
url: options.url || (
window.location.protocol + '//' + window.location.host),
socketPool: _sp,
policyPort: _policyPort,
policyUrl: _policyUrl,
connections: options.connections || _maxConnections,
caCerts: options.caCerts,
cipherSuites: options.cipherSuites,
persistCookies: options.persistCookies || true,
primeTlsSockets: options.primeTlsSockets || false,
verify: options.verify,
getCertificate: options.getCertificate,
getPrivateKey: options.getPrivateKey,
getSignature: options.getSignature
});
_clients[_client.url.full] = _client;
forge.log.debug(cat, 'ready');
};
/**
* Called to clean up the clients and socket pool.
*/
xhrApi.cleanup = function() {
// destroy all clients
for(var key in _clients) {
_clients[key].destroy();
}
_clients = {};
_client = null;
// destroy socket pool
_sp.destroy();
_sp = null;
};
/**
* Sets a cookie.
*
* @param cookie the cookie with parameters:
* name: the name of the cookie.
* value: the value of the cookie.
* comment: an optional comment string.
* maxAge: the age of the cookie in seconds relative to created time.
* secure: true if the cookie must be sent over a secure protocol.
* httpOnly: true to restrict access to the cookie from javascript
* (inaffective since the cookies are stored in javascript).
* path: the path for the cookie.
* domain: optional domain the cookie belongs to (must start with dot).
* version: optional version of the cookie.
* created: creation time, in UTC seconds, of the cookie.
*/
xhrApi.setCookie = function(cookie) {
// default cookie expiration to never
cookie.maxAge = cookie.maxAge || -1;
// if the cookie's domain is set, use the appropriate client
if(cookie.domain) {
// add the cookies to the applicable domains
for(var key in _clients) {
var client = _clients[key];
if(http.withinCookieDomain(client.url, cookie) &&
client.secure === cookie.secure) {
client.setCookie(cookie);
}
}
}
// use the default domain
// FIXME: should a null domain cookie be added to all clients? should
// this be an option?
else {
_client.setCookie(cookie);
}
};
/**
* Gets a cookie.
*
* @param name the name of the cookie.
* @param path an optional path for the cookie (if there are multiple cookies
* with the same name but different paths).
* @param domain an optional domain for the cookie (if not using the default
* domain).
*
* @return the cookie, cookies (if multiple matches), or null if not found.
*/
xhrApi.getCookie = function(name, path, domain) {
var rval = null;
if(domain) {
// get the cookies from the applicable domains
for(var key in _clients) {
var client = _clients[key];
if(http.withinCookieDomain(client.url, domain)) {
var cookie = client.getCookie(name, path);
if(cookie !== null) {
if(rval === null) {
rval = cookie;
}
else if(rval.constructor != Array) {
rval = [rval, cookie];
}
else {
rval.push(cookie);
}
}
}
}
}
else {
// get cookie from default domain
rval = _client.getCookie(name, path);
}
return rval;
};
/**
* Removes a cookie.
*
* @param name the name of the cookie.
* @param path an optional path for the cookie (if there are multiple cookies
* with the same name but different paths).
* @param domain an optional domain for the cookie (if not using the default
* domain).
*
* @return true if a cookie was removed, false if not.
*/
xhrApi.removeCookie = function(name, path, domain) {
var rval = false;
if(domain) {
// remove the cookies from the applicable domains
for(var key in _clients) {
var client = _clients[key];
if(http.withinCookieDomain(client.url, domain)) {
if(client.removeCookie(name, path)) {
rval = true;
}
}
}
}
else {
// remove cookie from default domain
rval = _client.removeCookie(name, path);
}
return rval;
};
/**
* Creates a new XmlHttpRequest. By default the base URL, flash policy port,
* etc, will be used. However, an XHR can be created to point at another
* cross-domain URL.
*
* @param options:
* logWarningOnError: If true and an HTTP error status code is received then
* log a warning, otherwise log a verbose message.
* verbose: If true be very verbose in the output including the response
* event and response body, otherwise only include status, timing, and
* data size.
* logError: a multi-var log function for warnings that takes the log
* category as the first var.
* logWarning: a multi-var log function for warnings that takes the log
* category as the first var.
* logDebug: a multi-var log function for warnings that takes the log
* category as the first var.
* logVerbose: a multi-var log function for warnings that takes the log
* category as the first var.
* url: the default base URL to connect to if xhr URLs are relative,
* eg: https://myserver.com, and note that the following options will be
* ignored if the URL is absent or the same as the default base URL.
* policyPort: the port that provides the server's flash policy, 0 to use
* the flash default.
* policyUrl: the policy file URL to use instead of a policy port.
* connections: the maximum number of concurrent connections.
* caCerts: a list of PEM-formatted certificates to trust.
* cipherSuites: an optional array of cipher suites to use, see
* forge.tls.CipherSuites.
* verify: optional TLS certificate verify callback to use (see forge.tls
* for details).
* getCertificate: an optional callback used to get a client-side
* certificate.
* getPrivateKey: an optional callback used to get a client-side private key.
* getSignature: an optional callback used to get a client-side signature.
* persistCookies: true to use persistent cookies via flash local storage,
* false to only keep cookies in javascript.
* primeTlsSockets: true to immediately connect TLS sockets on their
* creation so that they will cache TLS sessions for reuse.
*
* @return the XmlHttpRequest.
*/
xhrApi.create = function(options) {
// set option defaults
options = $.extend({
logWarningOnError: true,
verbose: false,
logError: function(){},
logWarning: function(){},
logDebug: function(){},
logVerbose: function(){},
url: null
}, options || {});
// private xhr state
var _state = {
// the http client to use
client: null,
// request storage
request: null,
// response storage
response: null,
// asynchronous, true if doing asynchronous communication
asynchronous: true,
// sendFlag, true if send has been called
sendFlag: false,
// errorFlag, true if a network error occurred
errorFlag: false
};
// private log functions
var _log = {
error: options.logError || forge.log.error,
warning: options.logWarning || forge.log.warning,
debug: options.logDebug || forge.log.debug,
verbose: options.logVerbose || forge.log.verbose
};
// create public xhr interface
var xhr = {
// an EventListener
onreadystatechange: null,
// readonly, the current readyState
readyState: UNSENT,
// a string with the response entity-body
responseText: '',
// a Document for response entity-bodies that are XML
responseXML: null,
// readonly, returns the HTTP status code (i.e. 404)
status: 0,
// readonly, returns the HTTP status message (i.e. 'Not Found')
statusText: ''
};
// determine which http client to use
if(options.url === null) {
// use default
_state.client = _client;
}
else {
var url = http.parseUrl(options.url);
if(!url) {
throw {
message: 'Invalid url.',
details: {
url: options.url
}
};
}
// find client
if(url.full in _clients) {
// client found
_state.client = _clients[url.full];
}
else {
// create client
_state.client = http.createClient({
url: options.url,
socketPool: _sp,
policyPort: options.policyPort || _policyPort,
policyUrl: options.policyUrl || _policyUrl,
connections: options.connections || _maxConnections,
caCerts: options.caCerts,
cipherSuites: options.cipherSuites,
persistCookies: options.persistCookies || true,
primeTlsSockets: options.primeTlsSockets || false,
verify: options.verify,
getCertificate: options.getCertificate,
getPrivateKey: options.getPrivateKey,
getSignature: options.getSignature
});
_clients[url.full] = _state.client;
}
}
/**
* Opens the request. This method will create the HTTP request to send.
*
* @param method the HTTP method (i.e. 'GET').
* @param url the relative url (the HTTP request path).
* @param async always true, ignored.
* @param user always null, ignored.
* @param password always null, ignored.
*/
xhr.open = function(method, url, async, user, password) {
// 1. validate Document if one is associated
// TODO: not implemented (not used yet)
// 2. validate method token
// 3. change method to uppercase if it matches a known
// method (here we just require it to be uppercase, and
// we do not allow the standard methods)
// 4. disallow CONNECT, TRACE, or TRACK with a security error
switch(method) {
case 'DELETE':
case 'GET':
case 'HEAD':
case 'OPTIONS':
case 'POST':
case 'PUT':
// valid method
break;
case 'CONNECT':
case 'TRACE':
case 'TRACK':
// FIXME: handle exceptions
throw SECURITY_ERR;
default:
// FIXME: handle exceptions
//throw new SyntaxError('Invalid method: ' + method);
throw SYNTAX_ERR;
}
// TODO: other validation steps in algorithm are not implemented
// 19. set send flag to false
// set response body to null
// empty list of request headers
// set request method to given method
// set request URL
// set username, password
// set asychronous flag
_state.sendFlag = false;
xhr.responseText = '';
xhr.responseXML = null;
// custom: reset status and statusText
xhr.status = 0;
xhr.statusText = '';
// create the HTTP request
_state.request = http.createRequest({
method: method,
path: url
});
// 20. set state to OPENED
xhr.readyState = OPENED;
// 21. dispatch onreadystatechange
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
};
/**
* Adds an HTTP header field to the request.
*
* @param header the name of the header field.
* @param value the value of the header field.
*/
xhr.setRequestHeader = function(header, value) {
// 1. if state is not OPENED or send flag is true, raise exception
if(xhr.readyState != OPENED || _state.sendFlag) {
// FIXME: handle exceptions properly
throw INVALID_STATE_ERR;
}
// TODO: other validation steps in spec aren't implemented
// set header
_state.request.setField(header, value);
};
/**
* Sends the request and any associated data.
*
* @param data a string or Document object to send, null to send no data.
*/
xhr.send = function(data) {
// 1. if state is not OPENED or 2. send flag is true, raise
// an invalid state exception
if(xhr.readyState != OPENED || _state.sendFlag) {
// FIXME: handle exceptions properly
throw INVALID_STATE_ERR;
}
// 3. ignore data if method is GET or HEAD
if(data &&
_state.request.method !== 'GET' &&
_state.request.method !== 'HEAD') {
// handle non-IE case
if(typeof(XMLSerializer) !== 'undefined') {
if(data instanceof Document) {
var xs = new XMLSerializer();
_state.request.body = xs.serializeToString(data);
}
else {
_state.request.body = data;
}
}
// poorly implemented IE case
else {
if(typeof(data.xml) !== 'undefined') {
_state.request.body = xml;
}
else {
_state.request.body = data;
}
}
}
// 4. release storage mutex (not used)
// 5. set error flag to false
_state.errorFlag = false;
// 6. if asynchronous is true (must be in this implementation)
// 6.1 set send flag to true
_state.sendFlag = true;
// 6.2 dispatch onreadystatechange
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
// create send options
var options = {};
options.request = _state.request;
options.headerReady = function(e) {
// make cookies available for ease of use/iteration
xhr.cookies = _state.client.cookies;
// TODO: update document.cookie with any cookies where the
// script's domain matches
// headers received
xhr.readyState = HEADERS_RECEIVED;
xhr.status = e.response.code;
xhr.statusText = e.response.message;
_state.response = e.response;
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
if(!_state.response.aborted) {
// now loading body
xhr.readyState = LOADING;
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
}
};
options.bodyReady = function(e) {
xhr.readyState = DONE;
var ct = e.response.getField('Content-Type');
// Note: this null/undefined check is done outside because IE
// dies otherwise on a "'null' is null" error
if(ct) {
if(ct.indexOf('text/xml') === 0 ||
ct.indexOf('application/xml') === 0 ||
ct.indexOf('+xml') !== -1) {
try {
var doc = new ActiveXObject('MicrosoftXMLDOM');
doc.async = false;
doc.loadXML(e.response.body);
xhr.responseXML = doc;
}
catch(ex) {
var parser = new DOMParser();
xhr.responseXML = parser.parseFromString(ex.body, 'text/xml');
}
}
}
var length = 0;
if(e.response.body !== null) {
xhr.responseText = e.response.body;
length = e.response.body.length;
}
// build logging output
var req = _state.request;
var output =
req.method + ' ' + req.path + ' ' +
xhr.status + ' ' + xhr.statusText + ' ' +
length + 'B ' +
(e.request.connectTime + e.request.time + e.response.time) +
'ms';
var lFunc;
if(options.verbose) {
lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
_log.warning : _log.verbose;
lFunc(cat, output,
e, e.response.body ? '\n' + e.response.body : '\nNo content');
}
else {
lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
_log.warning : _log.debug;
lFunc(cat, output);
}
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
};
options.error = function(e) {
var req = _state.request;
_log.error(cat, req.method + ' ' + req.path, e);
// 1. set response body to null
xhr.responseText = '';
xhr.responseXML = null;
// 2. set error flag to true (and reset status)
_state.errorFlag = true;
xhr.status = 0;
xhr.statusText = '';
// 3. set state to done
xhr.readyState = DONE;
// 4. asyc flag is always true, so dispatch onreadystatechange
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
};
// 7. send request
_state.client.send(options);
};
/**
* Aborts the request.
*/
xhr.abort = function() {
// 1. abort send
// 2. stop network activity
_state.request.abort();
// 3. set response to null
xhr.responseText = '';
xhr.responseXML = null;
// 4. set error flag to true (and reset status)
_state.errorFlag = true;
xhr.status = 0;
xhr.statusText = '';
// 5. clear user headers
_state.request = null;
_state.response = null;
// 6. if state is DONE or UNSENT, or if OPENED and send flag is false
if(xhr.readyState == DONE || xhr.readyState == UNSENT ||
(xhr.readyState == OPENED && !_state.sendFlag)) {
// 7. set ready state to unsent
xhr.readyState = UNSENT;
}
else {
// 6.1 set state to DONE
xhr.readyState = DONE;
// 6.2 set send flag to false
_state.sendFlag = false;
// 6.3 dispatch onreadystatechange
if(xhr.onreadystatechange) {
xhr.onreadystatechange();
}
// 7. set state to UNSENT
xhr.readyState = UNSENT;
}
};
/**
* Gets all response headers as a string.
*
* @return the HTTP-encoded response header fields.
*/
xhr.getAllResponseHeaders = function() {
var rval = '';
if(_state.response !== null) {
var fields = _state.response.fields;
$.each(fields, function(name, array) {
$.each(array, function(i, value) {
rval += name + ': ' + value + '\r\n';
});
});
}
return rval;
};
/**
* Gets a single header field value or, if there are multiple
* fields with the same name, a comma-separated list of header
* values.
*
* @return the header field value(s) or null.
*/
xhr.getResponseHeader = function(header) {
var rval = null;
if(_state.response !== null) {
if(header in _state.response.fields) {
rval = _state.response.fields[header];
if(rval.constructor == Array) {
rval = rval.join();
}
}
}
return rval;
};
return xhr;
};
// expose public api
forge.xhr = xhrApi;
})(jQuery);

40
src/require-config.js Normal file
View File

@ -0,0 +1,40 @@
(function() {
'use strict';
require.config({
baseUrl: 'lib',
paths: {
js: '../js',
test: '../../test',
cryptoLib: '../js/crypto',
jquery: 'jquery-1.8.2.min',
underscore: 'underscore-1.4.4.min',
backbone: 'backbone-1.0.0.min',
lawnchair: 'lawnchair/lawnchair-git',
lawnchairSQL: 'lawnchair/lawnchair-adapter-webkit-sqlite-git',
lawnchairIDB: 'lawnchair/lawnchair-adapter-indexed-db-git',
cordova: 'cordova-2.5.0'
},
shim: {
lawnchair: {
deps: ['lawnchairSQL', 'lawnchairIDB'],
exports: 'Lawnchair'
},
backbone: {
//These script dependencies should be loaded before loading
//backbone.js
deps: ['underscore', 'jquery'],
//Once loaded, use the global 'Backbone' as the
//module value.
exports: 'Backbone'
},
underscore: {
exports: '_'
},
cordova: {
exports: 'cordova'
}
}
});
}());

View File

@ -1,10 +1,11 @@
var TestData = function() {
define(['cryptoLib/util'], function(util) {
'use strict';
var util = new cryptoLib.Util(window, uuid);
var self = {};
this.getEmailCollection = function(size) {
self.getEmailCollection = function(size) {
// create test data
var i, mail, envelope, collection = new app.model.EmailCollection(),
var i, mail, collection = new app.model.EmailCollection(),
bigAssString = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet,';
for (i = 0; i < size; i++) {
@ -23,7 +24,7 @@ var TestData = function() {
return collection;
};
this.packageCollectionForEncryption = function(collection, keySize, ivSize) {
self.packageCollectionForEncryption = function(collection, keySize, ivSize) {
// package json objects for batch encrytion
var envelope, envelopes = [];
@ -41,7 +42,7 @@ var TestData = function() {
return envelopes;
};
this.generateBigString = function(iterations) {
self.generateBigString = function(iterations) {
var test_message = '';
// generate test data
for (var i = 0; i < iterations; i++) {
@ -51,4 +52,5 @@ var TestData = function() {
return test_message;
};
};
return self;
});

View File

@ -1,23 +1,24 @@
module("AES Crypto");
define(['cryptoLib/aes-cbc', 'cryptoLib/util', 'test/test-data'], function(aes, util, testData) {
'use strict';
var aes_test = {
keySize: 128,
util: new cryptoLib.Util(window, uuid),
test_message: new TestData().generateBigString(1000)
};
module("AES Crypto");
test("CBC mode", 4, function() {
var aes = new cryptoLib.AesCBC(forge);
var aes_test = {
keySize: 128,
test_message: testData.generateBigString(1000)
};
var plaintext = aes_test.test_message;
var key = aes_test.util.random(aes_test.keySize);
var iv = aes_test.util.random(aes_test.keySize);
ok(key, 'Key: ' + key);
equal(aes_test.util.base642Str(key).length * 8, aes_test.keySize, 'Keysize ' + aes_test.keySize);
test("CBC mode", 4, function() {
var plaintext = aes_test.test_message;
var key = util.random(aes_test.keySize);
var iv = util.random(aes_test.keySize);
ok(key, 'Key: ' + key);
equal(util.base642Str(key).length * 8, aes_test.keySize, 'Keysize ' + aes_test.keySize);
var ciphertext = aes.encrypt(plaintext, key, iv);
ok(ciphertext, 'Ciphertext lenght: ' + ciphertext.length);
var ciphertext = aes.encrypt(plaintext, key, iv);
ok(ciphertext, 'Ciphertext lenght: ' + ciphertext.length);
var decrypted = aes.decrypt(ciphertext, key, iv);
equal(decrypted, plaintext, 'Decryption correct' + decrypted);
var decrypted = aes.decrypt(ciphertext, key, iv);
equal(decrypted, plaintext, 'Decryption correct' + decrypted);
});
});

View File

@ -1,40 +1,42 @@
module("Forge Crypto");
define(['forge', 'cryptoLib/util', 'test/test-data'], function(forge, util, testData) {
'use strict';
var forge_rsa_test = {
keySize: 1024,
test_message: '06a9214036b8a15b512e03d534120006'
};
module("Forge Crypto");
var forge_aes_test = {
keySize: 128,
test_message: new TestData().generateBigString(1000)
};
var forge_rsa_test = {
keySize: 1024,
test_message: '06a9214036b8a15b512e03d534120006'
};
test("SHA-1 Hash", 1, function() {
var sha1 = forge.md.sha1.create();
sha1.update(forge_aes_test.test_message);
var digest = sha1.digest().toHex();
ok(digest, digest);
});
var forge_aes_test = {
keySize: 128,
test_message: testData.generateBigString(1000)
};
test("SHA-256 Hash", 1, function() {
forge_rsa_test.md = forge.md.sha256.create();
forge_rsa_test.md.update(forge_aes_test.test_message);
var digest = forge_rsa_test.md.digest().toHex();
ok(digest, digest);
});
test("SHA-1 Hash", 1, function() {
var sha1 = forge.md.sha1.create();
sha1.update(forge_aes_test.test_message);
var digest = sha1.digest().toHex();
ok(digest, digest);
});
test("HMAC SHA-256", 1, function() {
var util = new cryptoLib.Util(window, uuid);
test("SHA-256 Hash", 1, function() {
forge_rsa_test.md = forge.md.sha256.create();
forge_rsa_test.md.update(forge_aes_test.test_message);
var digest = forge_rsa_test.md.digest().toHex();
ok(digest, digest);
});
var key = util.base642Str(util.random(forge_aes_test.keySize));
var iv = util.base642Str(util.random(forge_aes_test.keySize));
test("HMAC SHA-256", 1, function() {
var key = util.base642Str(util.random(forge_aes_test.keySize));
var iv = util.base642Str(util.random(forge_aes_test.keySize));
var hmac = forge.hmac.create();
hmac.start('sha256', key);
hmac.update(iv);
hmac.update(forge_aes_test.test_message);
var digest = hmac.digest().toHex();
var hmac = forge.hmac.create();
hmac.start('sha256', key);
hmac.update(iv);
hmac.update(forge_aes_test.test_message);
var digest = hmac.digest().toHex();
ok(digest, digest);
ok(digest, digest);
});
});

View File

@ -9,58 +9,17 @@
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script>
// clear session storage of failed tests, so async order is correct after fail & refresh
window.sessionStorage.clear();
//window.Worker = undefined;
</script>
<!-- dependencies -->
<script src="../../src/lib/cordova-2.5.0.js"></script>
<script src="../qunit-1.11.0.js"></script>
<script src="../../src/lib/jquery-1.8.2.min.js"></script>
<script src="../../src/lib/underscore-1.4.4.min.js"></script>
<script src="../../src/lib/backbone-1.0.0.min.js"></script>
<script src="../../src/lib/lawnchair/lawnchair-git.js"></script>
<script src="../../src/lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js"></script>
<script src="../../src/lib/lawnchair/lawnchair-adapter-indexed-db-git.js"></script>
<script>QUnit.config.autostart = false;</script>
<script data-main="main.js" src="../../src/lib/require.js"></script>
<script src="../../src/lib/forge/forge.rsa.bundle.js"></script>
<script src="../../src/lib/uuid.js"></script>
<script src="../../src/js/app-config.js"></script>
<script>
app.config.workerPath = '../../src/js';
app.config.cloudUrl = 'http://localhost:8888';
</script>
<script src="../../src/js/model/email-model.js"></script>
<script src="../../src/js/model/folder-model.js"></script>
<script src="../../src/js/model/account-model.js"></script>
<script src="../../src/js/crypto/util.js"></script>
<script src="../../src/js/crypto/pbkdf2.js"></script>
<script src="../../src/js/crypto/aes-cbc.js"></script>
<script src="../../src/js/crypto/rsa.js"></script>
<script src="../../src/js/crypto/crypto-batch.js"></script>
<script src="../../src/js/crypto/crypto.js"></script>
<script src="../../src/js/dao/lawnchair-dao.js"></script>
<script src="../../src/js/dao/devicestorage.js"></script>
<script src="../../src/js/dao/cloudstorage-dao.js"></script>
<script src="../../src/js/dao/keychain-dao.js"></script>
<script src="../../src/js/dao/email-dao.js"></script>
<!-- tests -->
<script src="../test-data.js"></script>
<script src="forge-test.js"></script>
<script src="aes-test.js"></script>
<!--<script src="aes-test.js"></script>
<script src="rsa-test.js"></script>
<script src="lawnchair-dao-test.js"></script>
<script src="keychain-dao-test.js"></script>
<script src="crypto-test.js"></script>
<script src="devicestorage-test.js"></script>
<script src="email-dao-test.js"></script>
<script src="email-dao-test.js"></script>-->
</body>
</html>

30
test/unit/main.js Normal file
View File

@ -0,0 +1,30 @@
'use strict';
require(['../../src/require-config'], function() {
require.config({
baseUrl: '../../src/lib'
});
// Start the main app logic.
require(['js/app-config'], function() {
// clear session storage of failed tests, so async order is correct after fail & refresh
window.sessionStorage.clear();
window.Worker = undefined;
app.config.workerPath = '../../src/js';
app.config.cloudUrl = 'http://localhost:8888';
startTests();
});
});
function startTests() {
require([
'test/unit/forge-test',
'test/unit/aes-test'
], function() {
QUnit.start(); //Tests loaded, run tests
});
}