Mercurial > sat_docs
view scripts/minifier/otr/otr.js @ 41:21d0d0ab7aa0
xeps: renamed published proto-xeps, and updated them according to published version
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 20 Mar 2015 16:13:10 +0100 |
parents | 1596660ddf72 |
children |
line wrap: on
line source
/*! otr.js v0.2.12 - 2014-04-15 (c) 2014 - Arlo Breault <arlolra@gmail.com> Freely distributed under the MPL v2.0 license. This file is concatenated for the browser. Please see: https://github.com/arlolra/otr */ ;(function (root, factory) { if (typeof define === 'function' && define.amd) { define([ "bigint" , "crypto" , "eventemitter" ], function (BigInt, CryptoJS, EventEmitter) { var root = { BigInt: BigInt , CryptoJS: CryptoJS , EventEmitter: EventEmitter , OTR: {} , DSA: {} } return factory.call(root) }) } else { root.OTR = {} root.DSA = {} factory.call(root) } }(this, function () { ;(function () { "use strict"; var root = this var CONST = { // diffie-heilman N : 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF' , G : '2' // otr message states , MSGSTATE_PLAINTEXT : 0 , MSGSTATE_ENCRYPTED : 1 , MSGSTATE_FINISHED : 2 // otr auth states , AUTHSTATE_NONE : 0 , AUTHSTATE_AWAITING_DHKEY : 1 , AUTHSTATE_AWAITING_REVEALSIG : 2 , AUTHSTATE_AWAITING_SIG : 3 // whitespace tags , WHITESPACE_TAG : '\x20\x09\x20\x20\x09\x09\x09\x09\x20\x09\x20\x09\x20\x09\x20\x20' , WHITESPACE_TAG_V2 : '\x20\x20\x09\x09\x20\x20\x09\x20' , WHITESPACE_TAG_V3 : '\x20\x20\x09\x09\x20\x20\x09\x09' // otr tags , OTR_TAG : '?OTR' , OTR_VERSION_1 : '\x00\x01' , OTR_VERSION_2 : '\x00\x02' , OTR_VERSION_3 : '\x00\x03' // smp machine states , SMPSTATE_EXPECT0 : 0 , SMPSTATE_EXPECT1 : 1 , SMPSTATE_EXPECT2 : 2 , SMPSTATE_EXPECT3 : 3 , SMPSTATE_EXPECT4 : 4 // unstandard status codes , STATUS_SEND_QUERY : 0 , STATUS_AKE_INIT : 1 , STATUS_AKE_SUCCESS : 2 , STATUS_END_OTR : 3 } if (typeof module !== 'undefined' && module.exports) { module.exports = CONST } else { root.OTR.CONST = CONST } }).call(this) ;(function () { "use strict"; var root = this var HLP = {}, CryptoJS, BigInt if (typeof module !== 'undefined' && module.exports) { module.exports = HLP = {} CryptoJS = require('../vendor/crypto.js') BigInt = require('../vendor/bigint.js') } else { if (root.OTR) root.OTR.HLP = HLP if (root.DSA) root.DSA.HLP = HLP CryptoJS = root.CryptoJS BigInt = root.BigInt } // data types (byte lengths) var DTS = { BYTE : 1 , SHORT : 2 , INT : 4 , CTR : 8 , MAC : 20 , SIG : 40 } // otr message wrapper begin and end var WRAPPER_BEGIN = "?OTR" , WRAPPER_END = "." var TWO = BigInt.str2bigInt('2', 10) HLP.debug = function (msg) { // used as HLP.debug.call(ctx, msg) if ( this.debug && typeof this.debug !== 'function' && typeof console !== 'undefined' ) console.log(msg) } HLP.extend = function (child, parent) { for (var key in parent) { if (Object.hasOwnProperty.call(parent, key)) child[key] = parent[key] } function Ctor() { this.constructor = child } Ctor.prototype = parent.prototype child.prototype = new Ctor() child.__super__ = parent.prototype } // constant-time string comparison HLP.compare = function (str1, str2) { if (str1.length !== str2.length) return false var i = 0, result = 0 for (; i < str1.length; i++) result |= str1[i].charCodeAt(0) ^ str2[i].charCodeAt(0) return result === 0 } HLP.randomExponent = function () { return BigInt.randBigInt(1536) } HLP.smpHash = function (version, fmpi, smpi) { var sha256 = CryptoJS.algo.SHA256.create() sha256.update(CryptoJS.enc.Latin1.parse(HLP.packBytes(version, DTS.BYTE))) sha256.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(fmpi))) if (smpi) sha256.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(smpi))) var hash = sha256.finalize() return HLP.bits2bigInt(hash.toString(CryptoJS.enc.Latin1)) } HLP.makeMac = function (aesctr, m) { var pass = CryptoJS.enc.Latin1.parse(m) var mac = CryptoJS.HmacSHA256(CryptoJS.enc.Latin1.parse(aesctr), pass) return HLP.mask(mac.toString(CryptoJS.enc.Latin1), 0, 160) } HLP.make1Mac = function (aesctr, m) { var pass = CryptoJS.enc.Latin1.parse(m) var mac = CryptoJS.HmacSHA1(CryptoJS.enc.Latin1.parse(aesctr), pass) return mac.toString(CryptoJS.enc.Latin1) } HLP.encryptAes = function (msg, c, iv) { var opts = { mode: CryptoJS.mode.CTR , iv: CryptoJS.enc.Latin1.parse(iv) , padding: CryptoJS.pad.NoPadding } var aesctr = CryptoJS.AES.encrypt( msg , CryptoJS.enc.Latin1.parse(c) , opts ) var aesctr_decoded = CryptoJS.enc.Base64.parse(aesctr.toString()) return CryptoJS.enc.Latin1.stringify(aesctr_decoded) } HLP.decryptAes = function (msg, c, iv) { msg = CryptoJS.enc.Latin1.parse(msg) var opts = { mode: CryptoJS.mode.CTR , iv: CryptoJS.enc.Latin1.parse(iv) , padding: CryptoJS.pad.NoPadding } return CryptoJS.AES.decrypt( CryptoJS.enc.Base64.stringify(msg) , CryptoJS.enc.Latin1.parse(c) , opts ) } HLP.multPowMod = function (a, b, c, d, e) { return BigInt.multMod(BigInt.powMod(a, b, e), BigInt.powMod(c, d, e), e) } HLP.ZKP = function (v, c, d, e) { return BigInt.equals(c, HLP.smpHash(v, d, e)) } // greater than, or equal HLP.GTOE = function (a, b) { return (BigInt.equals(a, b) || BigInt.greater(a, b)) } HLP.between = function (x, a, b) { return (BigInt.greater(x, a) && BigInt.greater(b, x)) } HLP.checkGroup = function (g, N_MINUS_2) { return HLP.GTOE(g, TWO) && HLP.GTOE(N_MINUS_2, g) } HLP.h1 = function (b, secbytes) { var sha1 = CryptoJS.algo.SHA1.create() sha1.update(CryptoJS.enc.Latin1.parse(b)) sha1.update(CryptoJS.enc.Latin1.parse(secbytes)) return (sha1.finalize()).toString(CryptoJS.enc.Latin1) } HLP.h2 = function (b, secbytes) { var sha256 = CryptoJS.algo.SHA256.create() sha256.update(CryptoJS.enc.Latin1.parse(b)) sha256.update(CryptoJS.enc.Latin1.parse(secbytes)) return (sha256.finalize()).toString(CryptoJS.enc.Latin1) } HLP.mask = function (bytes, start, n) { return bytes.substr(start / 8, n / 8) } var _toString = String.fromCharCode; HLP.packBytes = function (val, bytes) { val = val.toString(16) var nex, res = '' // big-endian, unsigned long for (; bytes > 0; bytes--) { nex = val.length ? val.substr(-2, 2) : '0' val = val.substr(0, val.length - 2) res = _toString(parseInt(nex, 16)) + res } return res } HLP.packINT = function (d) { return HLP.packBytes(d, DTS.INT) } HLP.packCtr = function (d) { return HLP.padCtr(HLP.packBytes(d, DTS.CTR)) } HLP.padCtr = function (ctr) { return ctr + '\x00\x00\x00\x00\x00\x00\x00\x00' } HLP.unpackCtr = function (d) { d = HLP.toByteArray(d.substring(0, 8)) return HLP.unpack(d) } HLP.unpack = function (arr) { var val = 0, i = 0, len = arr.length for (; i < len; i++) { val = (val * 256) + arr[i] } return val } HLP.packData = function (d) { return HLP.packINT(d.length) + d } HLP.bits2bigInt = function (bits) { bits = HLP.toByteArray(bits) return BigInt.ba2bigInt(bits) } HLP.packMPI = function (mpi) { return HLP.packData(BigInt.bigInt2bits(BigInt.trim(mpi, 0))) } HLP.packSHORT = function (short) { return HLP.packBytes(short, DTS.SHORT) } HLP.unpackSHORT = function (short) { short = HLP.toByteArray(short) return HLP.unpack(short) } HLP.packTLV = function (type, value) { return HLP.packSHORT(type) + HLP.packSHORT(value.length) + value } HLP.readLen = function (msg) { msg = HLP.toByteArray(msg.substring(0, 4)) return HLP.unpack(msg) } HLP.readData = function (data) { var n = HLP.unpack(data.splice(0, 4)) return [n, data] } HLP.readMPI = function (data) { data = HLP.toByteArray(data) data = HLP.readData(data) return BigInt.ba2bigInt(data[1]) } HLP.packMPIs = function (arr) { return arr.reduce(function (prv, cur) { return prv + HLP.packMPI(cur) }, '') } HLP.unpackMPIs = function (num, mpis) { var i = 0, arr = [] for (; i < num; i++) arr.push('MPI') return (HLP.splitype(arr, mpis)).map(function (m) { return HLP.readMPI(m) }) } HLP.wrapMsg = function (msg, fs, v3, our_it, their_it) { msg = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Latin1.parse(msg)) msg = WRAPPER_BEGIN + ":" + msg + WRAPPER_END var its if (v3) { its = '|' its += (HLP.readLen(our_it)).toString(16) its += '|' its += (HLP.readLen(their_it)).toString(16) } if (!fs) return [null, msg] var n = Math.ceil(msg.length / fs) if (n > 65535) return ['Too many fragments'] if (n == 1) return [null, msg] var k, bi, ei, frag, mf, mfs = [] for (k = 1; k <= n; k++) { bi = (k - 1) * fs ei = k * fs frag = msg.slice(bi, ei) mf = WRAPPER_BEGIN if (v3) mf += its mf += ',' + k + ',' mf += n + ',' mf += frag + ',' mfs.push(mf) } return [null, mfs] } HLP.splitype = function splitype(arr, msg) { var data = [] arr.forEach(function (a) { var str switch (a) { case 'PUBKEY': str = splitype(['SHORT', 'MPI', 'MPI', 'MPI', 'MPI'], msg).join('') break case 'DATA': // falls through case 'MPI': str = msg.substring(0, HLP.readLen(msg) + 4) break default: str = msg.substring(0, DTS[a]) } data.push(str) msg = msg.substring(str.length) }) return data } // https://github.com/msgpack/msgpack-javascript/blob/master/msgpack.js var _bin2num = (function () { var i = 0, _bin2num = {} for (; i < 0x100; ++i) { _bin2num[String.fromCharCode(i)] = i // "\00" -> 0x00 } for (i = 0x80; i < 0x100; ++i) { // [Webkit][Gecko] _bin2num[String.fromCharCode(0xf700 + i)] = i // "\f780" -> 0x80 } return _bin2num }()) HLP.toByteArray = function (data) { var rv = [] , ary = data.split("") , i = -1 , iz = ary.length , remain = iz % 8 while (remain--) { ++i rv[i] = _bin2num[ary[i]] } remain = iz >> 3 while (remain--) { rv.push(_bin2num[ary[++i]], _bin2num[ary[++i]], _bin2num[ary[++i]], _bin2num[ary[++i]], _bin2num[ary[++i]], _bin2num[ary[++i]], _bin2num[ary[++i]], _bin2num[ary[++i]]) } return rv } }).call(this) ;(function () { "use strict"; var root = this var CryptoJS, BigInt, Worker, WWPath, HLP if (typeof module !== 'undefined' && module.exports) { module.exports = DSA CryptoJS = require('../vendor/crypto.js') BigInt = require('../vendor/bigint.js') WWPath = require('path').join(__dirname, '/dsa-webworker.js') HLP = require('./helpers.js') } else { // copy over and expose internals Object.keys(root.DSA).forEach(function (k) { DSA[k] = root.DSA[k] }) root.DSA = DSA CryptoJS = root.CryptoJS BigInt = root.BigInt Worker = root.Worker WWPath = 'dsa-webworker.js' HLP = DSA.HLP } var ZERO = BigInt.str2bigInt('0', 10) , ONE = BigInt.str2bigInt('1', 10) , TWO = BigInt.str2bigInt('2', 10) , KEY_TYPE = '\x00\x00' var DEBUG = false function timer() { var start = (new Date()).getTime() return function (s) { if (!DEBUG || typeof console === 'undefined') return var t = (new Date()).getTime() console.log(s + ': ' + (t - start)) start = t } } function makeRandom(min, max) { var c = BigInt.randBigInt(BigInt.bitSize(max)) if (!HLP.between(c, min, max)) return makeRandom(min, max) return c } // altered BigInt.randProbPrime() // n rounds of Miller Rabin (after trial division with small primes) var rpprb = [] function isProbPrime(k, n) { var i, B = 30000, l = BigInt.bitSize(k) var primes = BigInt.primes if (primes.length === 0) primes = BigInt.findPrimes(B) if (rpprb.length != k.length) rpprb = BigInt.dup(k) // check ans for divisibility by small primes up to B for (i = 0; (i < primes.length) && (primes[i] <= B); i++) if (BigInt.modInt(k, primes[i]) === 0 && !BigInt.equalsInt(k, primes[i])) return 0 // do n rounds of Miller Rabin, with random bases less than k for (i = 0; i < n; i++) { BigInt.randBigInt_(rpprb, l, 0) while(!BigInt.greater(k, rpprb)) // pick a random rpprb that's < k BigInt.randBigInt_(rpprb, l, 0) if (!BigInt.millerRabin(k, rpprb)) return 0 } return 1 } var bit_lengths = { '1024': { N: 160, repeat: 40 } // 40x should give 2^-80 confidence , '2048': { N: 224, repeat: 56 } } var primes = {} // follows go lang http://golang.org/src/pkg/crypto/dsa/dsa.go // fips version was removed in 0c99af0df3e7 function generatePrimes(bit_length) { var t = timer() // for debugging // number of MR tests to perform var repeat = bit_lengths[bit_length].repeat var N = bit_lengths[bit_length].N var LM1 = BigInt.twoToThe(bit_length - 1) var bl4 = 4 * bit_length var brk = false var q, p, rem, counter for (;;) { q = BigInt.randBigInt(N, 1) q[0] |= 1 if (!isProbPrime(q, repeat)) continue t('q') for (counter = 0; counter < bl4; counter++) { p = BigInt.randBigInt(bit_length, 1) p[0] |= 1 rem = BigInt.mod(p, q) rem = BigInt.sub(rem, ONE) p = BigInt.sub(p, rem) if (BigInt.greater(LM1, p)) continue if (!isProbPrime(p, repeat)) continue t('p') primes[bit_length] = { p: p, q: q } brk = true break } if (brk) break } var h = BigInt.dup(TWO) var pm1 = BigInt.sub(p, ONE) var e = BigInt.multMod(pm1, BigInt.inverseMod(q, p), p) var g for (;;) { g = BigInt.powMod(h, e, p) if (BigInt.equals(g, ONE)) { h = BigInt.add(h, ONE) continue } primes[bit_length].g = g t('g') return } throw new Error('Unreachable!') } function DSA(obj, opts) { if (!(this instanceof DSA)) return new DSA(obj, opts) // options opts = opts || {} // inherit if (obj) { var self = this ;['p', 'q', 'g', 'y', 'x'].forEach(function (prop) { self[prop] = obj[prop] }) this.type = obj.type || KEY_TYPE return } // default to 1024 var bit_length = parseInt(opts.bit_length ? opts.bit_length : 1024, 10) if (!bit_lengths[bit_length]) throw new Error('Unsupported bit length.') // set primes if (!primes[bit_length]) generatePrimes(bit_length) this.p = primes[bit_length].p this.q = primes[bit_length].q this.g = primes[bit_length].g // key type this.type = KEY_TYPE // private key this.x = makeRandom(ZERO, this.q) // public keys (p, q, g, y) this.y = BigInt.powMod(this.g, this.x, this.p) // nocache? if (opts.nocache) primes[bit_length] = null } DSA.prototype = { constructor: DSA, packPublic: function () { var str = this.type str += HLP.packMPI(this.p) str += HLP.packMPI(this.q) str += HLP.packMPI(this.g) str += HLP.packMPI(this.y) return str }, packPrivate: function () { var str = this.packPublic() + HLP.packMPI(this.x) str = CryptoJS.enc.Latin1.parse(str) return str.toString(CryptoJS.enc.Base64) }, // http://www.imperialviolet.org/2013/06/15/suddendeathentropy.html generateNonce: function (m) { var priv = BigInt.bigInt2bits(BigInt.trim(this.x, 0)) var rand = BigInt.bigInt2bits(BigInt.randBigInt(256)) var sha256 = CryptoJS.algo.SHA256.create() sha256.update(CryptoJS.enc.Latin1.parse(priv)) sha256.update(m) sha256.update(CryptoJS.enc.Latin1.parse(rand)) var hash = sha256.finalize() hash = HLP.bits2bigInt(hash.toString(CryptoJS.enc.Latin1)) BigInt.rightShift_(hash, 256 - BigInt.bitSize(this.q)) return HLP.between(hash, ZERO, this.q) ? hash : this.generateNonce(m) }, sign: function (m) { m = CryptoJS.enc.Latin1.parse(m) var b = BigInt.str2bigInt(m.toString(CryptoJS.enc.Hex), 16) var k, r = ZERO, s = ZERO while (BigInt.isZero(s) || BigInt.isZero(r)) { k = this.generateNonce(m) r = BigInt.mod(BigInt.powMod(this.g, k, this.p), this.q) if (BigInt.isZero(r)) continue s = BigInt.inverseMod(k, this.q) s = BigInt.mult(s, BigInt.add(b, BigInt.mult(this.x, r))) s = BigInt.mod(s, this.q) } return [r, s] }, fingerprint: function () { var pk = this.packPublic() if (this.type === KEY_TYPE) pk = pk.substring(2) pk = CryptoJS.enc.Latin1.parse(pk) return CryptoJS.SHA1(pk).toString(CryptoJS.enc.Hex) } } DSA.parsePublic = function (str, priv) { var fields = ['SHORT', 'MPI', 'MPI', 'MPI', 'MPI'] if (priv) fields.push('MPI') str = HLP.splitype(fields, str) var obj = { type: str[0] , p: HLP.readMPI(str[1]) , q: HLP.readMPI(str[2]) , g: HLP.readMPI(str[3]) , y: HLP.readMPI(str[4]) } if (priv) obj.x = HLP.readMPI(str[5]) return new DSA(obj) } function tokenizeStr(str) { var start, end start = str.indexOf("(") end = str.lastIndexOf(")") if (start < 0 || end < 0) throw new Error("Malformed S-Expression") str = str.substring(start + 1, end) var splt = str.search(/\s/) var obj = { type: str.substring(0, splt) , val: [] } str = str.substring(splt + 1, end) start = str.indexOf("(") if (start < 0) obj.val.push(str) else { var i, len, ss, es while (start > -1) { i = start + 1 len = str.length for (ss = 1, es = 0; i < len && es < ss; i++) { if (str[i] === "(") ss++ if (str[i] === ")") es++ } obj.val.push(tokenizeStr(str.substring(start, ++i))) str = str.substring(++i) start = str.indexOf("(") } } return obj } function parseLibotr(obj) { if (!obj.type) throw new Error("Parse error.") var o, val if (obj.type === "privkeys") { o = [] obj.val.forEach(function (i) { o.push(parseLibotr(i)) }) return o } o = {} obj.val.forEach(function (i) { val = i.val[0] if (typeof val === "string") { if (val.indexOf("#") === 0) { val = val.substring(1, val.lastIndexOf("#")) val = BigInt.str2bigInt(val, 16) } } else { val = parseLibotr(i) } o[i.type] = val }) return o } DSA.parsePrivate = function (str, libotr) { if (!libotr) { str = CryptoJS.enc.Base64.parse(str) str = str.toString(CryptoJS.enc.Latin1) return DSA.parsePublic(str, true) } // only returning the first key found return parseLibotr(tokenizeStr(str))[0]["private-key"].dsa } DSA.verify = function (key, m, r, s) { if (!HLP.between(r, ZERO, key.q) || !HLP.between(s, ZERO, key.q)) return false var hm = CryptoJS.enc.Latin1.parse(m) // CryptoJS.SHA1(m) hm = BigInt.str2bigInt(hm.toString(CryptoJS.enc.Hex), 16) var w = BigInt.inverseMod(s, key.q) var u1 = BigInt.multMod(hm, w, key.q) var u2 = BigInt.multMod(r, w, key.q) u1 = BigInt.powMod(key.g, u1, key.p) u2 = BigInt.powMod(key.y, u2, key.p) var v = BigInt.mod(BigInt.multMod(u1, u2, key.p), key.q) return BigInt.equals(v, r) } DSA.createInWebWorker = function (options, cb) { var opts = { path: WWPath , seed: BigInt.getSeed } if (options && typeof options === 'object') Object.keys(options).forEach(function (k) { opts[k] = options[k] }) // load optional dep. in node if (typeof module !== 'undefined' && module.exports) Worker = require('webworker-threads').Worker var worker = new Worker(opts.path) worker.onmessage = function (e) { var data = e.data switch (data.type) { case "debug": if (!DEBUG || typeof console === 'undefined') return console.log(data.val) break; case "data": worker.terminate() cb(DSA.parsePrivate(data.val)) break; default: throw new Error("Unrecognized type.") } } worker.postMessage({ seed: opts.seed() , imports: opts.imports , debug: DEBUG }) } }).call(this) ;(function () { "use strict"; var root = this var Parse = {}, CryptoJS, CONST, HLP if (typeof module !== 'undefined' && module.exports) { module.exports = Parse CryptoJS = require('../vendor/crypto.js') CONST = require('./const.js') HLP = require('./helpers.js') } else { root.OTR.Parse = Parse CryptoJS = root.CryptoJS CONST = root.OTR.CONST HLP = root.OTR.HLP } // whitespace tags var tags = {} tags[CONST.WHITESPACE_TAG_V2] = CONST.OTR_VERSION_2 tags[CONST.WHITESPACE_TAG_V3] = CONST.OTR_VERSION_3 Parse.parseMsg = function (otr, msg) { var ver = [] // is this otr? var start = msg.indexOf(CONST.OTR_TAG) if (!~start) { // restart fragments this.initFragment(otr) // whitespace tags ind = msg.indexOf(CONST.WHITESPACE_TAG) if (~ind) { msg = msg.split('') msg.splice(ind, 16) var tag, len = msg.length for (; ind < len;) { tag = msg.slice(ind, ind + 8).join('') if (Object.hasOwnProperty.call(tags, tag)) { msg.splice(ind, 8) ver.push(tags[tag]) continue } ind += 8 } msg = msg.join('') } return { msg: msg, ver: ver } } var ind = start + CONST.OTR_TAG.length var com = msg[ind] // message fragment if (com === ',' || com === '|') { return this.msgFragment(otr, msg.substring(ind + 1), (com === '|')) } this.initFragment(otr) // query message if (~['?', 'v'].indexOf(com)) { // version 1 if (msg[ind] === '?') { ver.push(CONST.OTR_VERSION_1) ind += 1 } // other versions var vers = { '2': CONST.OTR_VERSION_2 , '3': CONST.OTR_VERSION_3 } var qs = msg.substring(ind + 1) var qi = qs.indexOf('?') if (qi >= 1) { qs = qs.substring(0, qi).split('') if (msg[ind] === 'v') { qs.forEach(function (q) { if (Object.hasOwnProperty.call(vers, q)) ver.push(vers[q]) }) } } return { cls: 'query', ver: ver } } // otr message if (com === ':') { ind += 1 var info = msg.substring(ind, ind + 4) if (info.length < 4) return { msg: msg } info = CryptoJS.enc.Base64.parse(info).toString(CryptoJS.enc.Latin1) var version = info.substring(0, 2) var type = info.substring(2) // supporting otr versions 2 and 3 if (!otr['ALLOW_V' + HLP.unpackSHORT(version)]) return { msg: msg } ind += 4 var end = msg.substring(ind).indexOf('.') if (!~end) return { msg: msg } msg = CryptoJS.enc.Base64.parse(msg.substring(ind, ind + end)) msg = CryptoJS.enc.Latin1.stringify(msg) // instance tags var instance_tags if (version === CONST.OTR_VERSION_3) { instance_tags = msg.substring(0, 8) msg = msg.substring(8) } var cls if (~['\x02', '\x0a', '\x11', '\x12'].indexOf(type)) { cls = 'ake' } else if (type === '\x03') { cls = 'data' } return { version: version , type: type , msg: msg , cls: cls , instance_tags: instance_tags } } // error message if (msg.substring(ind, ind + 7) === ' Error:') { if (otr.ERROR_START_AKE) { otr.sendQueryMsg() } return { msg: msg.substring(ind + 7), cls: 'error' } } return { msg: msg } } Parse.initFragment = function (otr) { otr.fragment = { s: '', j: 0, k: 0 } } Parse.msgFragment = function (otr, msg, v3) { msg = msg.split(',') // instance tags if (v3) { var its = msg.shift().split('|') var their_it = HLP.packINT(parseInt(its[0], 16)) var our_it = HLP.packINT(parseInt(its[1], 16)) if (otr.checkInstanceTags(their_it + our_it)) return // ignore } if (msg.length < 4 || isNaN(parseInt(msg[0], 10)) || isNaN(parseInt(msg[1], 10)) ) return var k = parseInt(msg[0], 10) var n = parseInt(msg[1], 10) msg = msg[2] if (n < k || n === 0 || k === 0) { this.initFragment(otr) return } if (k === 1) { this.initFragment(otr) otr.fragment = { k: 1, n: n, s: msg } } else if (n === otr.fragment.n && k === (otr.fragment.k + 1)) { otr.fragment.s += msg otr.fragment.k += 1 } else { this.initFragment(otr) } if (n === k) { msg = otr.fragment.s this.initFragment(otr) return this.parseMsg(otr, msg) } return } }).call(this) ;(function () { "use strict"; var root = this var CryptoJS, BigInt, CONST, HLP, DSA if (typeof module !== 'undefined' && module.exports) { module.exports = AKE CryptoJS = require('../vendor/crypto.js') BigInt = require('../vendor/bigint.js') CONST = require('./const.js') HLP = require('./helpers.js') DSA = require('./dsa.js') } else { root.OTR.AKE = AKE CryptoJS = root.CryptoJS BigInt = root.BigInt CONST = root.OTR.CONST HLP = root.OTR.HLP DSA = root.DSA } // diffie-hellman modulus // see group 5, RFC 3526 var N = BigInt.str2bigInt(CONST.N, 16) var N_MINUS_2 = BigInt.sub(N, BigInt.str2bigInt('2', 10)) function hMac(gx, gy, pk, kid, m) { var pass = CryptoJS.enc.Latin1.parse(m) var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, pass) hmac.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(gx))) hmac.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(gy))) hmac.update(CryptoJS.enc.Latin1.parse(pk)) hmac.update(CryptoJS.enc.Latin1.parse(kid)) return (hmac.finalize()).toString(CryptoJS.enc.Latin1) } // AKE constructor function AKE(otr) { if (!(this instanceof AKE)) return new AKE(otr) // otr instance this.otr = otr // our keys this.our_dh = otr.our_old_dh this.our_keyid = otr.our_keyid - 1 // their keys this.their_y = null this.their_keyid = null this.their_priv_pk = null // state this.ssid = null this.transmittedRS = false this.r = null // bind methods var self = this ;['sendMsg'].forEach(function (meth) { self[meth] = self[meth].bind(self) }) } AKE.prototype = { constructor: AKE, createKeys: function(g) { var s = BigInt.powMod(g, this.our_dh.privateKey, N) var secbytes = HLP.packMPI(s) this.ssid = HLP.mask(HLP.h2('\x00', secbytes), 0, 64) // first 64-bits var tmp = HLP.h2('\x01', secbytes) this.c = HLP.mask(tmp, 0, 128) // first 128-bits this.c_prime = HLP.mask(tmp, 128, 128) // second 128-bits this.m1 = HLP.h2('\x02', secbytes) this.m2 = HLP.h2('\x03', secbytes) this.m1_prime = HLP.h2('\x04', secbytes) this.m2_prime = HLP.h2('\x05', secbytes) }, verifySignMac: function (mac, aesctr, m2, c, their_y, our_dh_pk, m1, ctr) { // verify mac var vmac = HLP.makeMac(aesctr, m2) if (!HLP.compare(mac, vmac)) return ['MACs do not match.'] // decrypt x var x = HLP.decryptAes(aesctr.substring(4), c, ctr) x = HLP.splitype(['PUBKEY', 'INT', 'SIG'], x.toString(CryptoJS.enc.Latin1)) var m = hMac(their_y, our_dh_pk, x[0], x[1], m1) var pub = DSA.parsePublic(x[0]) var r = HLP.bits2bigInt(x[2].substring(0, 20)) var s = HLP.bits2bigInt(x[2].substring(20)) // verify sign m if (!DSA.verify(pub, m, r, s)) return ['Cannot verify signature of m.'] return [null, HLP.readLen(x[1]), pub] }, makeM: function (their_y, m1, c, m2) { var pk = this.otr.priv.packPublic() var kid = HLP.packINT(this.our_keyid) var m = hMac(this.our_dh.publicKey, their_y, pk, kid, m1) m = this.otr.priv.sign(m) var msg = pk + kid msg += BigInt.bigInt2bits(m[0], 20) // pad to 20 bytes msg += BigInt.bigInt2bits(m[1], 20) msg = CryptoJS.enc.Latin1.parse(msg) var aesctr = HLP.packData(HLP.encryptAes(msg, c, HLP.packCtr(0))) var mac = HLP.makeMac(aesctr, m2) return aesctr + mac }, akeSuccess: function (version) { HLP.debug.call(this.otr, 'success') if (BigInt.equals(this.their_y, this.our_dh.publicKey)) return this.otr.error('equal keys - we have a problem.', true) this.otr.our_old_dh = this.our_dh this.otr.their_priv_pk = this.their_priv_pk if (!( (this.their_keyid === this.otr.their_keyid && BigInt.equals(this.their_y, this.otr.their_y)) || (this.their_keyid === (this.otr.their_keyid - 1) && BigInt.equals(this.their_y, this.otr.their_old_y)) )) { this.otr.their_y = this.their_y this.otr.their_old_y = null this.otr.their_keyid = this.their_keyid // rotate keys this.otr.sessKeys[0] = [ new this.otr.DHSession( this.otr.our_dh , this.otr.their_y ), null ] this.otr.sessKeys[1] = [ new this.otr.DHSession( this.otr.our_old_dh , this.otr.their_y ), null ] } // ake info this.otr.ssid = this.ssid this.otr.transmittedRS = this.transmittedRS this.otr_version = version // go encrypted this.otr.authstate = CONST.AUTHSTATE_NONE this.otr.msgstate = CONST.MSGSTATE_ENCRYPTED // null out values this.r = null this.myhashed = null this.dhcommit = null this.encrypted = null this.hashed = null this.otr.trigger('status', [CONST.STATUS_AKE_SUCCESS]) // send stored msgs this.otr.sendStored() }, handleAKE: function (msg) { var send, vsm, type var version = msg.version switch (msg.type) { case '\x02': HLP.debug.call(this.otr, 'd-h key message') msg = HLP.splitype(['DATA', 'DATA'], msg.msg) if (this.otr.authstate === CONST.AUTHSTATE_AWAITING_DHKEY) { var ourHash = HLP.readMPI(this.myhashed) var theirHash = HLP.readMPI(msg[1]) if (BigInt.greater(ourHash, theirHash)) { type = '\x02' send = this.dhcommit break // ignore } else { // forget this.our_dh = this.otr.dh() this.otr.authstate = CONST.AUTHSTATE_NONE this.r = null this.myhashed = null } } else if ( this.otr.authstate === CONST.AUTHSTATE_AWAITING_SIG ) this.our_dh = this.otr.dh() this.otr.authstate = CONST.AUTHSTATE_AWAITING_REVEALSIG this.encrypted = msg[0].substring(4) this.hashed = msg[1].substring(4) type = '\x0a' send = HLP.packMPI(this.our_dh.publicKey) break case '\x0a': HLP.debug.call(this.otr, 'reveal signature message') msg = HLP.splitype(['MPI'], msg.msg) if (this.otr.authstate !== CONST.AUTHSTATE_AWAITING_DHKEY) { if (this.otr.authstate === CONST.AUTHSTATE_AWAITING_SIG) { if (!BigInt.equals(this.their_y, HLP.readMPI(msg[0]))) return } else { return // ignore } } this.otr.authstate = CONST.AUTHSTATE_AWAITING_SIG this.their_y = HLP.readMPI(msg[0]) // verify gy is legal 2 <= gy <= N-2 if (!HLP.checkGroup(this.their_y, N_MINUS_2)) return this.otr.error('Illegal g^y.', true) this.createKeys(this.their_y) type = '\x11' send = HLP.packMPI(this.r) send += this.makeM(this.their_y, this.m1, this.c, this.m2) this.m1 = null this.m2 = null this.c = null break case '\x11': HLP.debug.call(this.otr, 'signature message') if (this.otr.authstate !== CONST.AUTHSTATE_AWAITING_REVEALSIG) return // ignore msg = HLP.splitype(['DATA', 'DATA', 'MAC'], msg.msg) this.r = HLP.readMPI(msg[0]) // decrypt their_y var key = CryptoJS.enc.Hex.parse(BigInt.bigInt2str(this.r, 16)) key = CryptoJS.enc.Latin1.stringify(key) var gxmpi = HLP.decryptAes(this.encrypted, key, HLP.packCtr(0)) gxmpi = gxmpi.toString(CryptoJS.enc.Latin1) this.their_y = HLP.readMPI(gxmpi) // verify hash var hash = CryptoJS.SHA256(CryptoJS.enc.Latin1.parse(gxmpi)) if (!HLP.compare(this.hashed, hash.toString(CryptoJS.enc.Latin1))) return this.otr.error('Hashed g^x does not match.', true) // verify gx is legal 2 <= g^x <= N-2 if (!HLP.checkGroup(this.their_y, N_MINUS_2)) return this.otr.error('Illegal g^x.', true) this.createKeys(this.their_y) vsm = this.verifySignMac( msg[2] , msg[1] , this.m2 , this.c , this.their_y , this.our_dh.publicKey , this.m1 , HLP.packCtr(0) ) if (vsm[0]) return this.otr.error(vsm[0], true) // store their key this.their_keyid = vsm[1] this.their_priv_pk = vsm[2] send = this.makeM( this.their_y , this.m1_prime , this.c_prime , this.m2_prime ) this.m1 = null this.m2 = null this.m1_prime = null this.m2_prime = null this.c = null this.c_prime = null this.sendMsg(version, '\x12', send) this.akeSuccess(version) return case '\x12': HLP.debug.call(this.otr, 'data message') if (this.otr.authstate !== CONST.AUTHSTATE_AWAITING_SIG) return // ignore msg = HLP.splitype(['DATA', 'MAC'], msg.msg) vsm = this.verifySignMac( msg[1] , msg[0] , this.m2_prime , this.c_prime , this.their_y , this.our_dh.publicKey , this.m1_prime , HLP.packCtr(0) ) if (vsm[0]) return this.otr.error(vsm[0], true) // store their key this.their_keyid = vsm[1] this.their_priv_pk = vsm[2] this.m1_prime = null this.m2_prime = null this.c_prime = null this.transmittedRS = true this.akeSuccess(version) return default: return // ignore } this.sendMsg(version, type, send) }, sendMsg: function (version, type, msg) { var send = version + type var v3 = (version === CONST.OTR_VERSION_3) // instance tags for v3 if (v3) { HLP.debug.call(this.otr, 'instance tags') send += this.otr.our_instance_tag send += this.otr.their_instance_tag } send += msg // fragment message if necessary send = HLP.wrapMsg( send , this.otr.fragment_size , v3 , this.otr.our_instance_tag , this.otr.their_instance_tag ) if (send[0]) return this.otr.error(send[0]) this.otr.io(send[1]) }, initiateAKE: function (version) { HLP.debug.call(this.otr, 'd-h commit message') this.otr.trigger('status', [CONST.STATUS_AKE_INIT]) this.otr.authstate = CONST.AUTHSTATE_AWAITING_DHKEY var gxmpi = HLP.packMPI(this.our_dh.publicKey) gxmpi = CryptoJS.enc.Latin1.parse(gxmpi) this.r = BigInt.randBigInt(128) var key = CryptoJS.enc.Hex.parse(BigInt.bigInt2str(this.r, 16)) key = CryptoJS.enc.Latin1.stringify(key) this.myhashed = CryptoJS.SHA256(gxmpi) this.myhashed = HLP.packData(this.myhashed.toString(CryptoJS.enc.Latin1)) this.dhcommit = HLP.packData(HLP.encryptAes(gxmpi, key, HLP.packCtr(0))) this.dhcommit += this.myhashed this.sendMsg(version, '\x02', this.dhcommit) } } }).call(this) ;(function () { "use strict"; var root = this var CryptoJS, BigInt, EventEmitter, CONST, HLP if (typeof module !== 'undefined' && module.exports) { module.exports = SM CryptoJS = require('../vendor/crypto.js') BigInt = require('../vendor/bigint.js') EventEmitter = require('../vendor/eventemitter.js') CONST = require('./const.js') HLP = require('./helpers.js') } else { root.OTR.SM = SM CryptoJS = root.CryptoJS BigInt = root.BigInt EventEmitter = root.EventEmitter CONST = root.OTR.CONST HLP = root.OTR.HLP } // diffie-hellman modulus and generator // see group 5, RFC 3526 var G = BigInt.str2bigInt(CONST.G, 10) var N = BigInt.str2bigInt(CONST.N, 16) var N_MINUS_2 = BigInt.sub(N, BigInt.str2bigInt('2', 10)) // to calculate D's for zero-knowledge proofs var Q = BigInt.sub(N, BigInt.str2bigInt('1', 10)) BigInt.divInt_(Q, 2) // meh function SM(reqs) { if (!(this instanceof SM)) return new SM(reqs) this.version = 1 this.our_fp = reqs.our_fp this.their_fp = reqs.their_fp this.ssid = reqs.ssid this.debug = !!reqs.debug // initial state this.init() } // inherit from EE HLP.extend(SM, EventEmitter) // set the initial values // also used when aborting SM.prototype.init = function () { this.smpstate = CONST.SMPSTATE_EXPECT1 this.secret = null } SM.prototype.makeSecret = function (our, secret) { var sha256 = CryptoJS.algo.SHA256.create() sha256.update(CryptoJS.enc.Latin1.parse(HLP.packBytes(this.version, 1))) sha256.update(CryptoJS.enc.Hex.parse(our ? this.our_fp : this.their_fp)) sha256.update(CryptoJS.enc.Hex.parse(our ? this.their_fp : this.our_fp)) sha256.update(CryptoJS.enc.Latin1.parse(this.ssid)) sha256.update(CryptoJS.enc.Latin1.parse(secret)) var hash = sha256.finalize() this.secret = HLP.bits2bigInt(hash.toString(CryptoJS.enc.Latin1)) } SM.prototype.makeG2s = function () { this.a2 = HLP.randomExponent() this.a3 = HLP.randomExponent() this.g2a = BigInt.powMod(G, this.a2, N) this.g3a = BigInt.powMod(G, this.a3, N) if ( !HLP.checkGroup(this.g2a, N_MINUS_2) || !HLP.checkGroup(this.g3a, N_MINUS_2) ) this.makeG2s() } SM.prototype.computeGs = function (g2a, g3a) { this.g2 = BigInt.powMod(g2a, this.a2, N) this.g3 = BigInt.powMod(g3a, this.a3, N) } SM.prototype.computePQ = function (r) { this.p = BigInt.powMod(this.g3, r, N) this.q = HLP.multPowMod(G, r, this.g2, this.secret, N) } SM.prototype.computeR = function () { this.r = BigInt.powMod(this.QoQ, this.a3, N) } SM.prototype.computeRab = function (r) { return BigInt.powMod(r, this.a3, N) } SM.prototype.computeC = function (v, r) { return HLP.smpHash(v, BigInt.powMod(G, r, N)) } SM.prototype.computeD = function (r, a, c) { return BigInt.subMod(r, BigInt.multMod(a, c, Q), Q) } // the bulk of the work SM.prototype.handleSM = function (msg) { var send, r2, r3, r7, t1, t2, t3, t4, rab, tmp2, cR, d7, ms, trust var expectStates = { 2: CONST.SMPSTATE_EXPECT1 , 3: CONST.SMPSTATE_EXPECT2 , 4: CONST.SMPSTATE_EXPECT3 , 5: CONST.SMPSTATE_EXPECT4 , 7: CONST.SMPSTATE_EXPECT1 } if (msg.type === 6) { this.init() this.trigger('abort') return } // abort! there was an error if (this.smpstate !== expectStates[msg.type]) return this.abort() switch (this.smpstate) { case CONST.SMPSTATE_EXPECT1: HLP.debug.call(this, 'smp tlv 2') // user specified question var ind, question if (msg.type === 7) { ind = msg.msg.indexOf('\x00') question = msg.msg.substring(0, ind) msg.msg = msg.msg.substring(ind + 1) } // 0:g2a, 1:c2, 2:d2, 3:g3a, 4:c3, 5:d3 ms = HLP.readLen(msg.msg.substr(0, 4)) if (ms !== 6) return this.abort() msg = HLP.unpackMPIs(6, msg.msg.substring(4)) if ( !HLP.checkGroup(msg[0], N_MINUS_2) || !HLP.checkGroup(msg[3], N_MINUS_2) ) return this.abort() // verify znp's if (!HLP.ZKP(1, msg[1], HLP.multPowMod(G, msg[2], msg[0], msg[1], N))) return this.abort() if (!HLP.ZKP(2, msg[4], HLP.multPowMod(G, msg[5], msg[3], msg[4], N))) return this.abort() this.g3ao = msg[3] // save for later this.makeG2s() // zero-knowledge proof that the exponents // associated with g2a & g3a are known r2 = HLP.randomExponent() r3 = HLP.randomExponent() this.c2 = this.computeC(3, r2) this.c3 = this.computeC(4, r3) this.d2 = this.computeD(r2, this.a2, this.c2) this.d3 = this.computeD(r3, this.a3, this.c3) this.computeGs(msg[0], msg[3]) this.smpstate = CONST.SMPSTATE_EXPECT0 // assume utf8 question question = CryptoJS.enc.Latin1 .parse(question) .toString(CryptoJS.enc.Utf8) // invoke question this.trigger('question', [question]) return case CONST.SMPSTATE_EXPECT2: HLP.debug.call(this, 'smp tlv 3') // 0:g2a, 1:c2, 2:d2, 3:g3a, 4:c3, 5:d3, 6:p, 7:q, 8:cP, 9:d5, 10:d6 ms = HLP.readLen(msg.msg.substr(0, 4)) if (ms !== 11) return this.abort() msg = HLP.unpackMPIs(11, msg.msg.substring(4)) if ( !HLP.checkGroup(msg[0], N_MINUS_2) || !HLP.checkGroup(msg[3], N_MINUS_2) || !HLP.checkGroup(msg[6], N_MINUS_2) || !HLP.checkGroup(msg[7], N_MINUS_2) ) return this.abort() // verify znp of c3 / c3 if (!HLP.ZKP(3, msg[1], HLP.multPowMod(G, msg[2], msg[0], msg[1], N))) return this.abort() if (!HLP.ZKP(4, msg[4], HLP.multPowMod(G, msg[5], msg[3], msg[4], N))) return this.abort() this.g3ao = msg[3] // save for later this.computeGs(msg[0], msg[3]) // verify znp of cP t1 = HLP.multPowMod(this.g3, msg[9], msg[6], msg[8], N) t2 = HLP.multPowMod(G, msg[9], this.g2, msg[10], N) t2 = BigInt.multMod(t2, BigInt.powMod(msg[7], msg[8], N), N) if (!HLP.ZKP(5, msg[8], t1, t2)) return this.abort() var r4 = HLP.randomExponent() this.computePQ(r4) // zero-knowledge proof that P & Q // were generated according to the protocol var r5 = HLP.randomExponent() var r6 = HLP.randomExponent() var tmp = HLP.multPowMod(G, r5, this.g2, r6, N) var cP = HLP.smpHash(6, BigInt.powMod(this.g3, r5, N), tmp) var d5 = this.computeD(r5, r4, cP) var d6 = this.computeD(r6, this.secret, cP) // store these this.QoQ = BigInt.divMod(this.q, msg[7], N) this.PoP = BigInt.divMod(this.p, msg[6], N) this.computeR() // zero-knowledge proof that R // was generated according to the protocol r7 = HLP.randomExponent() tmp2 = BigInt.powMod(this.QoQ, r7, N) cR = HLP.smpHash(7, BigInt.powMod(G, r7, N), tmp2) d7 = this.computeD(r7, this.a3, cR) this.smpstate = CONST.SMPSTATE_EXPECT4 send = HLP.packINT(8) + HLP.packMPIs([ this.p , this.q , cP , d5 , d6 , this.r , cR , d7 ]) // TLV send = HLP.packTLV(4, send) break case CONST.SMPSTATE_EXPECT3: HLP.debug.call(this, 'smp tlv 4') // 0:p, 1:q, 2:cP, 3:d5, 4:d6, 5:r, 6:cR, 7:d7 ms = HLP.readLen(msg.msg.substr(0, 4)) if (ms !== 8) return this.abort() msg = HLP.unpackMPIs(8, msg.msg.substring(4)) if ( !HLP.checkGroup(msg[0], N_MINUS_2) || !HLP.checkGroup(msg[1], N_MINUS_2) || !HLP.checkGroup(msg[5], N_MINUS_2) ) return this.abort() // verify znp of cP t1 = HLP.multPowMod(this.g3, msg[3], msg[0], msg[2], N) t2 = HLP.multPowMod(G, msg[3], this.g2, msg[4], N) t2 = BigInt.multMod(t2, BigInt.powMod(msg[1], msg[2], N), N) if (!HLP.ZKP(6, msg[2], t1, t2)) return this.abort() // verify znp of cR t3 = HLP.multPowMod(G, msg[7], this.g3ao, msg[6], N) this.QoQ = BigInt.divMod(msg[1], this.q, N) // save Q over Q t4 = HLP.multPowMod(this.QoQ, msg[7], msg[5], msg[6], N) if (!HLP.ZKP(7, msg[6], t3, t4)) return this.abort() this.computeR() // zero-knowledge proof that R // was generated according to the protocol r7 = HLP.randomExponent() tmp2 = BigInt.powMod(this.QoQ, r7, N) cR = HLP.smpHash(8, BigInt.powMod(G, r7, N), tmp2) d7 = this.computeD(r7, this.a3, cR) send = HLP.packINT(3) + HLP.packMPIs([ this.r, cR, d7 ]) send = HLP.packTLV(5, send) rab = this.computeRab(msg[5]) trust = !!BigInt.equals(rab, BigInt.divMod(msg[0], this.p, N)) this.trigger('trust', [trust, 'answered']) this.init() break case CONST.SMPSTATE_EXPECT4: HLP.debug.call(this, 'smp tlv 5') // 0:r, 1:cR, 2:d7 ms = HLP.readLen(msg.msg.substr(0, 4)) if (ms !== 3) return this.abort() msg = HLP.unpackMPIs(3, msg.msg.substring(4)) if (!HLP.checkGroup(msg[0], N_MINUS_2)) return this.abort() // verify znp of cR t3 = HLP.multPowMod(G, msg[2], this.g3ao, msg[1], N) t4 = HLP.multPowMod(this.QoQ, msg[2], msg[0], msg[1], N) if (!HLP.ZKP(8, msg[1], t3, t4)) return this.abort() rab = this.computeRab(msg[0]) trust = !!BigInt.equals(rab, this.PoP) this.trigger('trust', [trust, 'asked']) this.init() return } this.sendMsg(send) } // send a message SM.prototype.sendMsg = function (send) { this.trigger('send', [this.ssid, '\x00' + send]) } SM.prototype.rcvSecret = function (secret, question) { HLP.debug.call(this, 'receive secret') var fn, our = false if (this.smpstate === CONST.SMPSTATE_EXPECT0) { fn = this.answer } else { fn = this.initiate our = true } this.makeSecret(our, secret) fn.call(this, question) } SM.prototype.answer = function () { HLP.debug.call(this, 'smp answer') var r4 = HLP.randomExponent() this.computePQ(r4) // zero-knowledge proof that P & Q // were generated according to the protocol var r5 = HLP.randomExponent() var r6 = HLP.randomExponent() var tmp = HLP.multPowMod(G, r5, this.g2, r6, N) var cP = HLP.smpHash(5, BigInt.powMod(this.g3, r5, N), tmp) var d5 = this.computeD(r5, r4, cP) var d6 = this.computeD(r6, this.secret, cP) this.smpstate = CONST.SMPSTATE_EXPECT3 var send = HLP.packINT(11) + HLP.packMPIs([ this.g2a , this.c2 , this.d2 , this.g3a , this.c3 , this.d3 , this.p , this.q , cP , d5 , d6 ]) this.sendMsg(HLP.packTLV(3, send)) } SM.prototype.initiate = function (question) { HLP.debug.call(this, 'smp initiate') if (this.smpstate !== CONST.SMPSTATE_EXPECT1) this.abort() // abort + restart this.makeG2s() // zero-knowledge proof that the exponents // associated with g2a & g3a are known var r2 = HLP.randomExponent() var r3 = HLP.randomExponent() this.c2 = this.computeC(1, r2) this.c3 = this.computeC(2, r3) this.d2 = this.computeD(r2, this.a2, this.c2) this.d3 = this.computeD(r3, this.a3, this.c3) // set the next expected state this.smpstate = CONST.SMPSTATE_EXPECT2 var send = '' var type = 2 if (question) { send += question send += '\x00' type = 7 } send += HLP.packINT(6) + HLP.packMPIs([ this.g2a , this.c2 , this.d2 , this.g3a , this.c3 , this.d3 ]) this.sendMsg(HLP.packTLV(type, send)) } SM.prototype.abort = function () { this.init() this.sendMsg(HLP.packTLV(6, '')) this.trigger('abort') } }).call(this) ;(function () { "use strict"; var root = this var CryptoJS, BigInt, EventEmitter, Worker, SMWPath , CONST, HLP, Parse, AKE, SM, DSA if (typeof module !== 'undefined' && module.exports) { module.exports = OTR CryptoJS = require('../vendor/crypto.js') BigInt = require('../vendor/bigint.js') EventEmitter = require('../vendor/eventemitter.js') SMWPath = require('path').join(__dirname, '/sm-webworker.js') CONST = require('./const.js') HLP = require('./helpers.js') Parse = require('./parse.js') AKE = require('./ake.js') SM = require('./sm.js') DSA = require('./dsa.js') // expose CONST for consistency with docs OTR.CONST = CONST } else { // copy over and expose internals Object.keys(root.OTR).forEach(function (k) { OTR[k] = root.OTR[k] }) root.OTR = OTR CryptoJS = root.CryptoJS BigInt = root.BigInt EventEmitter = root.EventEmitter Worker = root.Worker SMWPath = 'sm-webworker.js' CONST = OTR.CONST HLP = OTR.HLP Parse = OTR.Parse AKE = OTR.AKE SM = OTR.SM DSA = root.DSA } // diffie-hellman modulus and generator // see group 5, RFC 3526 var G = BigInt.str2bigInt(CONST.G, 10) var N = BigInt.str2bigInt(CONST.N, 16) // JavaScript integers var MAX_INT = Math.pow(2, 53) - 1 // doubles var MAX_UINT = Math.pow(2, 31) - 1 // bitwise operators // OTR contructor function OTR(options) { if (!(this instanceof OTR)) return new OTR(options) // options options = options || {} // private keys if (options.priv && !(options.priv instanceof DSA)) throw new Error('Requires long-lived DSA key.') this.priv = options.priv ? options.priv : new DSA() this.fragment_size = options.fragment_size || 0 if (this.fragment_size < 0) throw new Error('Fragment size must be a positive integer.') this.send_interval = options.send_interval || 0 if (this.send_interval < 0) throw new Error('Send interval must be a positive integer.') this.outgoing = [] // instance tag this.our_instance_tag = options.instance_tag || OTR.makeInstanceTag() // debug this.debug = !!options.debug // smp in webworker options // this is still experimental and undocumented this.smw = options.smw // init vals this.init() // bind methods var self = this ;['sendMsg', 'receiveMsg'].forEach(function (meth) { self[meth] = self[meth].bind(self) }) EventEmitter.call(this) } // inherit from EE HLP.extend(OTR, EventEmitter) // add to prototype OTR.prototype.init = function () { this.msgstate = CONST.MSGSTATE_PLAINTEXT this.authstate = CONST.AUTHSTATE_NONE this.ALLOW_V2 = true this.ALLOW_V3 = true this.REQUIRE_ENCRYPTION = false this.SEND_WHITESPACE_TAG = false this.WHITESPACE_START_AKE = false this.ERROR_START_AKE = false Parse.initFragment(this) // their keys this.their_y = null this.their_old_y = null this.their_keyid = 0 this.their_priv_pk = null this.their_instance_tag = '\x00\x00\x00\x00' // our keys this.our_dh = this.dh() this.our_old_dh = this.dh() this.our_keyid = 2 // session keys this.sessKeys = [ new Array(2), new Array(2) ] // saved this.storedMgs = [] this.oldMacKeys = [] // smp this.sm = null // initialized after AKE // when ake is complete // save their keys and the session this._akeInit() // receive plaintext message since switching to plaintext // used to decide when to stop sending pt tags when SEND_WHITESPACE_TAG this.receivedPlaintext = false } OTR.prototype._akeInit = function () { this.ake = new AKE(this) this.transmittedRS = false this.ssid = null } // smp over webworker OTR.prototype._SMW = function (otr, reqs) { this.otr = otr var opts = { path: SMWPath , seed: BigInt.getSeed } if (typeof otr.smw === 'object') Object.keys(otr.smw).forEach(function (k) { opts[k] = otr.smw[k] }) // load optional dep. in node if (typeof module !== 'undefined' && module.exports) Worker = require('webworker-threads').Worker this.worker = new Worker(opts.path) var self = this this.worker.onmessage = function (e) { var d = e.data if (!d) return self.trigger(d.method, d.args) } this.worker.postMessage({ type: 'seed' , seed: opts.seed() , imports: opts.imports }) this.worker.postMessage({ type: 'init' , reqs: reqs }) } // inherit from EE HLP.extend(OTR.prototype._SMW, EventEmitter) // shim sm methods ;['handleSM', 'rcvSecret', 'abort'].forEach(function (m) { OTR.prototype._SMW.prototype[m] = function () { this.worker.postMessage({ type: 'method' , method: m , args: Array.prototype.slice.call(arguments, 0) }) } }) OTR.prototype._smInit = function () { var reqs = { ssid: this.ssid , our_fp: this.priv.fingerprint() , their_fp: this.their_priv_pk.fingerprint() , debug: this.debug } if (this.smw) { if (this.sm) this.sm.worker.terminate() // destroy prev webworker this.sm = new this._SMW(this, reqs) } else { this.sm = new SM(reqs) } var self = this ;['trust', 'abort', 'question'].forEach(function (e) { self.sm.on(e, function () { self.trigger('smp', [e].concat(Array.prototype.slice.call(arguments))) }) }) this.sm.on('send', function (ssid, send) { if (self.ssid === ssid) { send = self.prepareMsg(send) self.io(send) } }) } OTR.prototype.io = function (msg, meta) { // buffer msg = ([].concat(msg)).map(function(m){ return { msg: m, meta: meta } }) this.outgoing = this.outgoing.concat(msg) var self = this ;(function send(first) { if (!first) { if (!self.outgoing.length) return var elem = self.outgoing.shift() self.trigger('io', [elem.msg, elem.meta]) } setTimeout(send, first ? 0 : self.send_interval) }(true)) } OTR.prototype.dh = function dh() { var keys = { privateKey: BigInt.randBigInt(320) } keys.publicKey = BigInt.powMod(G, keys.privateKey, N) return keys } // session constructor OTR.prototype.DHSession = function DHSession(our_dh, their_y) { if (!(this instanceof DHSession)) return new DHSession(our_dh, their_y) // shared secret var s = BigInt.powMod(their_y, our_dh.privateKey, N) var secbytes = HLP.packMPI(s) // session id this.id = HLP.mask(HLP.h2('\x00', secbytes), 0, 64) // first 64-bits // are we the high or low end of the connection? var sq = BigInt.greater(our_dh.publicKey, their_y) var sendbyte = sq ? '\x01' : '\x02' var rcvbyte = sq ? '\x02' : '\x01' // sending and receiving keys this.sendenc = HLP.mask(HLP.h1(sendbyte, secbytes), 0, 128) // f16 bytes this.sendmac = CryptoJS.SHA1(CryptoJS.enc.Latin1.parse(this.sendenc)) this.sendmac = this.sendmac.toString(CryptoJS.enc.Latin1) this.rcvenc = HLP.mask(HLP.h1(rcvbyte, secbytes), 0, 128) this.rcvmac = CryptoJS.SHA1(CryptoJS.enc.Latin1.parse(this.rcvenc)) this.rcvmac = this.rcvmac.toString(CryptoJS.enc.Latin1) this.rcvmacused = false // extra symmetric key this.extra_symkey = HLP.h2('\xff', secbytes) // counters this.send_counter = 0 this.rcv_counter = 0 } OTR.prototype.rotateOurKeys = function () { // reveal old mac keys var self = this this.sessKeys[1].forEach(function (sk) { if (sk && sk.rcvmacused) self.oldMacKeys.push(sk.rcvmac) }) // rotate our keys this.our_old_dh = this.our_dh this.our_dh = this.dh() this.our_keyid += 1 this.sessKeys[1][0] = this.sessKeys[0][0] this.sessKeys[1][1] = this.sessKeys[0][1] this.sessKeys[0] = [ this.their_y ? new this.DHSession(this.our_dh, this.their_y) : null , this.their_old_y ? new this.DHSession(this.our_dh, this.their_old_y) : null ] } OTR.prototype.rotateTheirKeys = function (their_y) { // increment their keyid this.their_keyid += 1 // reveal old mac keys var self = this this.sessKeys.forEach(function (sk) { if (sk[1] && sk[1].rcvmacused) self.oldMacKeys.push(sk[1].rcvmac) }) // rotate their keys / session this.their_old_y = this.their_y this.sessKeys[0][1] = this.sessKeys[0][0] this.sessKeys[1][1] = this.sessKeys[1][0] // new keys / sessions this.their_y = their_y this.sessKeys[0][0] = new this.DHSession(this.our_dh, this.their_y) this.sessKeys[1][0] = new this.DHSession(this.our_old_dh, this.their_y) } OTR.prototype.prepareMsg = function (msg, esk) { if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED || this.their_keyid === 0) return this.error('Not ready to encrypt.') var sessKeys = this.sessKeys[1][0] if (sessKeys.send_counter >= MAX_INT) return this.error('Should have rekeyed by now.') sessKeys.send_counter += 1 var ctr = HLP.packCtr(sessKeys.send_counter) var send = this.ake.otr_version + '\x03' // version and type var v3 = (this.ake.otr_version === CONST.OTR_VERSION_3) if (v3) { send += this.our_instance_tag send += this.their_instance_tag } send += '\x00' // flag send += HLP.packINT(this.our_keyid - 1) send += HLP.packINT(this.their_keyid) send += HLP.packMPI(this.our_dh.publicKey) send += ctr.substring(0, 8) if (Math.ceil(msg.length / 8) >= MAX_UINT) // * 16 / 128 return this.error('Message is too long.') var aes = HLP.encryptAes( CryptoJS.enc.Latin1.parse(msg) , sessKeys.sendenc , ctr ) send += HLP.packData(aes) send += HLP.make1Mac(send, sessKeys.sendmac) send += HLP.packData(this.oldMacKeys.splice(0).join('')) send = HLP.wrapMsg( send , this.fragment_size , v3 , this.our_instance_tag , this.their_instance_tag ) if (send[0]) return this.error(send[0]) // emit extra symmetric key if (esk) this.trigger('file', ['send', sessKeys.extra_symkey, esk]) return send[1] } OTR.prototype.handleDataMsg = function (msg) { var vt = msg.version + msg.type if (this.ake.otr_version === CONST.OTR_VERSION_3) vt += msg.instance_tags var types = ['BYTE', 'INT', 'INT', 'MPI', 'CTR', 'DATA', 'MAC', 'DATA'] msg = HLP.splitype(types, msg.msg) // ignore flag var ign = (msg[0] === '\x01') if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED || msg.length !== 8) { if (!ign) this.error('Received an unreadable encrypted message.', true) return } var our_keyid = this.our_keyid - HLP.readLen(msg[2]) var their_keyid = this.their_keyid - HLP.readLen(msg[1]) if (our_keyid < 0 || our_keyid > 1) { if (!ign) this.error('Not of our latest keys.', true) return } if (their_keyid < 0 || their_keyid > 1) { if (!ign) this.error('Not of your latest keys.', true) return } var their_y = their_keyid ? this.their_old_y : this.their_y if (their_keyid === 1 && !their_y) { if (!ign) this.error('Do not have that key.') return } var sessKeys = this.sessKeys[our_keyid][their_keyid] var ctr = HLP.unpackCtr(msg[4]) if (ctr <= sessKeys.rcv_counter) { if (!ign) this.error('Counter in message is not larger.') return } sessKeys.rcv_counter = ctr // verify mac vt += msg.slice(0, 6).join('') var vmac = HLP.make1Mac(vt, sessKeys.rcvmac) if (!HLP.compare(msg[6], vmac)) { if (!ign) this.error('MACs do not match.') return } sessKeys.rcvmacused = true var out = HLP.decryptAes( msg[5].substring(4) , sessKeys.rcvenc , HLP.padCtr(msg[4]) ) out = out.toString(CryptoJS.enc.Latin1) if (!our_keyid) this.rotateOurKeys() if (!their_keyid) this.rotateTheirKeys(HLP.readMPI(msg[3])) // parse TLVs var ind = out.indexOf('\x00') if (~ind) { this.handleTLVs(out.substring(ind + 1), sessKeys) out = out.substring(0, ind) } out = CryptoJS.enc.Latin1.parse(out) return out.toString(CryptoJS.enc.Utf8) } OTR.prototype.handleTLVs = function (tlvs, sessKeys) { var type, len, msg for (; tlvs.length; ) { type = HLP.unpackSHORT(tlvs.substr(0, 2)) len = HLP.unpackSHORT(tlvs.substr(2, 2)) msg = tlvs.substr(4, len) // TODO: handle pathological cases better if (msg.length < len) break switch (type) { case 1: // Disconnected this.msgstate = CONST.MSGSTATE_FINISHED this.trigger('status', [CONST.STATUS_END_OTR]) break case 2: case 3: case 4: case 5: case 6: case 7: // SMP if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED) { if (this.sm) this.sm.abort() return } if (!this.sm) this._smInit() this.sm.handleSM({ msg: msg, type: type }) break case 8: // utf8 filenames msg = msg.substring(4) // remove 4-byte indication msg = CryptoJS.enc.Latin1.parse(msg) msg = msg.toString(CryptoJS.enc.Utf8) // Extra Symkey this.trigger('file', ['receive', sessKeys.extra_symkey, msg]) break } tlvs = tlvs.substring(4 + len) } } OTR.prototype.smpSecret = function (secret, question) { if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED) return this.error('Must be encrypted for SMP.') if (typeof secret !== 'string' || secret.length < 1) return this.error('Secret is required.') if (!this.sm) this._smInit() // utf8 inputs secret = CryptoJS.enc.Utf8.parse(secret).toString(CryptoJS.enc.Latin1) question = CryptoJS.enc.Utf8.parse(question).toString(CryptoJS.enc.Latin1) this.sm.rcvSecret(secret, question) } OTR.prototype.sendQueryMsg = function () { var versions = {} , msg = CONST.OTR_TAG if (this.ALLOW_V2) versions['2'] = true if (this.ALLOW_V3) versions['3'] = true // but we don't allow v1 // if (versions['1']) msg += '?' var vs = Object.keys(versions) if (vs.length) { msg += 'v' vs.forEach(function (v) { if (v !== '1') msg += v }) msg += '?' } this.io(msg) this.trigger('status', [CONST.STATUS_SEND_QUERY]) } OTR.prototype.sendMsg = function (msg, meta) { if ( this.REQUIRE_ENCRYPTION || this.msgstate !== CONST.MSGSTATE_PLAINTEXT ) { msg = CryptoJS.enc.Utf8.parse(msg) msg = msg.toString(CryptoJS.enc.Latin1) } switch (this.msgstate) { case CONST.MSGSTATE_PLAINTEXT: if (this.REQUIRE_ENCRYPTION) { this.storedMgs.push({msg: msg, meta: meta}) this.sendQueryMsg() return } if (this.SEND_WHITESPACE_TAG && !this.receivedPlaintext) { msg += CONST.WHITESPACE_TAG // 16 byte tag if (this.ALLOW_V3) msg += CONST.WHITESPACE_TAG_V3 if (this.ALLOW_V2) msg += CONST.WHITESPACE_TAG_V2 } break case CONST.MSGSTATE_FINISHED: this.storedMgs.push({msg: msg, meta: meta}) this.error('Message cannot be sent at this time.') return case CONST.MSGSTATE_ENCRYPTED: msg = this.prepareMsg(msg) break default: throw new Error('Unknown message state.') } if (msg) this.io(msg, meta) } OTR.prototype.receiveMsg = function (msg) { // parse type msg = Parse.parseMsg(this, msg) if (!msg) return switch (msg.cls) { case 'error': this.error(msg.msg) return case 'ake': if ( msg.version === CONST.OTR_VERSION_3 && this.checkInstanceTags(msg.instance_tags) ) return // ignore this.ake.handleAKE(msg) return case 'data': if ( msg.version === CONST.OTR_VERSION_3 && this.checkInstanceTags(msg.instance_tags) ) return // ignore msg.msg = this.handleDataMsg(msg) msg.encrypted = true break case 'query': if (this.msgstate === CONST.MSGSTATE_ENCRYPTED) this._akeInit() this.doAKE(msg) break default: // check for encrypted if ( this.REQUIRE_ENCRYPTION || this.msgstate !== CONST.MSGSTATE_PLAINTEXT ) this.error('Received an unencrypted message.') // received a plaintext message // stop sending the whitespace tag this.receivedPlaintext = true // received a whitespace tag if (this.WHITESPACE_START_AKE && msg.ver.length > 0) this.doAKE(msg) } if (msg.msg) this.trigger('ui', [msg.msg, !!msg.encrypted]) } OTR.prototype.checkInstanceTags = function (it) { var their_it = HLP.readLen(it.substr(0, 4)) var our_it = HLP.readLen(it.substr(4, 4)) if (our_it && our_it !== HLP.readLen(this.our_instance_tag)) return true if (HLP.readLen(this.their_instance_tag)) { if (HLP.readLen(this.their_instance_tag) !== their_it) return true } else { if (their_it < 100) return true this.their_instance_tag = HLP.packINT(their_it) } } OTR.prototype.doAKE = function (msg) { if (this.ALLOW_V3 && ~msg.ver.indexOf(CONST.OTR_VERSION_3)) { this.ake.initiateAKE(CONST.OTR_VERSION_3) } else if (this.ALLOW_V2 && ~msg.ver.indexOf(CONST.OTR_VERSION_2)) { this.ake.initiateAKE(CONST.OTR_VERSION_2) } else { // is this an error? this.error('OTR conversation requested, ' + 'but no compatible protocol version found.') } } OTR.prototype.error = function (err, send) { if (send) { if (!this.debug) err = "An OTR error has occurred." err = '?OTR Error:' + err this.io(err) return } this.trigger('error', [err]) } OTR.prototype.sendStored = function () { var self = this ;(this.storedMgs.splice(0)).forEach(function (elem) { var msg = self.prepareMsg(elem.msg) self.io(msg, elem.meta) }) } OTR.prototype.sendFile = function (filename) { if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED) return this.error('Not ready to encrypt.') if (this.ake.otr_version !== CONST.OTR_VERSION_3) return this.error('Protocol v3 required.') if (!filename) return this.error('Please specify a filename.') // utf8 filenames var l1name = CryptoJS.enc.Utf8.parse(filename) l1name = l1name.toString(CryptoJS.enc.Latin1) if (l1name.length >= 65532) return this.error('filename is too long.') var msg = '\x00' // null byte msg += '\x00\x08' // type 8 tlv msg += HLP.packSHORT(4 + l1name.length) // length of value msg += '\x00\x00\x00\x01' // four bytes indicating file msg += l1name msg = this.prepareMsg(msg, filename) this.io(msg) } OTR.prototype.endOtr = function () { if (this.msgstate === CONST.MSGSTATE_ENCRYPTED) { this.sendMsg('\x00\x00\x01\x00\x00') if (this.sm) { if (this.smw) this.sm.worker.terminate() // destroy webworker this.sm = null } } this.msgstate = CONST.MSGSTATE_PLAINTEXT this.receivedPlaintext = false this.trigger('status', [CONST.STATUS_END_OTR]) } // attach methods OTR.makeInstanceTag = function () { var num = BigInt.randBigInt(32) if (BigInt.greater(BigInt.str2bigInt('100', 16), num)) return OTR.makeInstanceTag() return HLP.packINT(parseInt(BigInt.bigInt2str(num, 10), 10)) } }).call(this) return { OTR: this.OTR , DSA: this.DSA } }))