commit a8e4b9981fa26bf8af2ebb8f9cf5d12de8ae97db Author: Radomir Dopieralski Date: Tue May 20 15:18:24 2014 +0200 Version 2.0.0 of JSEncrypt library packaged for XStatic diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3085b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.pyc +*.sw? +*.sqlite3 +.DS_STORE +*.egg-info +.venv +.tox +build +dist diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..98da6f1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include README.txt +recursive-include xstatic/pkg/jsencrypt * + +global-exclude *.pyc +global-exclude *.pyo +global-exclude *.orig +global-exclude *.rej + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..c8739d5 --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +XStatic-JSEncrypt +-------------- + +JSEncrypt JavaScript library packaged for setuptools (easy_install) / pip. + +This package is intended to be used by **any** project that needs these files. + +It intentionally does **not** provide any extra code except some metadata +**nor** has any extra requirements. You MAY use some minimal support code from +the XStatic base package, if you like. + +You can find more info about the xstatic packaging way in the package `XStatic`. + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..797b1ee --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +from xstatic.pkg import jsencrypt as xs + +# The README.txt file should be written in reST so that PyPI can use +# it to generate your project's PyPI page. +long_description = open('README.txt').read() + +from setuptools import setup, find_packages + +setup( + name=xs.PACKAGE_NAME, + version=xs.PACKAGE_VERSION, + description=xs.DESCRIPTION, + long_description=long_description, + classifiers=xs.CLASSIFIERS, + keywords=xs.KEYWORDS, + maintainer=xs.MAINTAINER, + maintainer_email=xs.MAINTAINER_EMAIL, + license=xs.LICENSE, + url=xs.HOMEPAGE, + platforms=xs.PLATFORMS, + packages=find_packages(), + namespace_packages=['xstatic', 'xstatic.pkg', ], + include_package_data=True, + zip_safe=False, + install_requires=[], # nothing! :) + # if you like, you MAY use the 'XStatic' package. +) diff --git a/xstatic/__init__.py b/xstatic/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/__init__.py b/xstatic/pkg/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/pkg/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/jsencrypt/__init__.py b/xstatic/pkg/jsencrypt/__init__.py new file mode 100644 index 0000000..3a51eea --- /dev/null +++ b/xstatic/pkg/jsencrypt/__init__.py @@ -0,0 +1,49 @@ +""" +XStatic resource package + +See package 'XStatic' for documentation and basic tools. +""" + +DISPLAY_NAME = 'JSEncrypt' # official name, upper/lowercase allowed, no spaces +PACKAGE_NAME = 'XStatic-%s' % DISPLAY_NAME # name used for PyPi + +NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar') + # please use a all-lowercase valid python + # package name + +VERSION = '2.0.0' # version of the packaged files, please use the upstream + # version number +BUILD = '1' # our package build number, so we can release new builds + # with fixes for xstatic stuff. +PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi + +DESCRIPTION = "%s %s (XStatic packaging standard)" % (DISPLAY_NAME, VERSION) + +PLATFORMS = 'any' +CLASSIFIERS = [] +KEYWORDS = '%s xstatic' % NAME + +# XStatic-* package maintainer: +MAINTAINER = 'Radomir Dopieralski' +MAINTAINER_EMAIL = 'openstack@sheep.art.pl' + +# this refers to the project homepage of the stuff we packaged: +HOMEPAGE = 'http://travistidwell.com/jsencrypt/' + +# this refers to all files: +LICENSE = '(same as %s)' % DISPLAY_NAME + +from os.path import join, dirname +BASE_DIR = join(dirname(__file__), 'data') +# linux package maintainers just can point to their file locations like this: +#BASE_DIR = '/usr/share/javascript/jsencrypt' + +LOCATIONS = { + # CDN locations (if no public CDN exists, use an empty dict) + # if value is a string, it is a base location, just append relative + # path/filename. if value is a dict, do another lookup using the + # relative path/filename you want. + # your relative path/filenames should usually be without version + # information, because either the base dir/url is exactly for this + # version or the mapping will care for accessing this version. +} diff --git a/xstatic/pkg/jsencrypt/data/jsencrypt.js b/xstatic/pkg/jsencrypt/data/jsencrypt.js new file mode 100644 index 0000000..a98f59c --- /dev/null +++ b/xstatic/pkg/jsencrypt/data/jsencrypt.js @@ -0,0 +1,478 @@ +/** + * Retrieve the hexadecimal value (as a string) of the current ASN.1 element + * @returns {string} + * @public + */ +ASN1.prototype.getHexStringValue = function(){ + var hexString = this.toHexString(); + var offset = this.header * 2; + var length = this.length * 2; + return hexString.substr(offset,length); +}; + +/** + * Method to parse a pem encoded string containing both a public or private key. + * The method will translate the pem encoded string in a der encoded string and + * will parse private key and public key parameters. This method accepts public key + * in the rsaencryption pkcs #1 format (oid: 1.2.840.113549.1.1.1). + * @todo Check how many rsa formats use the same format of pkcs #1. The format is defined as: + * PublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * PublicKey BIT STRING + * } + * Where AlgorithmIdentifier is: + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm + * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) + * } + * and PublicKey is a SEQUENCE encapsulated in a BIT STRING + * RSAPublicKey ::= SEQUENCE { + * modulus INTEGER, -- n + * publicExponent INTEGER -- e + * } + * it's possible to examine the structure of the keys obtained from openssl using + * an asn.1 dumper as the one used here to parse the components: http://lapo.it/asn1js/ + * @argument {string} pem the pem encoded string, can include the BEGIN/END header/footer + * @private + */ +RSAKey.prototype.parseKey = function(pem) { + try{ + var reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/; + var der = reHex.test(pem) ? Hex.decode(pem) : Base64.unarmor(pem); + var asn1 = ASN1.decode(der); + if (asn1.sub.length === 9){ + // the data is a Private key + //in order + //Algorithm version, n, e, d, p, q, dmp1, dmq1, coeff + //Alg version, modulus, public exponent, private exponent, prime 1, prime 2, exponent 1, exponent 2, coefficient + var modulus = asn1.sub[1].getHexStringValue(); //bigint + this.n = parseBigInt(modulus, 16); + + var public_exponent = asn1.sub[2].getHexStringValue(); //int + this.e = parseInt(public_exponent, 16); + + var private_exponent = asn1.sub[3].getHexStringValue(); //bigint + this.d = parseBigInt(private_exponent, 16); + + var prime1 = asn1.sub[4].getHexStringValue(); //bigint + this.p = parseBigInt(prime1, 16); + + var prime2 = asn1.sub[5].getHexStringValue(); //bigint + this.q = parseBigInt(prime2, 16); + + var exponent1 = asn1.sub[6].getHexStringValue(); //bigint + this.dmp1 = parseBigInt(exponent1, 16); + + var exponent2 = asn1.sub[7].getHexStringValue(); //bigint + this.dmq1 = parseBigInt(exponent2, 16); + + var coefficient = asn1.sub[8].getHexStringValue(); //bigint + this.coeff = parseBigInt(coefficient, 16); + + }else if (asn1.sub.length === 2){ + //Public key + //The data PROBABLY is a public key + var bit_string = asn1.sub[1]; + var sequence = bit_string.sub[0]; + + var modulus = sequence.sub[0].getHexStringValue(); + this.n = parseBigInt(modulus, 16); + var public_exponent = sequence.sub[1].getHexStringValue(); + this.e = parseInt(public_exponent, 16); + + }else{ + return false; + } + return true; + }catch(ex){ + return false; + } +}; + +/** + * Translate rsa parameters in a hex encoded string representing the rsa key. + * The translation follow the ASN.1 notation : + * RSAPrivateKey ::= SEQUENCE { + * version Version, + * modulus INTEGER, -- n + * publicExponent INTEGER, -- e + * privateExponent INTEGER, -- d + * prime1 INTEGER, -- p + * prime2 INTEGER, -- q + * exponent1 INTEGER, -- d mod (p1) + * exponent2 INTEGER, -- d mod (q-1) + * coefficient INTEGER, -- (inverse of q) mod p + * } + * @returns {string} DER Encoded String representing the rsa private key + * @private + */ +RSAKey.prototype.getPrivateBaseKey = function() { + //Algorithm version, n, e, d, p, q, dmp1, dmq1, coeff + //Alg version, modulus, public exponent, private exponent, prime 1, prime 2, exponent 1, exponent 2, coefficient + var options = { + 'array' : [ + new KJUR.asn1.DERInteger({'int' : 0}), + new KJUR.asn1.DERInteger({'bigint' : this.n}), + new KJUR.asn1.DERInteger({'int' : this.e}), + new KJUR.asn1.DERInteger({'bigint' : this.d}), + new KJUR.asn1.DERInteger({'bigint' : this.p}), + new KJUR.asn1.DERInteger({'bigint' : this.q}), + new KJUR.asn1.DERInteger({'bigint' : this.dmp1}), + new KJUR.asn1.DERInteger({'bigint' : this.dmq1}), + new KJUR.asn1.DERInteger({'bigint' : this.coeff}) + ] + }; + var seq = new KJUR.asn1.DERSequence(options); + return seq.getEncodedHex(); +}; + +/** + * base64 (pem) encoded version of the DER encoded representation + * @returns {string} pem encoded representation without header and footer + * @public + */ +RSAKey.prototype.getPrivateBaseKeyB64 = function (){ + return hex2b64(this.getPrivateBaseKey()); +}; + +/** + * Translate rsa parameters in a hex encoded string representing the rsa public key. + * The representation follow the ASN.1 notation : + * PublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * PublicKey BIT STRING + * } + * Where AlgorithmIdentifier is: + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm + * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) + * } + * and PublicKey is a SEQUENCE encapsulated in a BIT STRING + * RSAPublicKey ::= SEQUENCE { + * modulus INTEGER, -- n + * publicExponent INTEGER -- e + * } + * @returns {string} DER Encoded String representing the rsa public key + * @private + */ +RSAKey.prototype.getPublicBaseKey = function() { + var options = { + 'array' : [ + new KJUR.asn1.DERObjectIdentifier({'oid':'1.2.840.113549.1.1.1'}), //RSA Encryption pkcs #1 oid + new KJUR.asn1.DERNull() + ] + }; + var first_sequence = new KJUR.asn1.DERSequence(options); + + options = { + 'array' : [ + new KJUR.asn1.DERInteger({'bigint' : this.n}), + new KJUR.asn1.DERInteger({'int' : this.e}) + ] + }; + var second_sequence = new KJUR.asn1.DERSequence(options); + + options = { + 'hex' : '00'+second_sequence.getEncodedHex() + }; + var bit_string = new KJUR.asn1.DERBitString(options); + + options = { + 'array' : [ + first_sequence, + bit_string + ] + }; + var seq = new KJUR.asn1.DERSequence(options); + return seq.getEncodedHex(); +}; + +/** + * base64 (pem) encoded version of the DER encoded representation + * @returns {string} pem encoded representation without header and footer + * @public + */ +RSAKey.prototype.getPublicBaseKeyB64 = function (){ + return hex2b64(this.getPublicBaseKey()); +}; + +/** + * wrap the string in block of width chars. The default value for rsa keys is 64 + * characters. + * @param {string} str the pem encoded string without header and footer + * @param {Number} [width=64] - the length the string has to be wrapped at + * @returns {string} + * @private + */ +RSAKey.prototype.wordwrap = function(str, width) { + width = width || 64; + if (!str) + return str; + var regex = '(.{1,' + width + '})( +|$\n?)|(.{1,' + width + '})'; + return str.match(RegExp(regex, 'g')).join('\n'); +}; + +/** + * Retrieve the pem encoded private key + * @returns {string} the pem encoded private key with header/footer + * @public + */ +RSAKey.prototype.getPrivateKey = function() { + var key = "-----BEGIN RSA PRIVATE KEY-----\n"; + key += this.wordwrap(this.getPrivateBaseKeyB64()) + "\n"; + key += "-----END RSA PRIVATE KEY-----"; + return key; +}; + +/** + * Retrieve the pem encoded public key + * @returns {string} the pem encoded public key with header/footer + * @public + */ +RSAKey.prototype.getPublicKey = function() { + var key = "-----BEGIN PUBLIC KEY-----\n"; + key += this.wordwrap(this.getPublicBaseKeyB64()) + "\n"; + key += "-----END PUBLIC KEY-----"; + return key; +}; + +/** + * Check if the object contains the necessary parameters to populate the rsa modulus + * and public exponent parameters. + * @param {Object} [obj={}] - An object that may contain the two public key + * parameters + * @returns {boolean} true if the object contains both the modulus and the public exponent + * properties (n and e) + * @todo check for types of n and e. N should be a parseable bigInt object, E should + * be a parseable integer number + * @private + */ +RSAKey.prototype.hasPublicKeyProperty = function(obj){ + obj = obj || {}; + return obj.hasOwnProperty('n') && + obj.hasOwnProperty('e'); +}; + +/** + * Check if the object contains ALL the parameters of an RSA key. + * @param {Object} [obj={}] - An object that may contain nine rsa key + * parameters + * @returns {boolean} true if the object contains all the parameters needed + * @todo check for types of the parameters all the parameters but the public exponent + * should be parseable bigint objects, the public exponent should be a parseable integer number + * @private + */ +RSAKey.prototype.hasPrivateKeyProperty = function(obj){ + obj = obj || {}; + return obj.hasOwnProperty('n') && + obj.hasOwnProperty('e') && + obj.hasOwnProperty('d') && + obj.hasOwnProperty('p') && + obj.hasOwnProperty('q') && + obj.hasOwnProperty('dmp1') && + obj.hasOwnProperty('dmq1') && + obj.hasOwnProperty('coeff'); +}; + +/** + * Parse the properties of obj in the current rsa object. Obj should AT LEAST + * include the modulus and public exponent (n, e) parameters. + * @param {Object} obj - the object containing rsa parameters + * @private + */ +RSAKey.prototype.parsePropertiesFrom = function(obj){ + this.n = obj.n; + this.e = obj.e; + + if (obj.hasOwnProperty('d')){ + this.d = obj.d; + this.p = obj.p; + this.q = obj.q; + this.dmp1 = obj.dmp1; + this.dmq1 = obj.dmq1; + this.coeff = obj.coeff; + } +}; + +/** + * Create a new JSEncryptRSAKey that extends Tom Wu's RSA key object. + * This object is just a decorator for parsing the key parameter + * @param {string|Object} key - The key in string format, or an object containing + * the parameters needed to build a RSAKey object. + * @constructor + */ +var JSEncryptRSAKey = function(key) { + // Call the super constructor. + RSAKey.call(this); + // If a key key was provided. + if (key) { + // If this is a string... + if (typeof key === 'string') { + this.parseKey(key); + }else if (this.hasPrivateKeyProperty(key)||this.hasPublicKeyProperty(key)) { + // Set the values for the key. + this.parsePropertiesFrom(key); + } + } +}; + +// Derive from RSAKey. +JSEncryptRSAKey.prototype = new RSAKey(); + +// Reset the contructor. +JSEncryptRSAKey.prototype.constructor = JSEncryptRSAKey; + + +/** + * + * @param {Object} [options = {}] - An object to customize JSEncrypt behaviour + * possible parameters are: + * - default_key_size {number} default: 1024 the key size in bit + * - default_public_exponent {string} default: '010001' the hexadecimal representation of the public exponent + * - log {boolean} default: false whether log warn/error or not + * @constructor + */ +var JSEncrypt = function(options) { + options = options || {}; + this.default_key_size = parseInt(options.default_key_size) || 1024; + this.default_public_exponent = options.default_public_exponent || '010001'; //65537 default openssl public exponent for rsa key type + this.log = options.log || false; + // The private and public key. + this.key = null; +}; + +/** + * Method to set the rsa key parameter (one method is enough to set both the public + * and the private key, since the private key contains the public key paramenters) + * Log a warning if logs are enabled + * @param {Object|string} key the pem encoded string or an object (with or without header/footer) + * @public + */ +JSEncrypt.prototype.setKey = function(key){ + if (this.log && this.key) + console.warn('A key was already set, overriding existing.'); + this.key = new JSEncryptRSAKey(key); +}; + +/** + * Proxy method for setKey, for api compatibility + * @see setKey + * @public + */ +JSEncrypt.prototype.setPrivateKey = function(privkey) { + // Create the key. + this.setKey(privkey); +}; + +/** + * Proxy method for setKey, for api compatibility + * @see setKey + * @public + */ +JSEncrypt.prototype.setPublicKey = function(pubkey) { + // Sets the public key. + this.setKey(pubkey); +}; + +/** + * Proxy method for RSAKey object's decrypt, decrypt the string using the private + * components of the rsa key object. Note that if the object was not set will be created + * on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor + * @param {string} string base64 encoded crypted string to decrypt + * @return {string} the decrypted string + * @public + */ +JSEncrypt.prototype.decrypt = function(string) { + // Return the decrypted string. + try{ + return this.getKey().decrypt(b64tohex(string)); + }catch(ex){ + return false; + } +}; + +/** + * Proxy method for RSAKey object's encrypt, encrypt the string using the public + * components of the rsa key object. Note that if the object was not set will be created + * on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor + * @param {string} string the string to encrypt + * @return {string} the encrypted string encoded in base64 + * @public + */ +JSEncrypt.prototype.encrypt = function(string) { + // Return the encrypted string. + try{ + return hex2b64(this.getKey().encrypt(string)); + }catch(ex){ + return false; + } +}; + +/** + * Getter for the current JSEncryptRSAKey object. If it doesn't exists a new object + * will be created and returned + * @param {callback} [cb] the callback to be called if we want the key to be generated + * in an async fashion + * @returns {JSEncryptRSAKey} the JSEncryptRSAKey object + * @public + */ +JSEncrypt.prototype.getKey = function(cb){ + // Only create new if it does not exist. + if (!this.key) { + // Get a new private key. + this.key = new JSEncryptRSAKey(); + if (cb && {}.toString.call(cb) === '[object Function]'){ + this.key.generateAsync(this.default_key_size, this.default_public_exponent,cb); + return; + } + // Generate the key. + this.key.generate(this.default_key_size, this.default_public_exponent); + } + return this.key; +}; + +/** + * Returns the pem encoded representation of the private key + * If the key doesn't exists a new key will be created + * @returns {string} pem encoded representation of the private key WITH header and footer + * @public + */ +JSEncrypt.prototype.getPrivateKey = function() { + // Return the private representation of this key. + return this.getKey().getPrivateKey(); +}; + +/** + * Returns the pem encoded representation of the private key + * If the key doesn't exists a new key will be created + * @returns {string} pem encoded representation of the private key WITHOUT header and footer + * @public + */ +JSEncrypt.prototype.getPrivateKeyB64 = function() { + // Return the private representation of this key. + return this.getKey().getPrivateBaseKeyB64(); +}; + + +/** + * Returns the pem encoded representation of the public key + * If the key doesn't exists a new key will be created + * @returns {string} pem encoded representation of the public key WITH header and footer + * @public + */ +JSEncrypt.prototype.getPublicKey = function() { + // Return the private representation of this key. + return this.getKey().getPublicKey(); +}; + +/** + * Returns the pem encoded representation of the public key + * If the key doesn't exists a new key will be created + * @returns {string} pem encoded representation of the public key WITHOUT header and footer + * @public + */ +JSEncrypt.prototype.getPublicKeyB64 = function() { + // Return the private representation of this key. + return this.getKey().getPublicBaseKeyB64(); +}; +