+ first import

This commit is contained in:
cbonar 2008-03-16 11:56:45 +00:00
parent fcd8b5fc87
commit c8f0515376
4 changed files with 659 additions and 0 deletions

380
ciform/trunk/src/ciform.js Normal file
View file

@ -0,0 +1,380 @@
/**
@fileoverview
This library provides specifications and basic functions to add encryption functionalities to an HTML form,
therefore called "Ciform".
@requires base64.js {@link www.haneWIN.de}
@requires hex.js {@link www.haneWIN.de}
@requires sha1.js {@link www.haneWIN.de}
@requires rsa.js {@link www.haneWIN.de}
*/
// Data from the server is available in a literal object named 'CIFORM'
// e.g. { 'serverURL':"login.php", 'pubKey':{'type':'rsa', 'e':10000,'m':24} }
// TODO : define the Ciform protocol using constants that can change from one server to another
// This protocol would be retrieved from the server (either with a <script src> or using Ajax) so nothing is hard coded
// defines a namespace for this project
ciform = {}; // end of ns:ciform
//
// DEFAULT ENCODER
//
/**
Implementations of an encoder must re-define all the defined method.
@constructor
*/
ciform.Encoder = function()
{
}
/**
The basic way to encode/encrypt a message.
@param {String} message The text to encrypt
@return the encrypted message (ciphertext) : the result depends on the encoder
*/
ciform.Encoder.prototype.encode = function( message )
{
return message;
}
//
// SHA-1 ENCODER
//
/**
@param {Object} options Default values :
- 'preamble' = false (don't add meta-data in the beginning of the ciphertext)
@constructor
*/
ciform.SHA1Encoder = function( options )
{
this.options = options ? options : {'preamble':false};
}
ciform.SHA1Encoder.prototype = new ciform.Encoder();
/**
@see ciform#Encoder#encode
@return the sha-1 of the message, base64 encoded, with meta-data about the encoding if this.options['preamble'] is true
*/
ciform.SHA1Encoder.prototype.encode = function( message )
{
console.debug(this,"encode(",message,")");
return (this.options['preamble'] ? "sha1:b64:" : "") + b64_sha1(message);
};
//
// RSA ENCODER
//
/**
@param {Object} pubKey The public key to use for encryption : a dictionary with the following fields :
- 'type' : must be 'rsa'
- 'pq' : modulo of the RSA key
- 'e' : exponent of the RSA key
- 'mpi' (optional) : the RSA key as a base64 encoded mutli-precision integer
@param {Object} options Default values :
- 'preamble' = false (don't add meta-data in the beginning of the ciphertext)
- 'salt' = false (don't add salt in the beginning of the ciphertext : WARNING : without salt, the same message encoded with the same key will always give the same ciphertext)
- 'checkPadding' = true (check that the padding scheme is correct : does not apply if salt is added)
@throw TypeError if the public key is not correct
@constructor
*/
ciform.RSAEncoder = function( pubKey, options )
{
this.options = options ? options : {'preamble':false,'salt':false,'nopadding':false};
if ( pubKey['type'] == "rsa" )
{
if ( pubKey['pq'] && pubKey['e'] )
{
this.pubKey = pubKey;
}
else
{
throw new TypeError("Public key is missing a field : both 'pq' and 'e' are required");
}
}
else
{
throw new TypeError("Type of public key must be 'rsa'");
}
};
ciform.RSAEncoder.prototype = new ciform.Encoder();
ciform.RSAEncoder.prototype.SALT_MAX = 9999;
ciform.RSAEncoder.prototype.SALT_MIN = 1;
ciform.RSAEncoder.prototype._getMPI = function()
{
// this function can be called several times so we don't compute the following each time
if ( ! this.pubKey['mpi'] )
{
this.pubKey['mpi'] = s2r(b2mpi(this.pubKey['pq'])+b2mpi([this.pubKey['e']])).replace(/\n/,'');
}
return this.pubKey['mpi'];
}
/**
@return a random number between this.SALT_MIN and this.SALT_MAX
*/
ciform.RSAEncoder.prototype._getSalt = function()
{
return Math.floor(Math.random() * (this.SALT_MAX - this.SALT_MIN + 1) + this.SALT_MIN);
}
/**
Computes the maximum length the message should be to prevent attacks against RSA without padding
(http://en.wikipedia.org/wiki/RSA#Padding_schemes)
@return the max. length for a message to be encoded.
In case salt is added to the ciphertext, the real max. length might be longer,
because the salt has a variable length
@see ciform.RSAEncoder#_getSalt
*/
ciform.RSAEncoder.prototype.maxLength = function()
{
var s = r2s(this._getMPI());
var l = Math.floor((s.charCodeAt(0)*256 + s.charCodeAt(1)+7)/8);
var lmax = l - 4;
if ( this.options['salt'] )
{
lmax -= new Number(this.SALT_MAX).toString().length;
}
console.debug(this,".maxLength()=",lmax);
return lmax;
}
/**
@see ciform.Encoder#encode
@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
@throws RangeError if the message is too long to be secure for the current public key (ignored if either 'salt' or 'nopadding' is true)
*/
ciform.RSAEncoder.prototype.encode = function( message )
{
console.debug(this,"encode(",message,")");
var mod=new Array();
var exp=new Array();
//var s = r2s(this._getMPI());
//var l = Math.floor((s.charCodeAt(0)*256 + s.charCodeAt(1)+7)/8);
//mod = mpi2b(s.substr(0,l+2));
//exp = mpi2b(s.substr(l+2));
mod = this.pubKey['pq'];
exp = this.pubKey['e'];
var saltMessage = message;
if ( this.options['salt'] )
{
// some salt to randomize the string
var salt = this._getSalt();
console.debug("salt="+salt);
saltMessage = "salt" + salt + ":" + message;
}
var p = saltMessage+String.fromCharCode(1);
var maxLength = this.maxLength();
if ( !this.options['nopadding'] && !this.options['salt'] && p.length > maxLength )
{
throw new RangeError("Plain text length must be less than "+maxLength+" characters");
}
var b = s2b(p);
// rsa-encrypts the result and converts into mpi
var ciphertext = RSAencrypt(b,exp,mod);
return (this.options['preamble'] ? "rsa:0x" : "") + s2hex(b2s(ciphertext));
};
//
// CHAIN ENCODER
//
/**
This encoder simply combine encoders in a chain.
For instance, the message will be first hashed through SHA-1, and then encrypted with a RSA key.
@param {Array[ciform.Encoder]} encoders A list with the instances of encoders to use (The chain starts with index 0)
*/
ciform.ChainEncoder = function( encoders )
{
this.encoders = encoders;
};
ciform.ChainEncoder.prototype = new ciform.Encoder();
ciform.ChainEncoder.prototype.encode = function( message )
{
var ciphertext = message;
for ( var e=0 ; e<this.encoders.length ; e++ )
{
ciphertext = this.encoders[e].encode(ciphertext);
}
return ciphertext;
}
//
// CIFORM HANDLER
//
ciform.Ciform = function( form, pubKey )
{
this.form = form;
this.pubKey = pubKey;
}
/**
This function should be called when the 'onsubmit' event happens.
Its job is to encrypt 'input' fiels into 'output' fields.
@param {Object} fields An array with the inputs and outputs, like [ {'inputName1':'outputName1'}, {'inputName2':'outputName2'}, ... ].
It can be either the DOM nodes, their id or name (in the latter, the second parameter must be set).
Input and output can be the same : the input value will be replaced with the ciphertext.
@param {HTMLFormElement} form The object containing the fields (optional)
*/
ciform.Ciform.prototype.encryptFields = function( fields, onError )
{
console.debug("encryptFields(",fields,onError,")");
var done = [];
for ( var f=0 ; f< fields.length ; f++ )
{
/*ciform.encryptFields([
{'in':"f1",'out':"f2",'encoding':["hex","rsa","base64","sha1"]},
{'in':"f3"},
"f4"
]);
}*/
// gets the nodes from either id, names or nodes
var kIn = fields[f]['in'] ? fields[f]['in'] : fields[f];
var kOut = fields[f]['out'] ? fields[f]['out'] : kIn;
var nodIn = this.form ? this.form[kIn] : document.getElementById(kIn) ? document.getElementById(kIn) : kIn;
var nodOut = this.form ? this.form[kOut] : document.getElementById(kOut) ? document.getElementById(kOut) : kOut;
//var message = nodIn.value;
var encoders = [];
// takes care of one-level sha-1 encoding for such fields
if ( /sha1/.test(nodOut.className) ) // FIXME : a more accurate filter
{
//message = b64_sha1(nodIn.value);
if ( /ciform-sha1/.test(nodOut.className) )
{
// for this class we add meta-data
// NOTE : message must not contain the ":" separator so it can be extracted later
//message = "salt" + salt + ":b64:sha1:" + message;
encoders.push( new ciform.SHA1Encoder({'preamble':true}) );
}
// fields to be encrypted are replaced with something else
// because they're not supposed to be transmitted in clear
// FIXME ? must respect character set and length of the original field
//input_replacement = "";
else
{
encoders.push( new ciform.SHA1Encoder() );
}
}
encoders.push( new ciform.RSAEncoder(this.pubKey,{'preamble':true,'salt':true}) );
var encoder = new ciform.ChainEncoder(encoders);
// encrypts the output field using the server's public key
//var key = CIFORM[CIFORM_ID_PUBKEY];
//var ciphertext = "ciform:rsa:0x" + ciform_encryptMessage(message,key);
try
{
var ciphertext = "ciform:" + encoder.encode(nodIn.value);
done.push({'field':nodOut,'value':ciphertext});
done.push({'field':nodIn,'value':""});
}
catch ( e )
{
// calls back the error handler if defined
if ( onError )
{
onError.apply(null,[e]);
return false;
}
else
{
throw e;
}
}
/*// if everything went fine, stores the replacement values for later
if ( ciphertext.length && ciphertext.length > 0 )
{
// TODO : lighter formalism : don't need to use formal object here
done.push({'field':nodOut,'value':ciphertext});
if ( input_replacement != null )
{
done.push({'field':input,'value':input_replacement});
}
}
else
{
// could happen if the text's length doesn't fit with the key's length
return false;
}*/
} // iterating over fields is over
// replaces the values of the fiels (in the end, so that nothing is done if there's any error)
for ( var f=0 ; f<done.length ; f++ )
{
done[f]['field'].value = done[f]['value'];
}
return true;
};

