fork-openpgpjs/doc/openpgp.crypto.js.html
Michal Kolodziej 23295342fc Added utf8 awareness to the literal and userid packet classes. Made s2k
work correctly with utf8 strings. Fixeda visibility issue with the
testing suite and added relevant unit tests.
2013-04-16 10:47:17 +02:00

480 lines
21 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: ciphers/openpgp.crypto.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: ciphers/openpgp.crypto.js</h1>
<section>
<article>
<pre class="prettyprint source"><code>// GPG4Browsers - An OpenPGP implementation in javascript
// Copyright (C) 2011 Recurity Labs GmbH
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// The GPG4Browsers crypto interface
/**
* Encrypts data using the specified public key multiprecision integers
* and the specified algorithm.
* @param {Integer} algo Algorithm to be used (See RFC4880 9.1)
* @param {openpgp_type_mpi[]} publicMPIs Algorithm dependent multiprecision integers
* @param {openpgp_type_mpi} data Data to be encrypted as MPI
* @return {(openpgp_type_mpi|openpgp_type_mpi[])} if RSA an openpgp_type_mpi;
* if elgamal encryption an array of two openpgp_type_mpi is returned; otherwise null
*/
function openpgp_crypto_asymetricEncrypt(algo, publicMPIs, data) {
switch(algo) {
case 1: // RSA (Encrypt or Sign) [HAC]
case 2: // RSA Encrypt-Only [HAC]
case 3: // RSA Sign-Only [HAC]
var rsa = new RSA();
var n = publicMPIs[0].toBigInteger();
var e = publicMPIs[1].toBigInteger();
var m = data.toBigInteger();
return rsa.encrypt(m,e,n).toMPI();
case 16: // Elgamal (Encrypt-Only) [ELGAMAL] [HAC]
var elgamal = new Elgamal();
var p = publicMPIs[0].toBigInteger();
var g = publicMPIs[1].toBigInteger();
var y = publicMPIs[2].toBigInteger();
var m = data.toBigInteger();
return elgamal.encrypt(m,g,p,y);
default:
return null;
}
}
/**
* Decrypts data using the specified public key multiprecision integers of the private key,
* the specified secretMPIs of the private key and the specified algorithm.
* @param {Integer} algo Algorithm to be used (See RFC4880 9.1)
* @param {openpgp_type_mpi[]} publicMPIs Algorithm dependent multiprecision integers
* of the public key part of the private key
* @param {openpgp_type_mpi[]} secretMPIs Algorithm dependent multiprecision integers
* of the private key used
* @param {openpgp_type_mpi} data Data to be encrypted as MPI
* @return {BigInteger} returns a big integer containing the decrypted data; otherwise null
*/
function openpgp_crypto_asymetricDecrypt(algo, publicMPIs, secretMPIs, dataMPIs) {
switch(algo) {
case 1: // RSA (Encrypt or Sign) [HAC]
case 2: // RSA Encrypt-Only [HAC]
case 3: // RSA Sign-Only [HAC]
var rsa = new RSA();
var d = secretMPIs[0].toBigInteger();
var p = secretMPIs[1].toBigInteger();
var q = secretMPIs[2].toBigInteger();
var u = secretMPIs[3].toBigInteger();
var m = dataMPIs[0].toBigInteger();
return rsa.decrypt(m, d, p, q, u);
case 16: // Elgamal (Encrypt-Only) [ELGAMAL] [HAC]
var elgamal = new Elgamal();
var x = secretMPIs[0].toBigInteger();
var c1 = dataMPIs[0].toBigInteger();
var c2 = dataMPIs[1].toBigInteger();
var p = publicMPIs[0].toBigInteger();
return elgamal.decrypt(c1,c2,p,x);
default:
return null;
}
}
/**
* generate random byte prefix as string for the specified algorithm
* @param {Integer} algo Algorithm to use (see RFC4880 9.2)
* @return {String} Random bytes with length equal to the block
* size of the cipher
*/
function openpgp_crypto_getPrefixRandom(algo) {
switch(algo) {
case 2:
case 3:
case 4:
return openpgp_crypto_getRandomBytes(8);
case 7:
case 8:
case 9:
case 10:
return openpgp_crypto_getRandomBytes(16);
default:
return null;
}
}
/**
* retrieve the MDC prefixed bytes by decrypting them
* @param {Integer} algo Algorithm to use (see RFC4880 9.2)
* @param {String} key Key as string. length is depending on the algorithm used
* @param {String} data Encrypted data where the prefix is decrypted from
* @return {String} Plain text data of the prefixed data
*/
function openpgp_crypto_MDCSystemBytes(algo, key, data) {
util.print_debug_hexstr_dump("openpgp_crypto_symmetricDecrypt:\nencrypteddata:",data);
switch(algo) {
case 0: // Plaintext or unencrypted data
return data;
case 2: // TripleDES (DES-EDE, [SCHNEIER] [HAC] - 168 bit key derived from 192)
return openpgp_cfb_mdc(desede, 8, key, data, openpgp_cfb);
case 3: // CAST5 (128 bit key, as per [RFC2144])
return openpgp_cfb_mdc(cast5_encrypt, 8, key, data);
case 4: // Blowfish (128 bit key, 16 rounds) [BLOWFISH]
return openpgp_cfb_mdc(BFencrypt, 8, key, data);
case 7: // AES with 128-bit key [AES]
case 8: // AES with 192-bit key
case 9: // AES with 256-bit key
return openpgp_cfb_mdc(AESencrypt, 16, keyExpansion(key), data);
case 10:
return openpgp_cfb_mdc(TFencrypt, 16, key, data);
case 1: // IDEA [IDEA]
util.print_error(""+ (algo == 1 ? "IDEA Algorithm not implemented" : "Twofish Algorithm not implemented"));
return null;
default:
}
return null;
}
/**
* Generating a session key for the specified symmetric algorithm
* @param {Integer} algo Algorithm to use (see RFC4880 9.2)
* @return {String} Random bytes as a string to be used as a key
*/
function openpgp_crypto_generateSessionKey(algo) {
switch (algo) {
case 2: // TripleDES (DES-EDE, [SCHNEIER] [HAC] - 168 bit key derived from 192)
case 8: // AES with 192-bit key
return openpgp_crypto_getRandomBytes(24);
case 3: // CAST5 (128 bit key, as per [RFC2144])
case 4: // Blowfish (128 bit key, 16 rounds) [BLOWFISH]
case 7: // AES with 128-bit key [AES]
util.print_debug("length = 16:\n"+util.hexstrdump(openpgp_crypto_getRandomBytes(16)));
return openpgp_crypto_getRandomBytes(16);
case 9: // AES with 256-bit key
case 10:// Twofish with 256-bit key [TWOFISH]
return openpgp_crypto_getRandomBytes(32);
}
return null;
}
/**
*
* @param {Integer} algo public Key algorithm
* @param {Integer} hash_algo Hash algorithm
* @param {openpgp_type_mpi[]} msg_MPIs Signature multiprecision integers
* @param {openpgp_type_mpi[]} publickey_MPIs Public key multiprecision integers
* @param {String} data Data on where the signature was computed on.
* @return {Boolean} true if signature (sig_data was equal to data over hash)
*/
function openpgp_crypto_verifySignature(algo, hash_algo, msg_MPIs, publickey_MPIs, data) {
var calc_hash = openpgp_crypto_hashData(hash_algo, data);
switch(algo) {
case 1: // RSA (Encrypt or Sign) [HAC]
case 2: // RSA Encrypt-Only [HAC]
case 3: // RSA Sign-Only [HAC]
var rsa = new RSA();
var n = publickey_MPIs[0].toBigInteger();
var e = publickey_MPIs[1].toBigInteger();
var x = msg_MPIs[0].toBigInteger();
var dopublic = rsa.verify(x,e,n);
var hash = openpgp_encoding_emsa_pkcs1_decode(hash_algo,dopublic.toMPI().substring(2));
if (hash == -1) {
util.print_error("PKCS1 padding in message or key incorrect. Aborting...");
return false;
}
return hash == calc_hash;
case 16: // Elgamal (Encrypt-Only) [ELGAMAL] [HAC]
util.print_error("signing with Elgamal is not defined in the OpenPGP standard.");
return null;
case 17: // DSA (Digital Signature Algorithm) [FIPS186] [HAC]
var dsa = new DSA();
var s1 = msg_MPIs[0].toBigInteger();
var s2 = msg_MPIs[1].toBigInteger();
var p = publickey_MPIs[0].toBigInteger();
var q = publickey_MPIs[1].toBigInteger();
var g = publickey_MPIs[2].toBigInteger();
var y = publickey_MPIs[3].toBigInteger();
var m = data;
var dopublic = dsa.verify(hash_algo,s1,s2,m,p,q,g,y);
return dopublic.compareTo(s1) == 0;
default:
return null;
}
}
/**
* Create a signature on data using the specified algorithm
* @param {Integer} hash_algo hash Algorithm to use (See RFC4880 9.4)
* @param {Integer} algo Asymmetric cipher algorithm to use (See RFC4880 9.1)
* @param {openpgp_type_mpi[]} publicMPIs Public key multiprecision integers
* of the private key
* @param {openpgp_type_mpi[]} secretMPIs Private key multiprecision
* integers which is used to sign the data
* @param {String} data Data to be signed
* @return {(String|openpgp_type_mpi)}
*/
function openpgp_crypto_signData(hash_algo, algo, publicMPIs, secretMPIs, data) {
switch(algo) {
case 1: // RSA (Encrypt or Sign) [HAC]
case 2: // RSA Encrypt-Only [HAC]
case 3: // RSA Sign-Only [HAC]
var rsa = new RSA();
var d = secretMPIs[0].toBigInteger();
var n = publicMPIs[0].toBigInteger();
var m = openpgp_encoding_emsa_pkcs1_encode(hash_algo, data,publicMPIs[0].mpiByteLength);
util.print_debug("signing using RSA");
return rsa.sign(m, d, n).toMPI();
case 17: // DSA (Digital Signature Algorithm) [FIPS186] [HAC]
var dsa = new DSA();
util.print_debug("DSA Sign: q size in Bytes:"+publicMPIs[1].getByteLength());
var p = publicMPIs[0].toBigInteger();
var q = publicMPIs[1].toBigInteger();
var g = publicMPIs[2].toBigInteger();
var y = publicMPIs[3].toBigInteger();
var x = secretMPIs[0].toBigInteger();
var m = data;
var result = dsa.sign(hash_algo,m, g, p, q, x);
util.print_debug("signing using DSA\n result:"+util.hexstrdump(result[0])+"|"+util.hexstrdump(result[1]));
return result[0]+result[1];
case 16: // Elgamal (Encrypt-Only) [ELGAMAL] [HAC]
util.print_debug("signing with Elgamal is not defined in the OpenPGP standard.");
return null;
default:
return null;
}
}
/**
* Create a hash on the specified data using the specified algorithm
* @param {Integer} algo Hash algorithm type (see RFC4880 9.4)
* @param {String} data Data to be hashed
* @return {String} hash value
*/
function openpgp_crypto_hashData(algo, data) {
var hash = null;
switch(algo) {
case 1: // - MD5 [HAC]
hash = MD5(data);
break;
case 2: // - SHA-1 [FIPS180]
hash = str_sha1(data);
break;
case 3: // - RIPE-MD/160 [HAC]
hash = RMDstring(data);
break;
case 8: // - SHA256 [FIPS180]
hash = str_sha256(data);
break;
case 9: // - SHA384 [FIPS180]
hash = str_sha384(data);
break;
case 10:// - SHA512 [FIPS180]
hash = str_sha512(data);
break;
case 11:// - SHA224 [FIPS180]
hash = str_sha224(data);
default:
break;
}
return hash;
}
/**
* Returns the hash size in bytes of the specified hash algorithm type
* @param {Integer} algo Hash algorithm type (See RFC4880 9.4)
* @return {Integer} Size in bytes of the resulting hash
*/
function openpgp_crypto_getHashByteLength(algo) {
var hash = null;
switch(algo) {
case 1: // - MD5 [HAC]
return 16;
case 2: // - SHA-1 [FIPS180]
case 3: // - RIPE-MD/160 [HAC]
return 20;
case 8: // - SHA256 [FIPS180]
return 32;
case 9: // - SHA384 [FIPS180]
return 48
case 10:// - SHA512 [FIPS180]
return 64;
case 11:// - SHA224 [FIPS180]
return 28;
}
return null;
}
/**
* Retrieve secure random byte string of the specified length
* @param {Integer} length Length in bytes to generate
* @return {String} Random byte string
*/
function openpgp_crypto_getRandomBytes(length) {
var result = '';
for (var i = 0; i &lt; length; i++) {
result += String.fromCharCode(openpgp_crypto_getSecureRandomOctet());
}
return result;
}
/**
* Return a pseudo-random number in the specified range
* @param {Integer} from Min of the random number
* @param {Integer} to Max of the random number (max 32bit)
* @return {Integer} A pseudo random number
*/
function openpgp_crypto_getPseudoRandom(from, to) {
return Math.round(Math.random()*(to-from))+from;
}
/**
* Return a secure random number in the specified range
* @param {Integer} from Min of the random number
* @param {Integer} to Max of the random number (max 32bit)
* @return {Integer} A secure random number
*/
function openpgp_crypto_getSecureRandom(from, to) {
var buf = new Uint32Array(1);
window.crypto.getRandomValues(buf);
var bits = ((to-from)).toString(2).length;
while ((buf[0] & (Math.pow(2, bits) -1)) > (to-from))
window.crypto.getRandomValues(buf);
return from+(Math.abs(buf[0] & (Math.pow(2, bits) -1)));
}
function openpgp_crypto_getSecureRandomOctet() {
var buf = new Uint32Array(1);
window.crypto.getRandomValues(buf);
return buf[0] & 0xFF;
}
/**
* Create a secure random big integer of bits length
* @param {Integer} bits Bit length of the MPI to create
* @return {BigInteger} Resulting big integer
*/
function openpgp_crypto_getRandomBigInteger(bits) {
if (bits &lt; 0)
return null;
var numBytes = Math.floor((bits+7)/8);
var randomBits = openpgp_crypto_getRandomBytes(numBytes);
if (bits % 8 > 0) {
randomBits = String.fromCharCode(
(Math.pow(2,bits % 8)-1) &
randomBits.charCodeAt(0)) +
randomBits.substring(1);
}
return new openpgp_type_mpi().create(randomBits).toBigInteger();
}
function openpgp_crypto_getRandomBigIntegerInRange(min, max) {
if (max.compareTo(min) &lt;= 0)
return;
var range = max.subtract(min);
var r = openpgp_crypto_getRandomBigInteger(range.bitLength());
while (r > range) {
r = openpgp_crypto_getRandomBigInteger(range.bitLength());
}
return min.add(r);
}
//This is a test method to ensure that encryption/decryption with a given 1024bit RSAKey object functions as intended
function openpgp_crypto_testRSA(key){
debugger;
var rsa = new RSA();
var mpi = new openpgp_type_mpi();
mpi.create(openpgp_encoding_eme_pkcs1_encode('ABABABAB', 128));
var msg = rsa.encrypt(mpi.toBigInteger(),key.ee,key.n);
var result = rsa.decrypt(msg, key.d, key.p, key.q, key.u);
}
/**
* @typedef {Object} openpgp_keypair
* @property {openpgp_packet_keymaterial} privateKey
* @property {openpgp_packet_keymaterial} publicKey
*/
/**
* Calls the necessary crypto functions to generate a keypair.
* Called directly by openpgp.js
* @param {Integer} keyType Follows OpenPGP algorithm convention.
* @param {Integer} numBits Number of bits to make the key to be generated
* @return {openpgp_keypair}
*/
function openpgp_crypto_generateKeyPair(keyType, numBits, passphrase, s2kHash, symmetricEncryptionAlgorithm){
var privKeyPacket;
var publicKeyPacket;
var d = new Date();
d = d.getTime()/1000;
var timePacket = String.fromCharCode(Math.floor(d/0x1000000%0x100)) + String.fromCharCode(Math.floor(d/0x10000%0x100)) + String.fromCharCode(Math.floor(d/0x100%0x100)) + String.fromCharCode(Math.floor(d%0x100));
switch(keyType){
case 1:
var rsa = new RSA();
var key = rsa.generate(numBits,"10001");
privKeyPacket = new openpgp_packet_keymaterial().write_private_key(keyType, key, passphrase, s2kHash, symmetricEncryptionAlgorithm, timePacket);
publicKeyPacket = new openpgp_packet_keymaterial().write_public_key(keyType, key, timePacket);
break;
default:
util.print_error("Unknown keytype "+keyType)
}
return {privateKey: privKeyPacket, publicKey: publicKeyPacket};
}
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Index</a></h2><h3>Classes</h3><ul><li><a href="_openpgp_packet.html">_openpgp_packet</a></li><li><a href="JXG.Util.html">Util</a></li><li><a href="openpgp.html">openpgp</a></li><li><a href="openpgp_config.html">openpgp_config</a></li><li><a href="openpgp_keyring.html">openpgp_keyring</a></li><li><a href="openpgp_msg_message.html">openpgp_msg_message</a></li><li><a href="openpgp_msg_privatekey.html">openpgp_msg_privatekey</a></li><li><a href="openpgp_msg_publickey.html">openpgp_msg_publickey</a></li><li><a href="openpgp_packet_compressed.html">openpgp_packet_compressed</a></li><li><a href="openpgp_packet_encrypteddata.html">openpgp_packet_encrypteddata</a></li><li><a href="openpgp_packet_encryptedintegrityprotecteddata.html">openpgp_packet_encryptedintegrityprotecteddata</a></li><li><a href="openpgp_packet_encryptedsessionkey.html">openpgp_packet_encryptedsessionkey</a></li><li><a href="openpgp_packet_keymaterial.html">openpgp_packet_keymaterial</a></li><li><a href="openpgp_packet_literaldata.html">openpgp_packet_literaldata</a></li><li><a href="openpgp_packet_marker.html">openpgp_packet_marker</a></li><li><a href="openpgp_packet_modificationdetectioncode.html">openpgp_packet_modificationdetectioncode</a></li><li><a href="openpgp_packet_onepasssignature.html">openpgp_packet_onepasssignature</a></li><li><a href="openpgp_packet_signature.html">openpgp_packet_signature</a></li><li><a href="openpgp_packet_userattribute.html">openpgp_packet_userattribute</a></li><li><a href="openpgp_packet_userid.html">openpgp_packet_userid</a></li><li><a href="openpgp_type_keyid.html">openpgp_type_keyid</a></li><li><a href="openpgp_type_mpi.html">openpgp_type_mpi</a></li><li><a href="openpgp_type_s2k.html">openpgp_type_s2k</a></li></ul><h3>Global</h3><ul><li><a href="global.html#bin2str">bin2str</a></li><li><a href="global.html#calc_checksum">calc_checksum</a></li><li><a href="global.html#crc_table">crc_table</a></li><li><a href="global.html#decode_utf8">decode_utf8</a></li><li><a href="global.html#encode_utf8">encode_utf8</a></li><li><a href="global.html#get_hashAlgorithmString">get_hashAlgorithmString</a></li><li><a href="global.html#getCheckSum">getCheckSum</a></li><li><a href="global.html#getPGPMessageType">getPGPMessageType</a></li><li><a href="global.html#hash_headers">hash_headers</a></li><li><a href="global.html#hex2bin">hex2bin</a></li><li><a href="global.html#hexidump">hexidump</a></li><li><a href="global.html#hexstrdump">hexstrdump</a></li><li><a href="global.html#MD5">MD5</a></li><li><a href="global.html#openpgp_cfb_decrypt">openpgp_cfb_decrypt</a></li><li><a href="global.html#openpgp_cfb_encrypt">openpgp_cfb_encrypt</a></li><li><a href="global.html#openpgp_cfb_mdc">openpgp_cfb_mdc</a></li><li><a href="global.html#openpgp_crypto_asymetricDecrypt">openpgp_crypto_asymetricDecrypt</a></li><li><a href="global.html#openpgp_crypto_asymetricEncrypt">openpgp_crypto_asymetricEncrypt</a></li><li><a href="global.html#openpgp_crypto_generateKeyPair">openpgp_crypto_generateKeyPair</a></li><li><a href="global.html#openpgp_crypto_generateSessionKey">openpgp_crypto_generateSessionKey</a></li><li><a href="global.html#openpgp_crypto_getHashByteLength">openpgp_crypto_getHashByteLength</a></li><li><a href="global.html#openpgp_crypto_getPrefixRandom">openpgp_crypto_getPrefixRandom</a></li><li><a href="global.html#openpgp_crypto_getPseudoRandom">openpgp_crypto_getPseudoRandom</a></li><li><a href="global.html#openpgp_crypto_getRandomBigInteger">openpgp_crypto_getRandomBigInteger</a></li><li><a href="global.html#openpgp_crypto_getRandomBytes">openpgp_crypto_getRandomBytes</a></li><li><a href="global.html#openpgp_crypto_getSecureRandom">openpgp_crypto_getSecureRandom</a></li><li><a href="global.html#openpgp_crypto_hashData">openpgp_crypto_hashData</a></li><li><a href="global.html#openpgp_crypto_MDCSystemBytes">openpgp_crypto_MDCSystemBytes</a></li><li><a href="global.html#openpgp_crypto_signData">openpgp_crypto_signData</a></li><li><a href="global.html#openpgp_crypto_symmetricDecrypt">openpgp_crypto_symmetricDecrypt</a></li><li><a href="global.html#openpgp_crypto_symmetricEncrypt">openpgp_crypto_symmetricEncrypt</a></li><li><a href="global.html#openpgp_crypto_verifySignature">openpgp_crypto_verifySignature</a></li><li><a href="global.html#openpgp_encoding_armor">openpgp_encoding_armor</a></li><li><a href="global.html#openpgp_encoding_armor_addheader">openpgp_encoding_armor_addheader</a></li><li><a href="global.html#openpgp_encoding_base64_decode">openpgp_encoding_base64_decode</a></li><li><a href="global.html#openpgp_encoding_base64_encode">openpgp_encoding_base64_encode</a></li><li><a href="global.html#openpgp_encoding_deArmor">openpgp_encoding_deArmor</a></li><li><a href="global.html#openpgp_encoding_eme_pkcs1_decode">openpgp_encoding_eme_pkcs1_decode</a></li><li><a href="global.html#openpgp_encoding_eme_pkcs1_encode">openpgp_encoding_eme_pkcs1_encode</a></li><li><a href="global.html#openpgp_encoding_emsa_pkcs1_decode">openpgp_encoding_emsa_pkcs1_decode</a></li><li><a href="global.html#openpgp_encoding_emsa_pkcs1_encode">openpgp_encoding_emsa_pkcs1_encode</a></li><li><a href="global.html#openpgp_encoding_html_encode">openpgp_encoding_html_encode</a></li><li><a href="global.html#print_debug">print_debug</a></li><li><a href="global.html#print_debug_hexstr_dump">print_debug_hexstr_dump</a></li><li><a href="global.html#print_error">print_error</a></li><li><a href="global.html#print_info">print_info</a></li><li><a href="global.html#shiftRight">shiftRight</a></li><li><a href="global.html#str2bin">str2bin</a></li><li><a href="global.html#str2Uint8Array">str2Uint8Array</a></li><li><a href="global.html#Uint8Array2str">Uint8Array2str</a></li><li><a href="global.html#util">util</a></li><li><a href="global.html#verifyCheckSum">verifyCheckSum</a></li></ul>
</nav>
<br clear="both">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.2.0-dev</a> on Tue Apr 16 2013 10:42:39 GMT+0200 (CEST)
</footer>
<script> prettyPrint(); </script>
</body>
</html>