diff --git a/resources/openpgp.js b/resources/openpgp.js index c4d4c62c..18b85d55 100644 --- a/resources/openpgp.js +++ b/resources/openpgp.js @@ -11417,13 +11417,7 @@ function openpgp_packet_public_key() { */ } else if (this.version == 4) { // - A four-octet number denoting the time that the key was created. - var timeb = bytes.substr(1, 4); - - this.created= new Date(( - (timeb[0].charCodeAt() << 24) | - (timeb[1].charCodeAt() << 16) | - (timeb[2].charCodeAt() << 8) | - (timeb[3].charCodeAt())) * 1000); + this.created = openpgp_packet_time_read(bytes.substr(1, 4)); // - A one-octet number denoting the public-key algorithm of this key. this.algorithm = bytes[5].charCodeAt(); @@ -11465,7 +11459,7 @@ function openpgp_packet_public_key() { */ this.write = function() { var result = String.fromCharCode(4); - result += '0000'; + result += openpgp_packet_time_write(this.created); result += String.fromCharCode(this.algorithm); for(var i in this.mpi) { @@ -11512,7 +11506,6 @@ function openpgp_packet_secret_key() { this.mpi = []; this.symmetric_algorithm = openpgp.symmetric.plaintext; this.s2k = null; - this.checksum_algorithm = openpgp.hash.sha1; this.encrypted = null; this.iv = null; @@ -11550,8 +11543,7 @@ function openpgp_packet_secret_key() { // string-to-key specifier. The length of the string-to-key // specifier is implied by its type, as described above. this.s2k = new openpgp_type_s2k(); - this.s2k.read(bytes, i); - i += this.s2k.s2kLength; + i += this.s2k.read(bytes.substr(i)); } // - [Optional] If secret data is encrypted (string-to-key usage octet @@ -11561,6 +11553,7 @@ function openpgp_packet_secret_key() { if (s2k_usage != 0 && s2k_usage != 255 && s2k_usage != 254) { this.symmetric_algorithm = s2k_usage; + } if (s2k_usage != 0 && this.s2k.type != 1001) { @@ -11587,7 +11580,7 @@ function openpgp_packet_secret_key() { var mpis = openpgp_crypto_getPrivateMpiCount(this.public_key.algorithm); this.mpi = []; - for(var j = 0; j < 4; j++) { + for(var j = 0; j < mpis; j++) { this.mpi[j] = new openpgp_type_mpi(); i += this.mpi[j].read(bytes.substr(i)); } @@ -11613,6 +11606,95 @@ function openpgp_packet_secret_key() { header: [string] OpenPGP packet header, string: [string] header+body} */ this.write = function() { + var bytes = this.public_key.write(); + + if(this.encrypted == null) { + bytes += String.fromCharCode(0); + + for(var i in this.mpi) { + bytes += this.mpi[i].write(); + } + + // TODO check the cheksum! + bytes += '00' + } else if(this.s2k == null) { + bytes += String.fromCharCode(this.symmetric_algorithm); + bytes += this.encrypted; + } else { + bytes += String.fromCharCode(254); + bytes += String.fromCharCode(this.symmetric_algorithm); + bytes += this.s2k.write(); + } + + + + switch(keyType){ + case 1: + body += String.fromCharCode(keyType);//public key algo + body += key.n.toMPI(); + body += key.ee.toMPI(); + var algorithmStart = body.length; + //below shows ske/s2k + if(password){ + body += String.fromCharCode(254); //octet of 254 indicates s2k with SHA1 + //if s2k == 255,254 then 1 octet symmetric encryption algo + body += String.fromCharCode(this.symmetric_algorithm); + //if s2k == 255,254 then s2k specifier + body += String.fromCharCode(3); //s2k salt+iter + body += String.fromCharCode(s2kHash); + //8 octet salt value + //1 octet count + var cleartextMPIs = key.d.toMPI() + key.p.toMPI() + key.q.toMPI() + key.u.toMPI(); + var sha1Hash = str_sha1(cleartextMPIs); + util.print_debug_hexstr_dump('write_private_key sha1: ',sha1Hash); + var salt = openpgp_crypto_getRandomBytes(8); + util.print_debug_hexstr_dump('write_private_key Salt: ',salt); + body += salt; + var c = 96; //c of 96 translates to count of 65536 + body += String.fromCharCode(c); + util.print_debug('write_private_key c: '+ c); + var s2k = new openpgp_type_s2k(); + var hashKey = s2k.write(3, s2kHash, password, salt, c); + //if s2k, IV of same length as cipher's block + switch(this.symmetric_algorithm){ + case 3: + this.IVLength = 8; + this.IV = openpgp_crypto_getRandomBytes(this.IVLength); + ciphertextMPIs = normal_cfb_encrypt(function(block, key) { + var cast5 = new openpgp_symenc_cast5(); + cast5.setKey(key); + return cast5.encrypt(util.str2bin(block)); + }, this.IVLength, util.str2bin(hashKey.substring(0,16)), cleartextMPIs + sha1Hash, this.IV); + body += this.IV + ciphertextMPIs; + break; + case 7: + case 8: + case 9: + this.IVLength = 16; + this.IV = openpgp_crypto_getRandomBytes(this.IVLength); + ciphertextMPIs = normal_cfb_encrypt(AESencrypt, + this.IVLength, hashKey, cleartextMPIs + sha1Hash, this.IV); + body += this.IV + ciphertextMPIs; + break; + } + } + else{ + body += String.fromCharCode(0);//1 octet -- s2k, 0 for no s2k + body += key.d.toMPI() + key.p.toMPI() + key.q.toMPI() + key.u.toMPI(); + var checksum = util.calc_checksum(key.d.toMPI() + key.p.toMPI() + key.q.toMPI() + key.u.toMPI()); + body += String.fromCharCode(checksum/0x100) + String.fromCharCode(checksum%0x100);//DEPRECATED:s2k == 0, 255: 2 octet checksum, sum all octets%65536 + util.print_debug_hexstr_dump('write_private_key basic checksum: '+ checksum); + } + break; + default : + body = ""; + util.print_error("openpgp.packet.keymaterial.js\n"+'error writing private key, unknown type :'+keyType); + } + var header = openpgp_packet.write_packet_header(tag,body.length); + return {string: header+body , header: header, body: body}; + } + + this.encrypt = function() { var body = String.fromCharCode(4); body += timePacket; @@ -11682,6 +11764,7 @@ function openpgp_packet_secret_key() { return {string: header+body , header: header, body: body}; } + /** * Decrypts the private key MPIs which are needed to use the key. * openpgp_packet_keymaterial.hasUnencryptedSecretKeyData should be @@ -12261,11 +12344,11 @@ function openpgp_packet_sym_encrypted_session_key() { this.private_algorithm = bytes[1].charCodeAt(); // A string-to-key (S2K) specifier, length as defined above. - this.s2k.read(bytes, 2); + var s2klength = this.s2k.read(bytes.substr(2)); // Optionally, the encrypted session key itself, which is decrypted // with the string-to-key object. - var done = this.s2k.length + 2; + var done = s2klength + 2; if(done < bytes.length) { this.encrypted = bytes.substr(done); } @@ -12387,6 +12470,42 @@ function openpgp_packet_symmetrically_encrypted() { + util.hexstrdump(this.encryptedData) + ']\n'; } }; + + + +function openpgp_packet_number_read(bytes) { + var n = 0; + + for(var i = 0; i < bytes.length; i++) { + n += bytes[i].charCodeAt() * 8 * (bytes.length - i - 1); + } + + return n; +} + +function openpgp_packet_number_write(n, bytes) { + var b = ''; + for(var i = 0; i < bytes; i++) { + b += String.fromCharCode((n >> 8 * (bytes.length - i - 1)) ^ 0xFF); + } + + return b; +} + + + +function openpgp_packet_time_read(bytes) { + var n = openpgp_packet_number_read(bytes); + var d = new Date(); + d.setTime(n * 1000); + return d; +} + +function openpgp_packet_time_write(time) { + var numeric = Math.round(this.time.getTime() / 1000); + + return openpgp_packet_number_write(numeric, 4); +} // GPG4Browsers - An OpenPGP implementation in javascript // Copyright (C) 2011 Recurity Labs GmbH // @@ -12769,6 +12888,185 @@ function openpgp_type_s2k() { // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +/** + * @class + * @classdesc Implementation of the String-to-key specifier (RFC4880 3.7) + * String-to-key (S2K) specifiers are used to convert passphrase strings + into symmetric-key encryption/decryption keys. They are used in two + places, currently: to encrypt the secret part of private keys in the + private keyring, and to convert passphrases to encryption keys for + symmetrically encrypted messages. + */ +function openpgp_type_s2k() { + /** @type {openpgp.hash} */ + this.algorithm = null; + /** @type {openpgp_type_s2k.type} */ + this.type = openpgp_type_s2k.type.iterated; + this.c = 1000; + + + // Exponen bias, defined in RFC4880 + var expbias = 6; + + this.get_count = function() { + return (16 + (this.c & 15)) << ((this.c >> 4) + expbias); + } + + /** + * Parsing function for a string-to-key specifier (RFC 4880 3.7). + * @param {String} input Payload of string-to-key specifier + * @return {Integer} Actual length of the object + */ + this.read = function(bytes) { + var i = 0; + this.type = bytes[i++].charCodeAt(); + this.algorithm = bytes[i++].charCodeAt(); + + var t = openpgp_type_s2k.type; + + switch (this.type) { + case t.simple: + break; + + case t.salted: + this.salt = bytes.substr(i, 8); + i += 8; + break; + + case t.iterated: + this.salt = bytes.substr(i, 8); + i += 8; + + // Octet 10: count, a one-octet, coded value + this.c = bytes[i++].charCodeAt(); + break; + + case t.gnu: + if(bytes.substr(i, 3) == "GNU") { + i += 3; // GNU + var gnuExtType = 1000 + bytes[i++].charCodeAt(); + if(gnuExtType == 1001) { + this.type = gnuExtType; + // GnuPG extension mode 1001 -- don't write secret key at all + } else { + util.print_error("unknown s2k gnu protection mode! "+this.type); + } + } else { + util.print_error("unknown s2k type! "+this.type); + } + break; + + default: + util.print_error("unknown s2k type! "+this.type); + break; + } + + return i; + } + + + /** + * writes an s2k hash based on the inputs. + * @return {String} Produced key of hashAlgorithm hash length + */ + this.write = function() { + var bytes = String.fromCharCode(this.type); + bytes += String.fromCharCode(this.algorithm); + + var t = openpgp_type_s2k.type; + switch(this.type) { + case t.simple: + break; + case t.salted: + bytes += this.salt; + break; + case t.iterated: + bytes += this.salt; + bytes += this.c; + break; + }; + + return bytes; + } + + /** + * Produces a key using the specified passphrase and the defined + * hashAlgorithm + * @param {String} passphrase Passphrase containing user input + * @return {String} Produced key with a length corresponding to + * hashAlgorithm hash length + */ + this.produce_key = function(passphrase, numBytes) { + passphrase = util.encode_utf8(passphrase); + + function round(prefix, s2k) { + + var t = openpgp_type_s2k.type; + switch(s2k.type) { + case t.simple: + return openpgp_crypto_hashData(s2k.algorithm, prefix + passphrase); + + case t.salted: + return openpgp_crypto_hashData(s2k.algorithm, + prefix + s2k.salt + passphrase); + + case t.iterated: + var isp = [], + count = s2k.get_count(); + data = s2k.salt + passphrase; + + while (isp.length * data.length < count) + isp.push(data); + + isp = isp.join(''); + + if (isp.length > count) + isp = isp.substr(0, count); + + return openpgp_crypto_hashData(s2k.algorithm, prefix + isp); + }; + } + + var result = '', + prefix = ''; + + while(result.length <= numBytes) { + result += round(prefix, this); + prefix += String.fromCharCode(0); + } + + return result.substr(0, numBytes); + } +} + + + +/** A string to key specifier type + * @enum {Integer} + */ +openpgp_type_s2k.type = { + simple: 0, + salted: 1, + iterated: 3, + gnu: 101 +} +// 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 + var Util = function() { this.emailRegEx = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/; diff --git a/resources/openpgp.min.js b/resources/openpgp.min.js index 9a3c44da..1fee8000 100644 --- a/resources/openpgp.min.js +++ b/resources/openpgp.min.js @@ -403,34 +403,42 @@ var a=10;switch(this.public_key_algorithm){case openpgp.publickey.rsa_encrypt:ca this.public_key_algorithm)}};this.write=function(){for(var b=String.fromCharCode(this.version),b=b+this.public_key_id.bytes,b=b+String.fromCharCode(this.public_key_algorithm),a=0;a>8&255),a=a+String.fromCharCode(c&255),c=new openpgp_type_mpi;c.fromBytes(openpgp_encoding_eme_pkcs1_encode(a, b[0].byteLength()));this.encrypted=openpgp_crypto_asymetricEncrypt(this.public_key_algorithm,b,c)};this.decrypt=function(b,a){var c=openpgp_crypto_asymetricDecrypt(this.public_key_algorithm,b,a,this.encrypted).toBytes(),d=(c.charCodeAt(c.length-2)<<8)+c.charCodeAt(c.length-1),c=openpgp_encoding_eme_pkcs1_decode(c,b[0].byteLength()),e=c.substring(1,c.length-2);d!=util.calc_checksum(e)?util.print_error("Checksum mismatch"):(this.symmetric_key=e,this.symmetric_algorithm=c.charCodeAt(0))};this.toString= function(){for(var b="5.1. Public-Key Encrypted Session Key Packets (Tag 1)\n KeyId: "+this.keyId.toString()+"\n length: "+this.packetLength+"\n version:"+this.version+"\n pubAlgUs:"+this.publicKeyAlgorithmUsed+"\n",a=0;athis.algorithm?2:16==this.algorithm?3:17==this.algorithm?4:0;this.mpi=[];for(var b=b.substr(6), -c=0,d=0;db.length&&util.print_error("openpgp.packet.keymaterial.js\nerror reading MPI @:"+c);return c+6}util.print_error("Unknown packet version")}};this.write=function(){var b=String.fromCharCode(4),b=b+"0000"+String.fromCharCode(this.algorithm),a;for(a in this.mpi)b+=this.mpi[a].write();return b}}function openpgp_packet_public_subkey(){openpgp_packet_public_key.call(this);this.tag=14} -function openpgp_packet_secret_key(){this.tag=5;this.public_key=new openpgp_packet_public_key;this.mpi=[];this.symmetric_algorithm=openpgp.symmetric.plaintext;this.s2k=null;this.checksum_algorithm=openpgp.hash.sha1;this.iv=this.encrypted=null;this.read=function(b){var a=this.public_key.read(b),b=b.substr(a),c=b[0].charCodeAt(),a=1;if(255==c||254==c)this.symmetric_algorithm=b[a++].charCodeAt(),this.s2k=new openpgp_type_s2k,this.s2k.read(b,a),a+=this.s2k.s2kLength;if(0!=c&&255!=c&&254!=c)this.symmetric_algorithm= -c;if(0!=c&&1001!=this.s2k.type)this.iv=b.substr(a,openpgp_crypto_getBlockLength(this.symmetric_algorithm)),a+=this.iv.length;if(0!=c&&1001==this.s2k.type)this.encrypted=this.mpi=null;else if(0!=c)this.encrypted=b.substr(a);else{openpgp_crypto_getPrivateMpiCount(this.public_key.algorithm);this.mpi=[];for(c=0;4>c;c++)this.mpi[c]=new openpgp_type_mpi,a+=this.mpi[c].read(b.substr(a));this.checksum=[];this.checksum[0]=b[a++].charCodeAt();this.checksum[1]=b[a++].charCodeAt()}};this.write=function(){var b= -String.fromCharCode(4),b=b+timePacket;switch(keyType){case 1:b+=String.fromCharCode(keyType);b+=key.n.toMPI();b+=key.ee.toMPI();if(password){var b=b+String.fromCharCode(254),b=b+String.fromCharCode(this.symmetric_algorithm),b=b+String.fromCharCode(3),b=b+String.fromCharCode(s2kHash),a=key.d.toMPI()+key.p.toMPI()+key.q.toMPI()+key.u.toMPI(),c=str_sha1(a);util.print_debug_hexstr_dump("write_private_key sha1: ",c);var d=openpgp_crypto_getRandomBytes(8);util.print_debug_hexstr_dump("write_private_key Salt: ", -d);b=b+d+String.fromCharCode(96);util.print_debug("write_private_key c: 96");d=(new openpgp_type_s2k).write(3,s2kHash,password,d,96);switch(this.symmetric_algorithm){case 3:this.IVLength=8;this.IV=openpgp_crypto_getRandomBytes(this.IVLength);ciphertextMPIs=normal_cfb_encrypt(function(a,b){var c=new openpgp_symenc_cast5;c.setKey(b);return c.encrypt(util.str2bin(a))},this.IVLength,util.str2bin(d.substring(0,16)),a+c,this.IV);b+=this.IV+ciphertextMPIs;break;case 7:case 8:case 9:this.IVLength=16,this.IV= -openpgp_crypto_getRandomBytes(this.IVLength),ciphertextMPIs=normal_cfb_encrypt(AESencrypt,this.IVLength,d,a+c,this.IV),b+=this.IV+ciphertextMPIs}}else b+=String.fromCharCode(0),b+=key.d.toMPI()+key.p.toMPI()+key.q.toMPI()+key.u.toMPI(),a=util.calc_checksum(key.d.toMPI()+key.p.toMPI()+key.q.toMPI()+key.u.toMPI()),b+=String.fromCharCode(a/256)+String.fromCharCode(a%256),util.print_debug_hexstr_dump("write_private_key basic checksum: "+a);break;default:b="",util.print_error("openpgp.packet.keymaterial.js\nerror writing private key, unknown type :"+ -keyType)}a=openpgp_packet.write_packet_header(tag,b.length);return{string:a+b,header:a,body:b}};this.decrypt=function(b){if(null!=this.encrypted){var a=this.s2k.produce_key(b),b="";switch(this.symmetric_algorithm){case 1:return util.print_error("openpgp.packet.keymaterial.js\nsymmetric encryption algorithim: IDEA is not implemented"),!1;case 2:b=normal_cfb_decrypt(function(a,b){return des(b,a,1,null,0)},this.IVLength,a,this.encrypted,this.IV);break;case 3:b=normal_cfb_decrypt(function(a,b){var c= -new openpgp_symenc_cast5;c.setKey(b);return c.encrypt(util.str2bin(a))},this.IVLength,util.str2bin(a.substring(0,16)),this.encrypted,this.IV);break;case 4:b=normal_cfb_decrypt(function(a,b){return(new Blowfish(b)).encrypt(a)},this.IVLength,a,this.encrypted,this.IV);break;case 7:case 8:case 9:b=16;8==this.symmetric_algorithm&&(b=24,a=this.s2k.produce_key(str_passphrase,b));9==this.symmetric_algorithm&&(b=32,a=this.s2k.produce_key(str_passphrase,b));b=normal_cfb_decrypt(function(a,b){return AESencrypt(util.str2bin(a), -b)},this.IVLength,keyExpansion(a.substring(0,b)),this.encrypted,this.IV);break;case 10:return util.print_error("openpgp.packet.keymaterial.js\nKey material is encrypted with twofish: not implemented"),!1;default:return util.print_error("openpgp.packet.keymaterial.js\nunknown encryption algorithm for secret key :"+this.symmetric_algorithm),!1}if(null==b)return util.print_error("openpgp.packet.keymaterial.js\ncleartextMPIs was null"),!1;a=b.length;if(254==s2k_usage&&str_sha1(b.substring(0,b.length- -20))==b.substring(b.length-20))a-=20;else if(254!=s2k_usage&&util.calc_checksum(b.substring(0,b.length-2))==(b.charCodeAt(b.length-2)<<8|b.charCodeAt(b.length-1)))a-=2;else return!1;if(0this.publicKey.publicKeyAlgorithm){var c=0;this.mpi=[];this.mpi[0]=new openpgp_type_mpi;this.mpi[0].read(b,0,a);c+=this.mpi[0].packetLength;this.mpi[1]=new openpgp_type_mpi;this.mpi[1].read(b,c,a-c);c+=this.mpi[1].packetLength;this.mpi[2]=new openpgp_type_mpi;this.mpi[2].read(b, -c,a-c);c+=this.mpi[2].packetLength;this.mpi[3]=new openpgp_type_mpi;this.mpi[3].read(b,c,a-c);c+=this.mpi[3].packetLength}else if(16==this.publicKey.publicKeyAlgorithm)this.mpi=[],this.mpi[0]=new openpgp_type_mpi,this.mpi[0].read(b,0,b);else if(17==this.publicKey.publicKeyAlgorithm)this.mpi=[],this.mpi[0]=new openpgp_type_mpi,this.mpi[0].read(b,0,a);return!0}}}function openpgp_packet_secret_subkey(){openpgp_packet_secret_key.call(this);this.tag=7} -function openpgp_packet_signature(){this.tag=2;this.write=function(){};this.read=function(){}} +function openpgp_packet_public_key(){this.tag=6;this.version=4;this.created=null;this.mpi=[];this.algorithm=openpgp.publickey.rsa_sign;this.read=function(b){this.version=b[0].charCodeAt();if(3!=this.version){if(4==this.version){this.created=openpgp_packet_time_read(b.substr(1,4));this.algorithm=b[5].charCodeAt();var a=0this.algorithm?2:16==this.algorithm?3:17==this.algorithm?4:0;this.mpi=[];for(var b=b.substr(6),c=0,d=0;db.length&&util.print_error("openpgp.packet.keymaterial.js\nerror reading MPI @:"+c);return c+6}util.print_error("Unknown packet version")}};this.write=function(){var b=String.fromCharCode(4),b=b+openpgp_packet_time_write(this.created),b=b+String.fromCharCode(this.algorithm),a;for(a in this.mpi)b+=this.mpi[a].write();return b}}function openpgp_packet_public_subkey(){openpgp_packet_public_key.call(this);this.tag=14} +function openpgp_packet_secret_key(){this.tag=5;this.public_key=new openpgp_packet_public_key;this.mpi=[];this.symmetric_algorithm=openpgp.symmetric.plaintext;this.iv=this.encrypted=this.s2k=null;this.read=function(b){var a=this.public_key.read(b),b=b.substr(a),c=b[0].charCodeAt(),a=1;if(255==c||254==c)this.symmetric_algorithm=b[a++].charCodeAt(),this.s2k=new openpgp_type_s2k,a+=this.s2k.read(b.substr(a));if(0!=c&&255!=c&&254!=c)this.symmetric_algorithm=c;if(0!=c&&1001!=this.s2k.type)this.iv=b.substr(a, +openpgp_crypto_getBlockLength(this.symmetric_algorithm)),a+=this.iv.length;if(0!=c&&1001==this.s2k.type)this.encrypted=this.mpi=null;else if(0!=c)this.encrypted=b.substr(a);else{c=openpgp_crypto_getPrivateMpiCount(this.public_key.algorithm);this.mpi=[];for(var d=0;dthis.publicKey.publicKeyAlgorithm){var c=0;this.mpi=[];this.mpi[0]=new openpgp_type_mpi;this.mpi[0].read(b,0,a);c+=this.mpi[0].packetLength;this.mpi[1]=new openpgp_type_mpi;this.mpi[1].read(b,c,a-c);c+=this.mpi[1].packetLength;this.mpi[2]=new openpgp_type_mpi;this.mpi[2].read(b,c,a-c);c+=this.mpi[2].packetLength;this.mpi[3]=new openpgp_type_mpi;this.mpi[3].read(b,c,a-c); +c+=this.mpi[3].packetLength}else if(16==this.publicKey.publicKeyAlgorithm)this.mpi=[],this.mpi[0]=new openpgp_type_mpi,this.mpi[0].read(b,0,b);else if(17==this.publicKey.publicKeyAlgorithm)this.mpi=[],this.mpi[0]=new openpgp_type_mpi,this.mpi[0].read(b,0,a);return!0}}}function openpgp_packet_secret_subkey(){openpgp_packet_secret_key.call(this);this.tag=7}function openpgp_packet_signature(){this.tag=2;this.write=function(){};this.read=function(){}} function openpgp_packet_sym_encrypted_integrity_protected(){this.tag=18;this.version=1;this.encrypted=null;this.modification=!1;this.packets=new openpgp_packetlist;this.read=function(b){this.version=b[0].charCodeAt();if(1!=this.version)return util.print_error("openpgp.packet.encryptedintegrityprotecteddata.js\nunknown encrypted integrity protected data packet version: "+this.version+"hex:"+util.hexstrdump(b)),null;this.encrypted=b.substr(1)};this.write=function(){return String.fromCharCode(this.version)+ this.encrypted};this.encrypt=function(b,a){var c=this.packets.write(),d=openpgp_crypto_getPrefixRandom(b),e=d+d.charAt(d.length-2)+d.charAt(d.length-1),c=c+String.fromCharCode(211),c=c+String.fromCharCode(20);util.print_debug_hexstr_dump("data to be hashed:",e+c);c+=str_sha1(e+c);util.print_debug_hexstr_dump("hash:",c.substring(c.length-20,c.length));this.encrypted=openpgp_crypto_symmetricEncrypt(d,b,a,c,!1).substring(0,e.length+c.length)};this.decrypt=function(b,a){var c=openpgp_crypto_symmetricDecrypt(b, a,this.encrypted,!1);this.hash=str_sha1(openpgp_crypto_MDCSystemBytes(b,a,this.encrypted)+c.substring(0,c.length-20));util.print_debug_hexstr_dump("calc hash = ",this.hash);this.hash!=c.substr(c.length-20,20)?(this.packets=new openpgp_packetlist,util.print_error("Decryption stopped: discovered a modification of encrypted data.")):this.packets.read(c.substr(0,c.length-22))};this.toString=function(){var b="";openpgp.config.debug&&(b=" data: Bytes ["+util.hexstrdump(this.encrypted)+"]");return"5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18)\n\n version: "+ this.version+"\n"+b}} -function openpgp_packet_sym_encrypted_session_key(){this.tag=3;this.algorithm=this.private_algorithm=openpgp.symmetric.plaintext;this.encrypted=null;this.s2k=new openpgp_type_s2k;this.read=function(b){this.version=b[0].charCodeAt();this.private_algorithm=b[1].charCodeAt();this.s2k.read(b,2);var a=this.s2k.length+2;if(a>8*(a.length-d-1)^255);return c}function openpgp_packet_time_read(b){var b=openpgp_packet_number_read(b),a=new Date;a.setTime(1E3*b);return a} +function openpgp_packet_time_write(){var b=Math.round(this.time.getTime()/1E3);return openpgp_packet_number_write(b,4)}function openpgp_packet_userid(){this.userid="";this.tag=13;this.read=function(b){this.userid=util.decode_utf8(b)};this.write=function(){return util.encode_utf8(this.userid)}} function openpgp_type_mpi(){this.data=null;this.read=function(b){var a=b[0].charCodeAt()<<8|b[1].charCodeAt(),a=Math.ceil(a/8);this.fromBytes(b.substr(2,a));return 2+a};this.fromBytes=function(b){this.data=new BigInteger(util.hexstrdump(b),16)};this.toBytes=function(){return this.write().substr(2)};this.byteLength=function(){return this.toBytes().length};this.write=function(){return this.data.toMPI()};this.toBigInteger=function(){return this.data.clone()};this.fromBigInteger=function(b){this.data= b.clone()};this.toString=function(){var b=" MPI("+this.mpiBitLength+"b/"+this.mpiByteLength+"B) : 0x",b=b+util.hexstrdump(this.MPI);return b+"\n"}}function openpgp_type_keyid(){for(var b="",a=0;8>a;a++)b+=String.fromCharCode(0);this.read_packet=function(a,b){this.bytes=a.substring(b,b+8);return this};this.toString=function(){return util.hexstrdump(this.bytes)}} function openpgp_type_s2k(){this.read=function(b,a){var c=a;this.type=b[c++].charCodeAt();switch(this.type){case 0:this.hashAlgorithm=b[c++].charCodeAt();this.s2kLength=1;break;case 1:this.hashAlgorithm=b[c++].charCodeAt();this.saltValue=b.substring(c,c+8);c+=8;this.s2kLength=9;break;case 3:this.hashAlgorithm=b[c++].charCodeAt();this.saltValue=b.substring(c,c+8);c+=8;this.EXPBIAS=6;var d=b[c++].charCodeAt();this.count=16+(d&15)<<(d>>4)+this.EXPBIAS;this.s2kLength=10;break;case 101:"GNU"==b.substring(c+ 1,c+4)?(this.hashAlgorithm=b[c++].charCodeAt(),c+=3,d=1E3+b[c++].charCodeAt(),1001==d?(this.type=d,this.s2kLength=5):util.print_error("unknown s2k gnu protection mode! "+this.type)):util.print_error("unknown s2k type! "+this.type);break;default:util.print_error("unknown s2k type! "+this.type)}this.packetLength=c-a;return this};this.write=function(b,a,c,d,e){this.type=b;if(3==this.type)this.saltValue=d,this.hashAlgorithm=a,this.count=16+(e&15)<<(e>>4)+6,this.s2kLength=10;return this.produce_key(c)}; this.produce_key=function(b,a){var b=util.encode_utf8(b),c;if(0==this.type)c=openpgp_crypto_hashData(this.hashAlgorithm,b);else if(1==this.type)c=openpgp_crypto_hashData(this.hashAlgorithm,this.saltValue+b);else if(3==this.type){c=[];for(c[0]=this.saltValue+b;c.length*(this.saltValue+b).lengththis.count&&(c=c.substr(0,this.count));c=a&&(24==a||32==a)?openpgp_crypto_hashData(this.hashAlgorithm,c)+openpgp_crypto_hashData(this.hashAlgorithm, String.fromCharCode(0)+c):openpgp_crypto_hashData(this.hashAlgorithm,c)}else return null;return c.substr(0,a)}} -var Util=function(){this.emailRegEx=/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;this.debug=!1;this.hexdump=function(a){for(var b=[],e=a.length,f=0,g,h=0;fg.length;)g="0"+g;b.push(" "+g);h++;0==h%32&&b.push("\n ")}return b.join("")};this.hexstrdump=function(a){if(null==a)return"";for(var b=[],e=a.length,f=0,g;f +function openpgp_type_s2k(){this.algorithm=null;this.type=openpgp_type_s2k.type.iterated;this.c=1E3;this.get_count=function(){return 16+(this.c&15)<<(this.c>>4)+6};this.read=function(b){var a=0;this.type=b[a++].charCodeAt();this.algorithm=b[a++].charCodeAt();var c=openpgp_type_s2k.type;switch(this.type){case c.simple:break;case c.salted:this.salt=b.substr(a,8);a+=8;break;case c.iterated:this.salt=b.substr(a,8);a+=8;this.c=b[a++].charCodeAt();break;case c.gnu:"GNU"==b.substr(a,3)?(a+=3,b=1E3+b[a++].charCodeAt(), +1001==b?this.type=b:util.print_error("unknown s2k gnu protection mode! "+this.type)):util.print_error("unknown s2k type! "+this.type);break;default:util.print_error("unknown s2k type! "+this.type)}return a};this.write=function(){var b=String.fromCharCode(this.type),b=b+String.fromCharCode(this.algorithm),a=openpgp_type_s2k.type;switch(this.type){case a.salted:b+=this.salt;break;case a.iterated:b+=this.salt,b+=this.c}return b};this.produce_key=function(b,a){for(var b=util.encode_utf8(b),c="",d="";c.length<= +a;){var e;a:{e=d;var f=openpgp_type_s2k.type;switch(this.type){case f.simple:e=openpgp_crypto_hashData(this.algorithm,e+b);break a;case f.salted:e=openpgp_crypto_hashData(this.algorithm,e+this.salt+b);break a;case f.iterated:var f=[],g=this.get_count();for(data=this.salt+b;f.length*data.lengthg&&(f=f.substr(0,g));e=openpgp_crypto_hashData(this.algorithm,e+f);break a}e=void 0}c+=e;d+=String.fromCharCode(0)}return c.substr(0,a)}} +openpgp_type_s2k.type={simple:0,salted:1,iterated:3,gnu:101}; +var Util=function(){this.emailRegEx=/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;this.debug=!1;this.hexdump=function(b){for(var a=[],e=b.length,f=0,g,h=0;fg.length;)g="0"+g;a.push(" "+g);h++;0==h%32&&a.push("\n ")}return a.join("")};this.hexstrdump=function(a){if(null==a)return"";for(var b=[],e=a.length,f=0,g;f g.length;)g="0"+g;b.push(""+g)}return b.join("")};this.hex2bin=function(a){for(var b="",e=0;eg.length;)g="0"+g;b.push(""+g)}return b.join("")};this.encode_utf8=function(a){return unescape(encodeURIComponent(a))};this.decode_utf8=function(a){return decodeURIComponent(escape(a))};var b=function(a,b){for(var e=0;e>=b%8,0> 8 * (bytes.length - i - 1)) ^ 0xFF); + } + + return b; +} + + + +function openpgp_packet_time_read(bytes) { + var n = openpgp_packet_number_read(bytes); + var d = new Date(); + d.setTime(n * 1000); + return d; +} + +function openpgp_packet_time_write(time) { + var numeric = Math.round(this.time.getTime() / 1000); + + return openpgp_packet_number_write(numeric, 4); +} diff --git a/src/type/s2k.js b/src/type/s2k.js new file mode 100644 index 00000000..edb41b1a --- /dev/null +++ b/src/type/s2k.js @@ -0,0 +1,179 @@ +// 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 + +/** + * @class + * @classdesc Implementation of the String-to-key specifier (RFC4880 3.7) + * String-to-key (S2K) specifiers are used to convert passphrase strings + into symmetric-key encryption/decryption keys. They are used in two + places, currently: to encrypt the secret part of private keys in the + private keyring, and to convert passphrases to encryption keys for + symmetrically encrypted messages. + */ +function openpgp_type_s2k() { + /** @type {openpgp.hash} */ + this.algorithm = null; + /** @type {openpgp_type_s2k.type} */ + this.type = openpgp_type_s2k.type.iterated; + this.c = 1000; + + + // Exponen bias, defined in RFC4880 + var expbias = 6; + + this.get_count = function() { + return (16 + (this.c & 15)) << ((this.c >> 4) + expbias); + } + + /** + * Parsing function for a string-to-key specifier (RFC 4880 3.7). + * @param {String} input Payload of string-to-key specifier + * @return {Integer} Actual length of the object + */ + this.read = function(bytes) { + var i = 0; + this.type = bytes[i++].charCodeAt(); + this.algorithm = bytes[i++].charCodeAt(); + + var t = openpgp_type_s2k.type; + + switch (this.type) { + case t.simple: + break; + + case t.salted: + this.salt = bytes.substr(i, 8); + i += 8; + break; + + case t.iterated: + this.salt = bytes.substr(i, 8); + i += 8; + + // Octet 10: count, a one-octet, coded value + this.c = bytes[i++].charCodeAt(); + break; + + case t.gnu: + if(bytes.substr(i, 3) == "GNU") { + i += 3; // GNU + var gnuExtType = 1000 + bytes[i++].charCodeAt(); + if(gnuExtType == 1001) { + this.type = gnuExtType; + // GnuPG extension mode 1001 -- don't write secret key at all + } else { + util.print_error("unknown s2k gnu protection mode! "+this.type); + } + } else { + util.print_error("unknown s2k type! "+this.type); + } + break; + + default: + util.print_error("unknown s2k type! "+this.type); + break; + } + + return i; + } + + + /** + * writes an s2k hash based on the inputs. + * @return {String} Produced key of hashAlgorithm hash length + */ + this.write = function() { + var bytes = String.fromCharCode(this.type); + bytes += String.fromCharCode(this.algorithm); + + var t = openpgp_type_s2k.type; + switch(this.type) { + case t.simple: + break; + case t.salted: + bytes += this.salt; + break; + case t.iterated: + bytes += this.salt; + bytes += this.c; + break; + }; + + return bytes; + } + + /** + * Produces a key using the specified passphrase and the defined + * hashAlgorithm + * @param {String} passphrase Passphrase containing user input + * @return {String} Produced key with a length corresponding to + * hashAlgorithm hash length + */ + this.produce_key = function(passphrase, numBytes) { + passphrase = util.encode_utf8(passphrase); + + function round(prefix, s2k) { + + var t = openpgp_type_s2k.type; + switch(s2k.type) { + case t.simple: + return openpgp_crypto_hashData(s2k.algorithm, prefix + passphrase); + + case t.salted: + return openpgp_crypto_hashData(s2k.algorithm, + prefix + s2k.salt + passphrase); + + case t.iterated: + var isp = [], + count = s2k.get_count(); + data = s2k.salt + passphrase; + + while (isp.length * data.length < count) + isp.push(data); + + isp = isp.join(''); + + if (isp.length > count) + isp = isp.substr(0, count); + + return openpgp_crypto_hashData(s2k.algorithm, prefix + isp); + }; + } + + var result = '', + prefix = ''; + + while(result.length <= numBytes) { + result += round(prefix, this); + prefix += String.fromCharCode(0); + } + + return result.substr(0, numBytes); + } +} + + + +/** A string to key specifier type + * @enum {Integer} + */ +openpgp_type_s2k.type = { + simple: 0, + salted: 1, + iterated: 3, + gnu: 101 +} diff --git a/test/general/packet.js b/test/general/packet.js index cea6d8f2..5bb5b4e2 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -225,6 +225,11 @@ unittests.register("Packet testing", function() { return new test_result('Public key encrypted packet (reading, GPG)', text == 'Hello world!'); + }, function() { + + + return new test_result('Secret key encryption/decryption test', + 'tello' == 'Hello world!'); }]; tests.reverse();