129
ciform/trunk/src/ciform.php Normal file
View file

@ -0,0 +1,129 @@
<?php
// constants exportable to included scripts
define("CIFORM_SESSION","CIFORM");
define("CIFORM_SESSION_KEYPAIR","KEYPAIR"); // TODO : move to ciform_rsa.php
if ( ! defined("CIFORM_DEBUG") ) define("CIFORM_DEBUG",FALSE);
require_once("ciform_rsa.php");
// private constants
define("CIFORM_REQUEST_PREFIX","ciform:");
define("CIFORM_KEYTYPE",CIFORM_RSA_KEYTYPE); // choose the desired encryption module here
if ( ! defined("CIFORM_AUTODECRYPT") ) define("CIFORM_AUTODECRYPT", TRUE );
// TODO : embed the key in the data (e.g. ciform:rsa:keyId:0x12345:0xdd33be2b17813b396d63dd1be9c72e9756bbd8ae5d5555b93a7f4b4fd5a8c80d:salt24325234)
function ciform_decode( $data, $keyPair, $base=1 )
{
if ( CIFORM_DEBUG ) echo "ciform_decrypt($data,keyPair,$base)<br>";
// $newData is going to be decoded by one of the following filters
// then, it'll be encoded to the destination base
$newData = $data;
// this flag means this parameter is handled by this library
if ( eregi('^'.CIFORM_REQUEST_PREFIX.'(.*)$',$data,$matches) > 0 )
{
$newData = ciform_decode($matches[1],$keyPair);
}
// this is just salt that adds randomness to the string : it can be removed safely
else if ( eregi('^salt[^:]*:(.*)$',$data,$matches) > 0 )
{
$newData = ciform_decode($matches[1],$keyPair);
}
// this is an hexadecimal string
else if ( eregi('^(hex:|0x)(.*)$',$data,$matches) > 0 )
{
$tmpData = ciform_decode($matches[2],$keyPair);
$newData = pack("H*",$tmpData);
}
// this a base64 encoded string
else if ( eregi('^(base64|b64):(.*)$',$data,$matches) > 0 )
{
$tmpData = ciform_decode($matches[2],$keyPair);
if ( $base == 64 )
{
// we're already in the right radix, don't go further
// (same can be done with other bases too, but right now we only need this one)
return $tmpData;
}
$newData = base64_decode($tmpData);
}
// this is an encrypted message
else if ( eregi('^'.CIFORM_KEYTYPE.':(.*)(:.+)?$',$data,$matches) > 0 )
{
$tmpData = ciform_decode($matches[1],$keyPair,64);
// decrypts the data using the configured module
$func = "ciform_".CIFORM_KEYTYPE."_decrypt";
$newData = ciform_decode( $func($tmpData,$keyPair), $keyPair );
}
// FIXME : do better recursion : each case should return ciform_decode($tmpData) except if no encoding is detected (which then ends the recursion)
// FIXME : put each case in a different 'decoder' class
// encodes the data into the desired base
switch( $base )
{
case 64:
return base64_encode($newData);
default:
return $newData;
}
}
function ciform_decryptParam( $data, $keyPair )
{
if ( gettype($data) == "string" && eregi('^'.CIFORM_REQUEST_PREFIX.'(.*)$',$data,$matches) > 0 )
{
return ciform_decode($matches[1],$keyPair);
}
return $data;
}
function ciform_decryptParams( $request, $keyPair )
{
$decoded = array();
// accepts encrypted data from the client
foreach ( $request as $key => $value )
{
$newValue = ciform_decryptParam($value,$keyPair);
$decoded[$key] = $newValue;
}
return $decoded;
}
// makes sure the key is accessible
// TODO : instanciate the given crypto class,
// which stores itself the key and all other specific data
// then, move the following code to the correct sub-script
if ( !isset($_SESSION[CIFORM_SESSION]) )
{
$_SESSION[CIFORM_SESSION] = array();
}
if ( !isset($_SESSION[CIFORM_SESSION][CIFORM_SESSION_KEYPAIR]) )
{
// the encryption module's name is given by the defined key type
$func = "ciform_".CIFORM_KEYTYPE."_getKeyPair";
$_SESSION[CIFORM_SESSION][CIFORM_SESSION_KEYPAIR] = $func();
}
if ( CIFORM_AUTODECRYPT )
{
$_REQUEST = ciform_decryptParams($_REQUEST,$_SESSION[CIFORM_SESSION][CIFORM_SESSION_KEYPAIR]);
}
?>

