      1 //////////////////////////////////////////////////////////////////////////////////
      2 //
      3 // ciform.js
      4 //
      5 // Copyright © 2008 Nicolas BONARDELLE <http://nicobo.net/contact>
      6 //
      7
      8 (function( $defined, Crypto, console ) {
      9
     10
     11 /**
     12  *	@fileoverview
     13  *
     14  *	<p>This library provides specifications and basic functions to add encryption to HTML form elements.</p>
     15  *
     16  *
     17  *	<p>Home : <a href="http://ciform.googlecode.com">http://ciform.googlecode.com</a></p>
     18  *
     19  *	<p>It does not contain cryptographic functions ; it's more a 'user-friendly' wrapper
     20  *	around cryptographic functions, dedicated to securing web forms.
     21  *	</p>
     22  *
     23  *	<p>It has 2 layers :<ol>
     24  *		<li>the {@link Ciform.ciphers} namespace contains the (external) encoders used by Ciform.
     25  *		 	This is the layer that really deals with the cryptographic functions.<br>
     26  *		<li>the {@link Ciform} top, 'user' layer contains classes dedicated to encryption of the data
     27  *			being exchanged between the client and the server.<br>
     28  *		</ol>
     29  *	</p>
     30  *
     31  *	<h5><b>Example 1 : encrypting password fields</b></h5>
     32  *
     33  *	<p>This way you define a simple encryption on all password fields of a form, that will take place when it is submitted.</p>
     34  *	<code><pre>
     35  * <HEAD>
     36  * ...
     37  * <SCRIPT type="text/javascript">
     38  *	// pubKey is defined elsewhere : it is the public key used by the default RSA encoder
     39  *	var mycipher = new {@link Ciform.Cipher}({'pubKey':pubkey});
     40  * </SCRIPT>
     41  * ...
     42  * </HEAD>
     43  * ...
     44  * <FORM action="http://myserver/mypage.php" onsubmit="javascript:mycipher.encryptForm(this,{'allowTypes':"password"});">
     45  * ...
     46  *	</pre></code>
     47  *
     48  *	<h5><b>Example 2 : different types of encryption within the same page</b></h5>
     49  *
     50  *	<p>You can use several {@link Ciform.Cipher} objects at the same time.</p>
     51  *	<code><pre>
     52  * var toServer1 = new {@link Ciform.Cipher}({'pubKey':pubkey1});
     53  * // in the following line the server is not the same one, so the public key is different
     54  * var toServer2 = new {@link Ciform.Cipher}({'pubKey':pubKey2});
     55  * ...
     56  * toServer1.encryptForm(document.forms[1]);
     57  * ...
     58  * toServer2.encryptField($('#country'));
     59  *	</pre></code>
     60  *	</p>
     61  *
     62  *	<h5><b>Example 3 : using the same object to encrypt different fields</b></h5>
     63  *
     64  *	<code><pre>
     65  * var ciform = new {@link Ciform.Cipher}({'pubKey':pubkey});
     66  * ...
     67  * // encrypts an input field : the encrypted value will replace its current value ; see  {@link Ciform.Cipher#encryptField}
     68  * ciform.encryptField(document.getElementById('myFieldId'));
     69  * ...
     70  * // encrypts the parameters in a URL (and replaces the original value) : {@link Ciform.Cipher#encryptURL}
     71  * document.getElementById('anAnchor').href = ciform.encryptURL(document.getElementById('anAnchor').href);
     72  *	</pre></code>
     73  *
     74  *	@author <a href="http://nicobo.net/contact">nicobo</a>
     75  */
     76
     77
     78
     79 //
     80 // NAMESPACE : Ciform.*
     81 //
     82
     83
     84
     85 /**
     86 	@namespace Defines a namespace for this project.
     87 		<p>NOTE : The first letter is in upper case mainly to prevent errors like using 'ciform' to name a variable,
     88 		and because it should also be considered as an Object containing the definitions of this library.<br>
     89 		It is not very clean since other namespaces are named with lower case characters, but there's currently no perfect solution in Javascript.</p>
     90
     91 	@requires http://www.hanewin.net/encrypt/rsa/base64.js
     92 	@requires http://www.hanewin.net/encrypt/rsa/hex.js
     93 	@requires http://pajhome.org.uk/crypt/md5/sha1.js
     94 	@requires http://www.hanewin.net/encrypt/rsa/rsa.js
     95 */
     96 Ciform = {};
     97
     98
     99
    100 /**
    101 	@namespace This namespace contains the constants required for the client to communicate with the server.<br>
    102
    103 		<p>Data from the server is served as a literal object indexed with some of those constants.<br>
    104 		e.g. <code>{ 'serverURL':"login.php", 'pubKey':{'type':'rsa', 'e':10000,'pq':24} }</code>.</p>
    105
    106 		<p>The normal behavior is to retrieve the protocol from the server and to compare it to the one of this library,
    107 		in order to know if they're compatible.</p>
    108 */
    109 Ciform.protocol = {};
    110 Ciform.protocol.prototype = new Object();
    111
    112 /**
    113 	Version of the protocol (updated when the protocol changes)
    114 	@type String
    115 */
    116 Ciform.protocol.VERSION = "0";
    117
    118 /**
    119 	Prefix to use for a ciform 'packet' to decode :
    120 	HTTP request parameters beginning with this String will be considered as 'Ciform-encoded' values.
    121 	@type String
    122 */
    123 Ciform.protocol.PACKET_PREFIX = "ciform:";
    124
    125
    126
    127 //
    128 // CLASS Field
    129 //
    130
    131
    132
    133 /**
    134 	@class This technical class represents an {@link HTMLElement} to be encrypted and its options.
    135
    136 	<p>Actually, it designates where to read the value from,
    137 	possibly where to write the encoded value into,
    138 	and some options / properties.</p>
    139
    140 	@param {HTMLFormElement} target
    141 		The element to encrypt.<br>
    142 		As an alternative, this parameter can be ignored if the input field is given in the options.
    143 	@param {Object} options (optional)
    144 		Overrides the properties of this object.
    145 	@constructor
    146 */
    147 Ciform.Field = function( target, options )
    148 {
    149 	// allows us to refer to 'options' even when only 1 argument was given
    150 	if ( !$defined(options) ) {
    151 		options = target;
    152 	}
    153
    154 	//
    155 	// PUBLIC PART
    156 	//
    157
    158 	/**
    159 		Input field : where to read the text to encode.
    160 		Must have a readable <em>value</em> property.
    161 		@type HTMLElement
    162 	*/
    163 	this.input = target instanceof HTMLElement ? target : options['input'];
    164
    165 	/**
    166 		Output field : the field where to write the encoded text into.
    167 		Must have a writeable <em>value</em> property.
    168 		Defaults to {@link #in}.
    169 		@type HTMLElement
    170 	*/
    171 	this.output = $defined(options['output']) ? options['output'] : this['input'];
    172
    173 	/**
    174 		<ul>
    175 		<li>If == 1, indicates that this field should be SHA1-encoded before encryption.
    176 		<li>If == 2, meta-data will also be prepended to the result.
    177 		</ul>
    178 		DEFAULT : 0 (do not use SHA-1 at all)
    179 		@type Integer
    180 	*/
    181 	this.sha1 = $defined(options['sha1']) ? options['sha1'] : 0;
    182
    183 	//
    184 	// PRIVATE PART
    185 	//
    186
    187 	/**
    188 		@private
    189 		Takes the value of the encrypted text on encryption.
    190 	*/
    191 	this._ciphertext = null;
    192
    193 	/**
    194 		@private
    195 		Applies the encryption value stored in {@link #_ciphertext} to the output field
    196 		and clears the input field so it is not transmitted in clear if it's different from the output one.
    197 	*/
    198 	this._commit = function()
    199 	{
    200 		// 1. clears the input value
    201 		// FIXME ? must respect character set and length of the original field
    202 		this['input'].value = "";
    203
    204 		// 2. applies the encrypted value to the output field
    205 		this['output'].value = this._ciphertext;
    206 	};
    207 };
    208 Ciform.Field.prototype = new Object();
    209
    210
    211
    212 //
    213 // NAMESPACE : Ciform.ciphers.*
    214 //
    215
    216
    217
    218 /**
    219 	@namespace This namespace contains a wrapper class for each supported encryption method :
    220 		it's a way to normalize encryption and to provide the upper layer an homogenous API.
    221 		This is the layer that really deals with the cryptographic functions.<br>
    222
    223 		<p>One goal of that layer is to provide a set of functions for encrypting data
    224 		without requiring to know the internals of the choosen cipher.</p>
    225
    226 		<p>All encoders follow the {@link Ciform.ciphers.Encoder} interface.</p>
    227 */
    228 Ciform.ciphers = {};
    229
    230
    231
    232 /**
    233 	@class An encoder provides a way to encode/encrypt a message.
    234 		Implementations of an encoder must re-define all of the methods of this class.
    235 */
    236 Ciform.ciphers.Encoder = function(){
    237 };
    238
    239
    240
    241 /**
    242 	The basic way to encode/encrypt a message.
    243
    244 	@param {String} message	The text to encrypt
    245 	@return the encrypted message (ciphertext) : the result depends on the encoder
    246 	@type String
    247 */
    248 Ciform.ciphers.Encoder.prototype.encode = function( message )
    249 {
    250 	return message;
    251 };
    252
    253
    254
    255 //
    256 // SHA-1 ENCODER
    257 //
    258
    259
    260
    261 /**
    262 	@class Encodes a text into its sha1 sum.
    263 	@param {Object} options overrides the fields of this object.
    264 	@constructor
    265 */
    266 Ciform.ciphers.SHA1Encoder = function( options )
    267 {
    268
    269 	/**
    270 		If true, meta-data will be prepended to the result of encryption.
    271 		<p>DEFAULT : false</p>
    272 		@type boolean
    273 	*/
    274 	this.preamble = false;
    275
    276 	this.extend(options);
    277 };
    278 Ciform.ciphers.SHA1Encoder.prototype = new Ciform.ciphers.Encoder();
    279
    280
    281
    282 /**
    283 	@see Ciform.ciphers.Encoder#encode
    284 	@return the sha-1 of the message, base64 encoded, with meta-data about the encoding if {@link #preamble} is true
    285 */
    286 Ciform.ciphers.SHA1Encoder.prototype.encode = function( message )
    287 {
    288 	console.debug(this,"encode(",message,")");
    289 	return (this['preamble'] ? "sha1:b64:" : "") + Crypto.SHA1.b64_sha1(message);
    290 };
    291
    292
    293
    294 //
    295 // RSA ENCODER
    296 //
    297
    298
    299
    300 /**
    301 	@class Formal description of a public RSA key.
    302 	@constructor
    303 */
    304 Ciform.ciphers.RSAPublicKey = function()
    305 {
    306 	// NOTE : this code is a formal description ; it is not functional. It is, however, syntaxically correct.
    307
    308 	/** Type of the key : must be "rsa".
    309 	@type String */
    310 	this.type = "rsa";
    311 	/** (not used) Size of the key, in bits
    312 	@type Number */
    313 	this.size = Number;
    314 	/** Public exponent as an array of 28 bits integers
    315 	@type Array(Number) */
    316 	this.e = Array(Number);
    317 	/** (not used) Prime factor p, as an array of 28 bits integers
    318 	@type Array(Number) */
    319 	this.p = Array(Number);
    320 	/** (not used) Prime factor q, as an array of 28 bits integers
    321 	@type Array(Number) */
    322 	this.q = Array(Number);
    323 	/** Modulus, as an array of 28 bits integers
    324 	@type Array(Number) */
    325 	this.pq = Array(Number);
    326 	/** (not used) e + modulus, encoded into a base64 <b>M</b>ulti-<b>P</b>recision <b>I</b>nteger string
    327 	@type Array(Number) */
    328 	this.mpi = Array(Number);
    329 };
    330
    331
    332
    333 /**
    334 	@class This encoder can encrypt a message given a public key.
    335 		The ciphertext may be decrypted only with the corresponding private key.
    336 		@see http://en.wikipedia.org/wiki/RSA.
    337 	@param {Object} options	Overrides the fields of this object. Must contain at least 'pubKey'.
    338 	@throws TypeError if the public key was not given or is not correct
    339 	@constructor
    340 */
    341 Ciform.ciphers.RSAEncoder = function( options )
    342 {
    343 	/**
    344 		<p>The public key used for asymmetric encryption.</p>
    345 		<p>REQUIRED (no default value)</p>
    346 		@type Ciform.ciphers.RSAPublicKey
    347 	*/
    348 	this.pubKey = null;
    349
    350 	/**
    351 		<p>If true, meta-data will be prepended to the result of encryption.</p>
    352 		<p>DEFAULT : false : don't add meta-data in the beginning of the ciphertext.</p>
    353 		@type boolean
    354 	*/
    355 	this.preamble = false;
    356
    357 	/**
    358 		<p>If true, a random string will be prepended to the text before encryption,
    359 		in order to make the ciphertext different every time, even with the same original text.<br>
    360 		E.g. "1234:my message" : "1234:" is the salt</p>
    361 		<p>DEFAULT : false : don't add salt in the beginning of the ciphertext</p>
    362 		<p><b>WARNING</b> : without salt, the same message encoded with the same key will always give the same ciphertext
    363 			(this could be a security issue).</p>
    364 		@type boolean
    365 	*/
    366 	this.salt = false;
    367
    368 	/**
    369 		<p>If true, does not check that the padding scheme is correct (does not apply if salt is added).</p>
    370 		<p>DEFAULT : false : do check that the padding scheme is correct (does not apply if salt is added).</p>
    371 		@type boolean
    372 	*/
    373 	this.noPadding = false;
    374
    375 	// adds the known options directly to this object
    376 	//this.extend( merge( {'preamble':false,'salt':false,'nopadding':false}, options ) );
    377 	this.extend(options);
    378
    379 	if ( !$defined(this.pubKey) ) {
    380 		throw new TypeError("The public key is required !");
    381 	}
    382
    383 	if ( this.pubKey['type'] != "rsa" ) {
    384 		throw new TypeError("Type of public key must be 'rsa'");
    385 	}
    386
    387 	if ( !this.pubKey['pq'] || !this.pubKey['e'] ) {
    388 		throw new TypeError("Public key is missing a field : both 'pq' and 'e' are required");
    389 	}
    390 };
    391 Ciform.ciphers.RSAEncoder.prototype = new Ciform.ciphers.Encoder();
    392
    393
    394 /** @final @type Integer */
    395 Ciform.ciphers.RSAEncoder.prototype.SALT_MAX = 9999;
    396 /** @final @type Integer */
    397 Ciform.ciphers.RSAEncoder.prototype.SALT_MIN = 1;
    398
    399
    400 /**
    401 	@private
    402 	@type Array[Number]
    403 */
    404 Ciform.ciphers.RSAEncoder.prototype._getMPI = function()
    405 {
    406 	// this function can be called several times so we don't compute the following each time
    407 	if ( ! this.pubKey['mpi'] )
    408 	{
    409 		this.pubKey['mpi'] = Crypto.Base64.s2r(Crypto.RSA.b2mpi(this.pubKey['pq'])+Crypto.RSA.b2mpi([this.pubKey['e']])).replace(/\n/,'');
    410 	}
    411
    412 	return this.pubKey['mpi'];
    413 };
    414
    415
    416
    417 /**
    418 	@private
    419 	@return a random number between this.SALT_MIN and this.SALT_MAX
    420 	@type Number
    421 */
    422 Ciform.ciphers.RSAEncoder.prototype._getSalt = function()
    423 {
    424 	return Math.floor(Math.random() * (this.SALT_MAX - this.SALT_MIN + 1) + this.SALT_MIN);
    425 };
    426
    427
    428
    429 /**
    430 	Computes the maximum length the message should be to prevent attacks against RSA without padding.
    431 	@see http://en.wikipedia.org/wiki/RSA#Padding_schemes.
    432
    433 	@return the max. length for a message to be encoded.
    434 		In case salt is added to the ciphertext, the real max. length might be longer,
    435 		because the salt has a variable length
    436 	@type Number
    437 	@see Ciform.ciphers.RSAEncoder#_getSalt
    438 */
    439 Ciform.ciphers.RSAEncoder.prototype.maxLength = function()
    440 {
    441 	var s = Crypto.Base64.r2s(this._getMPI());
    442 	var l = Math.floor((s.charCodeAt(0)*256 + s.charCodeAt(1)+7)/8);
    443
    444 	var lmax = l - 4;
    445
    446 	if ( this.salt )
    447 	{
    448 		lmax -= new Number(this.SALT_MAX).toString().length;
    449 	}
    450
    451 	console.debug(this,".maxLength()=",lmax);
    452 	return lmax;
    453 };
    454
    455
    456
    457 /**
    458 	@see Ciform.ciphers.Encoder#encode
    459 	@return the ciphertext of the message, encoded with the public RSA key of this encoder, with meta-data about the encoding if this.options['preamble'] is true
    460 	@throws RangeError if the message is too long to be secure for the current public key (ignored if either 'salt' or 'nopadding' is true)
    461 */
    462 Ciform.ciphers.RSAEncoder.prototype.encode = function( message )
    463 {
    464 	console.debug(this,"encode(",message,")");
    465
    466 	var mod=new Array();
    467 	var exp=new Array();
    468
    469 	//var s = r2s(this._getMPI());
    470 	//var l = Math.floor((s.charCodeAt(0)*256 + s.charCodeAt(1)+7)/8);
    471
    472 	//mod = mpi2b(s.substr(0,l+2));
    473 	//exp = mpi2b(s.substr(l+2));
    474
    475 	mod = this.pubKey['pq'];
    476 	exp = this.pubKey['e'];
    477
    478 	var saltMessage = message;
    479 	if ( this.salt )
    480 	{
    481 		// some salt to randomize the string
    482 		var salt = this._getSalt();
    483 		console.debug("salt="+salt);
    484 		saltMessage = "salt" + salt + ":" + message;
    485 	}
    486
    487 	var p = saltMessage+String.fromCharCode(1);
    488
    489 	var maxLength = this.maxLength();
    490 	if ( !this.noPadding && !this.salt && p.length > maxLength )
    491 	{
    492 	   throw new RangeError("Plain text length must be less than "+maxLength+" characters");
    493 	}
    494
    495 	var b = Crypto.Hex.s2b(p);
    496
    497 	// rsa-encrypts the result and converts into mpi
    498 	var ciphertext = Crypto.RSA.encrypt(b,exp,mod);
    499
    500 	return (this.preamble ? "rsa:0x" : "") + Crypto.Hex.s2hex(Crypto.Hex.b2s(ciphertext));
    501 };
    502
    503
    504
    505 //
    506 // CHAIN ENCODER
    507 //
    508
    509
    510
    511 /**
    512 	@class This encoder simply combine encoders into a chain.
    513 		For instance, the message will be first hashed through SHA-1, and then encrypted with a RSA key.
    514 	@param {Array[Ciform.ciphers.Encoder]} encoders	A list with the instances of encoders to use (The chain starts with index 0)
    515 */
    516 Ciform.ciphers.ChainEncoder = function( encoders )
    517 {
    518 	/** @private */
    519 	this.encoders = encoders;
    520 };
    521 Ciform.ciphers.ChainEncoder.prototype = new Ciform.ciphers.Encoder();
    522
    523
    524
    525 Ciform.ciphers.ChainEncoder.prototype.encode = function( message )
    526 {
    527 	var ciphertext = message;
    528
    529 	for ( var e=0 ; e<this.encoders.length ; e++ )
    530 	{
    531 		ciphertext = this.encoders[e].encode(ciphertext);
    532 	}
    533
    534 	return ciphertext;
    535 };
    536
    537
    538
    539 //
    540 // CIFORM 'PACKETIZER'
    541 //
    542
    543
    544
    545 /**
    546 	@class This encoder makes sure the ciphertext will be a ciform packet.
    547 	@see Ciform.protocol
    548 */
    549 Ciform.ciphers.CiformPacketizer = function() {};
    550 Ciform.ciphers.CiformPacketizer.prototype = new Ciform.ciphers.Encoder();
    551
    552
    553
    554 /**
    555 	Adds {@link Ciform.protocol#PACKET_PREFIX} to the message, if necessary.
    556 */
    557 Ciform.ciphers.CiformPacketizer.prototype.encode = function( message )
    558 {
    559 	// only if it doesn't start with the prefix already
    560 	return new RegExp("^"+Ciform.protocol.PACKET_PREFIX+"(.*)").test(message) ? message : Ciform.protocol.PACKET_PREFIX + message;
    561 };
    562
    563
    564
    565 //
    566 // CLASS Cipher
    567 //
    568
    569
    570
    571 /**
    572 	@class This class provides a simple way to encrypt different kind of elements (text, form fields, URL).<br>
    573 		It has first to be built with options to define the encryption ;
    574 		then one of its method must be called on the element to encrypt.
    575 	@constructor
    576 	@param {Object} options (optional) (and next arguments)
    577 		Overrides the default properties and functions of this object.<br>
    578 		For instance, provide <code>{'encoder':myencoder,'onerror':function(e,context){dosomething()}}</code>
    579 		to use a custom encoder and a custom error handler.
    580 */
    581 Ciform.Cipher = function( options )
    582 {
    583 	console.debug("new Ciform.Cipher(",options,")");
    584
    585 	//
    586 	// PROPERTIES DEFINITION
    587 	//
    588
    589 	/**
    590 		<p>The encoder to use on each of the {@link #targets}</p>
    591 		<p>Default : a {@link Ciform.ciphers.RSAEncoder} inside a {@link Ciform.ciphers.CiformPacketizer} <b>if at least the <code>pubKey</code> property is provided in the options</b></p>
    592 		@type Ciform.ciphers.Encoder
    593 	*/
    594 	this.encoder = null;
    595
    596
    597 	//
    598 	// FUNCTIONS DEFINITION
    599 	//
    600
    601 	/**
    602 		A custom error handler that will be called with the error as an argument during encryption operations.<br>
    603
    604 		<p>Default function will log the error and pass it to the upper error handler.</p>
    605
    606 		@param {Error} e the error that happened
    607 		@param {Object} context any information about the context, in the form of an object with properties
    608 		@type function
    609 	*/
    610 	this.onerror = function(e,context)
    611 	{
    612 		console.error(context);
    613 		throw e;
    614 	};
    615
    616
    617
    618
    619 	/**
    620 		This function is called just before the encryption of an object starts.<br>
    621
    622 		<p>This default implementation does nothing.
    623 		It should be overridden to match specific needs.</p>
    624
    625 		@param target	The object that's going to be encrypted
    626 		@param options	(optional) The parameters of the current context, if any
    627 	*/
    628 	this.onEncryptionStart = function( target, options )
    629 	{
    630 		console.debug(this,".onEncryptionStart(",target,options,")",new Date());
    631 	};
    632
    633
    634
    635 	/**
    636 		This function is called once the encryption of an object has ended.<br>
    637
    638 		<p>This default implementation does nothing.
    639 		It should be overridden to match specific needs.</p>
    640
    641 		@param target	The object that has just been be encrypted
    642 		@param options	(optional) The parameters of the current context, if any
    643 	*/
    644 	this.onEncryptionEnd = function( target, options )
    645 	{
    646 		console.debug(this,".onEncryptionEnd(",target,options,")",new Date());
    647 	};
    648
    649
    650
    651 	//
    652 	// INITIALISATION
    653 	//
    654
    655 	// copies the options into this object
    656 	for ( var a=0 ; a<arguments.length ; a++ ) {
    657 		this.extend(arguments[a]);
    658 	}
    659
    660 	// convenient default encoder provided : RSA
    661 	// NOTE : in the end because it requires the 'pubKey' parameter
    662 	if ( !this['encoder'] && this['pubKey'] )
    663 	{
    664 		this['encoder'] = new Ciform.ciphers.ChainEncoder( [
    665 			new Ciform.ciphers.RSAEncoder( merge({'preamble':true,'salt':true},this) ),
    666 			new Ciform.ciphers.CiformPacketizer()
    667 			]
    668 		);
    669 	}
    670
    671 	if ( !this['encoder'] )
    672 	{
    673 		throw new TypeError("The encoder is required !");
    674 	}
    675 };
    676
    677
    678
    679 /**
    680 	Encrypts a text.<br>
    681
    682 	@param {String} text
    683 		The message to encode (will also be made 'ciform-encoded' using a {@link Ciform.ciphers.CiformPacketizer}).<br>
    684 		As an alternative, this parameter can be ignored if the text is given in the options.
    685 	@param {Object} options (optional)
    686 		An object with the following properties :
    687 		<ul>
    688 		<li><code><{@link String}> text</code> -
    689 			The text to encrypt
    690 		</ul>
    691 	@return null if the text could not be encrypted
    692 	@type String
    693 */
    694 Ciform.Cipher.prototype.encryptText = function( text, options )
    695 {
    696 	console.debug(this,".encryptText(",text,options,")");
    697
    698 	// allows us to refer to 'options' even when only 1 argument was given
    699 	if ( !$defined(options) ) {
    700 		options = text;
    701 	}
    702
    703 	var localOptions = merge(this,options);
    704 	var e = localOptions['encoder'];
    705 	var result = null;
    706
    707 	// makes sure the result is 'ciform-encoded' by adding a CiformPacketizer at the end
    708 	if ( !(e instanceof Ciform.ciphers.ChainEncoder) || !(e.encoders[e.encoders.length-1] instanceof Ciform.ciphers.CiformPacketizer) ) {
    709 		e = new Ciform.ciphers.ChainEncoder([localOptions['encoder'],new Ciform.ciphers.CiformPacketizer()]);
    710 	}
    711
    712 	try
    713 	{
    714 		this.onEncryptionStart(text,localOptions);
    715
    716 		result = e.encode(text); // may throw an exception
    717 	}
    718 	catch ( e )
    719 	{
    720 		this.onerror(e,localOptions);
    721 		result = null;
    722 	}
    723 	finally
    724 	{
    725 		this.onEncryptionEnd(text,localOptions);
    726 	}
    727
    728 	return result;
    729 };
    730
    731
    732
    733 /**
    734 	Encrypts an HTML field.<br>
    735
    736 	@param {HTMLElement} target
    737 		The element to encrypt.<br>
    738 		As an alternative, this parameter can be ignored if the element is given in the options.
    739 	@param {Object} options (optional)
    740 		An object with the following properties :
    741 		As a convenience, this may be the field itself, all default options will then be applied.
    742 		<ul>
    743 		<li><code><{@link Ciform.Field}> field</code> -
    744 			Same as (replaces) <code>target</code>.<br>
    745 		<li><code><{@link boolean}> commit</code> -
    746 			If true, will write ciphertext into the output field.<br>
    747 			If false, will not touch the in and out field, but instead will return the {@link Ciform.Field}
    748 			with the following properties added :
    749 			<ul>
    750 				<li><code><{@link String}> text</code> -
    751 					The value of the <em>input</em> field at the end of the operation.
    752 				<li><code><{@link String}> ciphertext</code> -
    753 					The value of the <em>output</em> field at the end of the operation.
    754 			</ul>
    755 			Defaults to true.<br>
    756 			This parameter is usefull to handle transactional operations on several fields at once.
    757 		</ul>
    758 		As an alternative,<code>sha1</code> or <code>ciform-sha1</code> css classes can be set
    759 		in the <code>class</code> attribute of output fields, in order to set the {@link Ciform.Field#sha1} property to 1 or 2 respectively.
    760 	@return false if there was an error, true or a {@link Ciform.Field} object if not
    761 	@type boolean|Ciform.Field
    762 */
    763 Ciform.Cipher.prototype.encryptField = function( target, options )
    764 {
    765 	console.debug(this,".encryptField(",target,options,")");
    766
    767 	// allows us to refer to 'options' even when only 1 argument was given
    768 	if ( !$defined(options) ) {
    769 		options = target;
    770 	}
    771
    772 	// NOTE : localOptions inherit both options specific to the field and those more generic ('commit')
    773 	var localOptions = merge( {'commit':true}, new Ciform.Field(target), options );
    774 	var text = localOptions['input'].value;
    775 	var nodOut = localOptions['output'];
    776 	var result = false;
    777
    778 	// takes care of the special CSS classes
    779 	if ( /ciform-sha1/.test(nodOut.className) ) {
    780 		localOptions['sha1'] = 2;
    781 	}
    782 	else if ( /sha1/.test(nodOut.className) ) {	// FIXME : a more accurate filter
    783 		localOptions['sha1'] = 1;
    784 	}
    785
    786 	this.onEncryptionStart(target,localOptions);
    787 	try
    788 	{
    789 		// handles a first level of encoding through sha-1 if asked for it
    790 		if ( localOptions['sha1'] )
    791 		{
    792 			text = new Ciform.ciphers.SHA1Encoder( {'preamble':localOptions['sha1']==2} ).encode(text);
    793 		}
    794
    795 		// then a second level with the registered encoder
    796 		var ciphertext = this.encryptText(text,localOptions); // may throw an exception
    797
    798 		if ( ciphertext != null )
    799 		{
    800 			// records the 'output' value for committing
    801 			localOptions._ciphertext = ciphertext;
    802
    803 			if ( localOptions['commit'] )
    804 			{
    805 				localOptions._commit();
    806 				result = true;
    807 			}
    808 			else
    809 			{
    810 				result = localOptions;
    811 			}
    812 		}
    813 		else
    814 		{
    815 			result = false;
    816 		}
    817 	}
    818 	catch ( e )
    819 	{
    820 		this.onerror(e,localOptions);
    821 		result = false;
    822 	}
    823 	finally
    824 	{
    825 		this.onEncryptionEnd(target,localOptions);
    826 	}
    827
    828 	return result;
    829 }
    830
    831
    832
    833 /**
    834 	Tries its best to encrypt all fields in a single atomic operation.<br>
    835
    836 	<p>If an error occurs during the encryption of any field, no field is encrypted.</p>
    837
    838 	@param {Array} fields An array of {@link Ciform.Field}
    839 	@param {Object} options See {@link #encryptField}
    840 	@see #encryptField
    841 */
    842 Ciform.Cipher.prototype.encryptFields = function( fields, options )
    843 {
    844 	console.debug(this,".encryptFields(",fields,options,")");
    845
    846 	var done = [];
    847 	var result = false;
    848
    849 	try
    850 	{
    851 		this.onEncryptionStart(fields,options);
    852
    853 		// 1. stores the result of all operations
    854 		for ( var f=0 ; f<fields.length ; f++ )
    855 		{
    856 			// commit = false so the changes are not written, only stored into 'done'
    857 			done[f] = this.encryptField(fields[f],merge(options,{'commit':false}));
    858 			if ( done[f] == false )
    859 			{
    860 				// returns before replacing any value with the ciphertext
    861 				this.onEncryptionEnd(fields,options);
    862 				return false;
    863 			}
    864 		}
    865
    866 		// 2. replaces the values when we're sure all operations went well
    867 		for ( var f=0 ; f<done.length ; f++ )
    868 		{
    869 			done[f]._commit();
    870 		}
    871 		result = true;
    872 	}
    873 	catch ( e )
    874 	{
    875 		this.onerror(e,options);
    876 		result = false;
    877 	}
    878 	finally
    879 	{
    880 		this.onEncryptionEnd(fields,options);
    881 	}
    882
    883 	return result;
    884 };
    885
    886
    887
    888 /**
    889 	Encrypts fields of a form.<br>
    890
    891 	<p>For instance, the following encrypts input fields of type <em>password</em> in a form :
    892 	<pre>encryptForm(myForm,{'allowTypes':["password"]})</pre></p>
    893
    894 	@param {HTMLFormElement} target
    895 		The form to encrypt.<br>
    896 		As an alternative, this parameter can be ignored if the target is given in the options.
    897 	@param {Object} options (optional)
    898 		An object with the following properties :
    899 		<ul>
    900 		<li><code><{@link HTMLFormElement}> form</code> -
    901 			Same as (replaces) <code>target</code> argument.
    902 		<li><code><{@link Array}> allowTypes</code> -
    903 			If set, this array lists the only 'type' attributes allowed for an <INPUT> tag to be encrypted.
    904 			If not set, all tags are allowed.
    905 		<li><code><{@link Array}> rejectTypes</code> -
    906 			If set, this array lists the 'type' attributes of the <INPUT> tags not to encrypt.
    907 			If not set, all tags are allowed.
    908 		</ul>
    909 	@return false if there was an error, true if not
    910 	@type boolean
    911 */
    912 Ciform.Cipher.prototype.encryptForm = function( target, options )
    913 {
    914 	console.debug(this,".encryptForm(",target,options,")");
    915
    916 	// allows us to refer to 'options' even when only 1 argument was given
    917 	if ( !$defined(options) ) {
    918 		options = target;
    919 	}
    920
    921 	// 1. prepares parameters
    922 	var form = target instanceof HTMLFormElement ? target : options['form'];
    923 	var localOptions = merge( {'form':form}, options );
    924 	var fields = new Array();
    925 	fields.extend(localOptions['form'].elements);	// NOTE : HTMLFormElement.elements is not mutable, so we must create a new array from it
    926 	var result = true;
    927
    928 	try
    929 	{
    930 		this.onEncryptionStart(target,localOptions);
    931
    932 		// 2. filter is applied on the form control tag / type
    933 		// NOTE this is only a convenience so the user is not required to write complex rules
    934 		// to select the fields ; *no* more options like those one should be added.
    935 		var oktags = localOptions['allowTypes'];
    936 		var noktags = localOptions['rejectTypes'];
    937 		for ( var f=0 ; f<fields.length ; f++ )
    938 		{
    939 			var el = fields[f];
    940 			// a first level of protection : only regular fields are allowed
    941 			if ( el.tagName.toUpperCase() == "INPUT"
    942 				&& el.type
    943 				&& !["button","submit","image"].containsValue(el.type,true)
    944 				&& !equals("disabled",el.disabled,true)
    945 				&& !equals("readonly",el.readonly,true)	// TODO : check those controls
    946 				)
    947 			{
    948 				// removes any element that does not match the 'allowTypes' filter
    949 				if ( oktags && !oktags.containsValue(el.type,true) )
    950 				{
    951 					fields.splice(f,1);
    952 				}
    953 				// removes any element that does not match the 'rejectTypes' filter
    954 				else if ( noktags && noktags.containsValue(el.type,true) )
    955 				{
    956 					fields.splice(f,1);
    957 				}
    958 			}
    959 			// this field does not conform to the 'regular' behaviour -> removed
    960 			else
    961 			{
    962 				fields.splice(f,1);
    963 			}
    964 		}
    965
    966 		// 3. encrypts the remaining fields
    967 		result = this.encryptFields( fields, localOptions );
    968 	}
    969 	catch ( e )
    970 	{
    971 		this.onerror(e,localOptions);
    972 		result = false;
    973 	}
    974 	finally
    975 	{
    976 		this.onEncryptionEnd(target,localOptions);
    977 	}
    978
    979 	return result;
    980 };
    981
    982
    983
    984 /**
    985 	Encrypts the parameters in an URL.<br>
    986
    987 	@param {String} url The full URL to encrypt (e.g. <code>"http://plugnauth.sf.net/ciform/demo.php?password=mysecret"</code>)
    988 	@param {Object} options (optional)
    989 		An object with the following properties :
    990 		<ul>
    991 		<li><code><{@link Array}<{@link String}>> fields</code> -
    992 			The list containing the name of the fields that must be encrypted
    993 			(if not present, all detected fields are encrypted)
    994 		</ul>
    995 	@return The same URL, with the fields encrypted.<br>
    996 		E.g. <code>"http://plugnauth.sf.net/ciform/demo.php?password=mysecret"</code> could become <code>"http://plugnauth.sf.net/ciform/demo.php?password=ciform:rsa:0x2137230230234832423723"</code>
    997 	@type String
    998 */
    999 Ciform.Cipher.prototype.encryptURL = function( url, options )
    1000 {
    1001 	console.debug(this,".encryptURL(",url,options,")");
    1002
    1003 	var cipherurl = url;
    1004
    1005 	try
    1006 	{
    1007 		this.onEncryptionStart(url,options);
    1008
    1009 		// splits the URL into [base,query]
    1010 		var query = /([^\?]*)\?(.*)/.exec(url);
    1011 		if ( query && query.length >= 3 )
    1012 		{
    1013 			// reforms the base URL to concatenate the encrypted query to its end later
    1014 			cipherurl = query[1] + "?";
    1015 			// splits the query string into parameters (separated with a '&')
    1016 			var args = query[2].split(/&/);
    1017 			for ( var a=0 ; a<args.length ; a++ )
    1018 			{
    1019 				// splits the current parameter into [key,value]
    1020 				var keyval = /([^=]*)=?(.*)/.exec(args[a]);
    1021 				if ( keyval[1] )
    1022 				{
    1023 					var key = keyval[1];
    1024 					var val = keyval[2];
    1025
    1026 					cipherurl += key;
    1027
    1028 					if ( val )
    1029 					{
    1030 						// to be encrypted, either no field at all must be specified in the options
    1031 						// or this field must be present in the list
    1032 						var encryptMe = !options['fields'] || options['fields'][key];
    1033
    1034 						cipherurl += "=" + (encryptMe ? this.encryptText(val) : val);
    1035 					}
    1036 				}
    1037 				if ( a+1<args.length )
    1038 				{
    1039 					cipherurl += "&";
    1040 				}
    1041 			}
    1042 		}
    1043 	}
    1044 	catch ( e )
    1045 	{
    1046 		this.onerror(e,options);
    1047 	}
    1048 	finally
    1049 	{
    1050 		this.onEncryptionEnd(url,options);
    1051 	}
    1052
    1053 	return cipherurl;
    1054 };
    1055
    1056
    1057
    1058 // /**
    1059 // 	This function tries to automatically determine the target to encrypt given versatile arguments.<br>
    1060 //
    1061 // 	<p>If there is an error during the encryption, none of the field is changed.</p>
    1062 //
    1063 // 	@param target
    1064 // 		Either a text, a form, a form field or an array of fields to encrypt
    1065 // 	@param options	(optional)
    1066 // 		Options to pass to the encoding method
    1067 // 	@see Ciform.Cipher#encryptForm
    1068 // 	@see Ciform.Cipher#encryptField
    1069 // 	@see Ciform.Cipher#encryptFields
    1070 // 	@see Ciform.Cipher#encryptText
    1071 // 	@return The returned value depends on the target (see the definition of the corresponding function)
    1072 // 	@throws TypeError if a target could not be determined
    1073 // */
    1074 // Ciform.Cipher.prototype.encrypt = function( target, options )
    1075 // {
    1076 // 	console.debug(this,".encrypt(",target,options,")");
    1077 //
    1078 // 	if ( target instanceof HTMLFormElement ) {
    1079 // 		console.debug("Target is a form");
    1080 // 		return this.encryptForm(target,options);
    1081 // 	}
    1082 // 	else if ( target instanceof Array ) {
    1083 // 		console.debug("Target is a field array");
    1084 // 		return this.encryptFields(target,options);
    1085 // 	}
    1086 // 	else if ( target instanceof String ) {
    1087 // 		console.debug("Target is a text");
    1088 // 		return this.encryptText(target,options);
    1089 // 	}
    1090 // 	else {
    1091 // 		console.debug("Target should be an input field");
    1092 // 		return this.encryptField(target,options);
    1093 // 	}
    1094 //
    1095 // 	currently never happens because unknown targets are passed to #encryptField
    1096 // 	throw new TypeError("Target "+target+" is not handled");
    1097 // };
    1098
    1099
    1100
    1101 //
    1102 // CLASS Widget (TODO)
    1103 //
    1104
    1105
    1106 // Ciform.Widget = function( options )
    1107 // {
    1108 // 	/** If true, will show an indication of the progress of the encryption (because it can take some time) */
    1109 // 	this.showWait = true;
    1110 //
    1111 // 	Ciform.Cipher.apply(this,arguments);
    1112 // };
    1113 // Ciform.Widget.prototype = new Ciform.Cipher();
    1114 //
    1115 //
    1116 //
    1117 // /**
    1118 // 	<p>Prints a message in the window's status bar.</p>
    1119 // 	@see Ciform.Cipher#onEncryptionStart
    1120 // */
    1121 // Ciform.Widget.prototype.onEncryptionStart = function( target, options )
    1122 // {
    1123 // 	Ciform.Cipher.onEncryptionStart.apply(this,arguments);
    1124 // 	if ( this.showWait ) {
    1125 // 		window.status = "Starting to encode " + target;
    1126 // 	}
    1127 // };
    1128 //
    1129 //
    1130 //
    1131 // /**
    1132 // 	<p>Prints a message in the window's status bar.</p>
    1133 // 	@see Ciform.Cipher#onEncryptionEnd
    1134 // */
    1135 // Ciform.Widget.prototype.onEncryptionEnd = function( target, options )
    1136 // {
    1137 // 	Ciform.Cipher.onEncryptionEnd.apply(this,arguments);
    1138 // 	if ( this.showWait ) {
    1139 // 		window.status = "Finished encoding " + target;
    1140 // 	}
    1141 // };
    1142
    1143
    1144
    1145 //
    1146 // GLOBAL FUNCTIONS
    1147 //
    1148
    1149
    1150
    1151 // /**
    1152 /*			<li>the {@link $ciform} function :<br>
    1153 				It is a shortcut to make the creation of Ciform.Cipher objects less verbose.
    1154 				It returns a {@link Ciform.Cipher} object that can be used as usual.
    1155 			</ul>*/
    1156 // 	<p>This utility function provides a compact way to create a {@link Ciform.Cipher} object.</p>
    1157 //
    1158 // 	<p>All options to the usual arguments of {@link Ciform.Cipher} are supplied in one 'flat' argument.
    1159 // 	This is more compact than the object oriented way, but also less clear...</p>
    1160 //
    1161 // 	@param {Object} options Can contain all fields from {@link Ciform.Cipher}, {@link Ciform.ciphers.RSAEncoder}
    1162 // 	@return a {Ciform.Cipher} object
    1163 //
    1164 // */
    1165 // function $ciform( options )
    1166 // {
    1167 // 	console.debug("$ciform(",options,")");
    1168 //
    1169 // 	/**
    1170 // 		This array contains {@link Ciform.Field} objects : it defines which form control may be encrypted.
    1171 // 		NOTE : this field is <b>replaced</b>, not merged with the current one (if any)
    1172 // 		@type Array
    1173 // 	*/
    1174 // 	this.fields = null;
    1175 //
    1176 //
    1177 //
    1178 // 	options = $defined(options) ? options : {};	// so options is always defined
    1179 //
    1180 // 	// default encoder
    1181 // 	var encoder = options['encoder'];
    1182 // 	if ( !$defined(encoder) && $defined(options['pubKey']) ) {
    1183 // 		encoder = new Ciform.ciphers.RSAEncoder( merge({'preamble':true,'salt':true},options) );
    1184 // 	}
    1185 //
    1186 // 	// 1st case : two arguments defined
    1187 // 	if ( $defined(target) && $defined(options) )
    1188 // 	{
    1189 // 		// a. determines the target
    1190 // 		var $target = $(target);
    1191 // 		var targetOptions = {};
    1192 //
    1193 // 		if ( $target instanceof HTMLFormElement ) {
    1194 // 			console.debug("Target is a form");
    1195 // 			targetOptions = {'form':$target};
    1196 // 		}
    1197 // 		else if ( Ciform.Target.prototype.isCiformInput($target)  ) {
    1198 // 			console.debug("Target is an input field");
    1199 // 			targetOptions = {'fields':[$target]};
    1200 // 		}
    1201 // 		else if ( $target instanceof Array ) {
    1202 // 			console.debug("Target is a field array");
    1203 // 			targetOptions = {'fields':$target};
    1204 // 		}
    1205 //
    1206 // 		// b. merges the options correctly before instanciating the object
    1207 // 		var targ = new Ciform.Target( merge(options,targetOptions) );
    1208 // 		return new Ciform.Cipher( merge(options,{'target':targ}) );
    1209 // 	}
    1210 //
    1211 // 	// 2nd case : only one argument defined
    1212 // 	else if ( $defined(target) )
    1213 // 	{
    1214 // 		options = target;
    1215 // 		return new Ciform.Cipher(options);
    1216 // 	}
    1217 //
    1218 // 	// 3rd and last case is not quite useful
    1219 // 	else
    1220 // 	{
    1221 // 		return new Ciform.Cipher({});
    1222 // 	}
    1223 // }
    1224
    1225 })($defined,Crypto,console);