View file

@ -0,0 +1,11 @@
<?php
define("CIFORM_KEYTYPE_PGP_RSA","pgp_rsa");
define("CIFORM_KEYTYPE_PGP_ELGAMAL","pgp_elgamal");
function ciform_pgp_rsa_pubKey2Json( $pubKey )
{
return "{'type':'".CIFORM_KEYTYPE_PGP_RSA."', keyId:'".$pubKey->getKeyId()."' 'mpib64Value':'".$pubKey->getMPIBase64()."'};";
}
?>

View file

@ -0,0 +1,139 @@
<?php
require_once("Crypt/RSA.php");
define("CIFORM_RSA_KEYTYPE","rsa");
define("CIFORM_RSA_KEYSIZE",768);
define("CIFORM_RSA_KEYSTORE","keys");
define("CIFORM_RSA_KEYFILE_PEM",CIFORM_RSA_KEYSTORE."/protected/key-rsa.pem");
define("CIFORM_RSA_KEYFILE_JS",CIFORM_RSA_KEYSTORE."/key-rsa.pub.js");
define("CIFORM_RSA_REQUEST_GENKEY","ciform-genkey");
// TODO : include the following functions in a class inheriting from RSA_KeyPair
/**
Transforms a big integer value into a JSON array of 28 bits integers
*/
function ciform_rsa_bigInt2Json( $math, $binValue )
{
$json = "[";
$intValue = $math->bin2int($binValue);
$szBits = $math->bitLen($intValue); // total length, in bits
for ( $b=0 ; $b<$szBits ; )
{
$l = min(28,$szBits-$b);
$json .= $math->subint($intValue, $b, $l);
$b += $l;
if ( $b<$szBits )
{
$json .= ",";
}
}
return $json."]";
}
function ciform_rsa_pubKey2Json( $keyPair )
{
$pubKey = $keyPair->getPublicKey();
$math = $keyPair->_math_obj;
$p = ciform_rsa_bigInt2Json($math,$keyPair->_attrs['p']);
$q = ciform_rsa_bigInt2Json($math,$keyPair->_attrs['q']);
$e = ciform_rsa_bigInt2Json($math,$pubKey->getExponent());
$pq = ciform_rsa_bigInt2Json($math,$pubKey->getModulus());
//$mpi = base64_encode($math->bin2int($pubKey->getModulus())+$math->bin2int($pubKey->getExponent()));
$json = "{"
."'type':'".CIFORM_RSA_KEYTYPE."'"
.","
."'size':".$pubKey->getKeyLength() // size of the key, in bits
.","
."'p':$p" // prime factor p, as an array of 28 bits integers
.","
."'q':$q" // prime factor q, as an array of 28 bits integers
.","
."'e':$e" // public exponent as an array of 28 bits integers
.","
."'pq':$pq" // modulus, as an array of 28 bits integers ; not required (=p*q)
//.","
//."'mpi':'$mpi'" // e + modulus, encoded into a base64 MPI string
."}";
return $json;
}
/**
Uses an existing keypair stored in a file or generates a new one
*/
function ciform_rsa_genKeyPair( $keySize, $pemFilename, $jsFilename, $force=FALSE )
{
// if the key has been stored to a file, get it from there
if ( $contents = @file_get_contents($pemFilename) )
{
return Crypt_RSA_KeyPair::fromPEMString($contents);
}
// else, generate a new key and try to store it to a file
else
{
// generates the key
$keyPair = new Crypt_RSA_KeyPair($keySize);
// stores as PEM
if ( $force || !@file_exists($pemFilename) )
{
@mkdir(dirname($pemFilename),0777,TRUE);
@file_put_contents($pemFilename,$keyPair->toPEMString());
}
// store some Javascript variables, including the public key
// FIXME : if file_put_contents fails, no notification but the file is never written
if ( $force || !@file_exists($jsFilename) )
{
@mkdir(dirname($jsFilename),0777,TRUE);
// FIXME : serverURL must be absolute, so scripts can call it from other servers
$serverURL = $_SERVER['PHP_SELF'];
$pubKey = ciform_rsa_pubKey2Json($keyPair);
$jsContents .= "\nvar CIFORM = {'serverURL':'".str_replace("'","\\'",$serverURL)."', 'pubKey':$pubKey};";
@file_put_contents($jsFilename,$jsContents);
}
// returns the newly created key
return $keyPair;
}
}
function ciform_rsa_getKeyPair()
{
if ( CIFORM_DEBUG ) echo "ciform_rsa_getKeyPair() = ";
$keyPair = ciform_rsa_genKeyPair(CIFORM_RSA_KEYSIZE, CIFORM_RSA_KEYFILE_PEM, CIFORM_RSA_KEYFILE_JS);
if ( CIFORM_DEBUG ) print_r($keyPair);
return $keyPair;
}
function ciform_rsa_decrypt( $data, $keyPair )
{
if ( CIFORM_DEBUG ) echo "ciform_rsa_decrypt($data,keyPair)<br>";
$privateKey = $keyPair->getPrivateKey();
$rsa = new Crypt_RSA($privateKey->getKeyLength(),'BCMath');
return $rsa->decrypt($data,$privateKey);
}
// keypair generation is forced if this parameter is set
if ( isset($_SESSION[CIFORM_RSA_REQUEST_GENKEY]) )
{
$_SESSION[CIFORM_SESSION][CIFORM_SESSION_KEYPAIR] = ciform_rsa_genKeyPair(CIFORM_RSA_KEYSIZE, CIFORM_RSA_KEYFILE_PEM, CIFORM_RSA_KEYFILE_JS, TRUE);
}
?>