Evan Feenstra
5 years ago
12 changed files with 805 additions and 318 deletions
@ -1,18 +1,71 @@ |
|||||
import * as path from 'path' |
import * as path from 'path' |
||||
|
import RNCryptor from '../utils/rncryptor' |
||||
|
import * as fetch from 'node-fetch' |
||||
|
import {parseLDAT} from '../utils/ldat' |
||||
|
import * as rsa from '../crypto/rsa' |
||||
|
import * as crypto from 'crypto' |
||||
|
import * as Blob from 'fetch-blob' |
||||
|
|
||||
const constants = require(path.join(__dirname,'../../config/constants.json')) |
const constants = require(path.join(__dirname,'../../config/constants.json')) |
||||
const msgtypes = constants.message_types |
const msgtypes = constants.message_types |
||||
|
|
||||
export function modifyPayload(payload) { |
export async function modifyPayload(payload, chat) { |
||||
if(payload.type===msgtypes.attachment) { |
if(payload.type===msgtypes.attachment) { |
||||
console.log("MODIFY, ", payload) |
console.log("MODIFY, ", payload) |
||||
// download image from mediaToken
|
|
||||
// decrypt key
|
|
||||
// decrypt image
|
|
||||
// new key, re-encrypt, re-upload
|
|
||||
// new payload
|
|
||||
|
|
||||
|
const mt = payload.message && payload.message.mediaToken |
||||
|
const key = payload.message && payload.message.mediaKey |
||||
|
const typ = payload.message && payload.message.mediaType |
||||
|
if(!mt || !key) return payload |
||||
|
|
||||
|
const terms = parseLDAT(mt) |
||||
|
if(!terms.host) return payload |
||||
|
|
||||
|
try { |
||||
|
const r = await fetch(`https://${terms.host}/file/${mt}`) |
||||
|
const buf = await r.buffer() |
||||
|
|
||||
|
const decMediaKey = rsa.decrypt(chat.groupPrivateKey, key) |
||||
|
|
||||
|
const imgUTF8 = RNCryptor.Decrypt(decMediaKey, buf.toString()) |
||||
|
|
||||
|
const newKey = crypto.randomBytes(20).toString('hex') |
||||
|
|
||||
|
const encImg = RNCryptor.Encrypt(newKey, imgUTF8) |
||||
|
|
||||
|
const resp = await fetch(`https://${terms.host}/file`, { |
||||
|
method: 'POST', |
||||
|
body: new Blob([encImg], { type: typ||'image/jpg', name:'file', filename:'Image.jpg' }) |
||||
|
}) |
||||
|
|
||||
|
let json = resp.json() |
||||
|
if(!json.muid) return payload |
||||
|
|
||||
|
// PUT NEW TERMS, to finish in personalizeMessage
|
||||
|
|
||||
|
const amt = terms.meta&&terms.meta.amt |
||||
|
const ttl = terms.meta&&terms.meta.ttl |
||||
|
const mediaTerms: {[k:string]:any} = { |
||||
|
muid:json.muid, ttl:ttl||31536000, |
||||
|
meta:{...amt && {amt}}, |
||||
|
skipSigning: amt ? true : false // only sign if its free
|
||||
|
} |
||||
|
|
||||
|
const encKey = rsa.encrypt(chat.groupKey, newKey) |
||||
|
return fillmsg(payload, {mediaTerms,mediaKey:encKey}) // key is re-encrypted later
|
||||
|
} catch(e) { |
||||
|
return payload |
||||
|
} |
||||
// how to link w og msg? ogMediaToken?
|
// how to link w og msg? ogMediaToken?
|
||||
} |
} |
||||
return payload |
return payload |
||||
} |
} |
||||
|
|
||||
|
function fillmsg(full, props){ |
||||
|
return { |
||||
|
...full, message: { |
||||
|
...full.message, |
||||
|
...props, |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
|
||||
|
|
||||
|
import {RNCryptor} from './rncryptor' |
||||
|
|
||||
|
export default RNCryptor |
@ -0,0 +1,100 @@ |
|||||
|
var sjcl = require('sjcl') |
||||
|
|
||||
|
var RNCryptor = {}; |
||||
|
|
||||
|
/* |
||||
|
Takes password string and salt WordArray |
||||
|
Returns key bitArray |
||||
|
*/ |
||||
|
|
||||
|
RNCryptor.KeyForPassword = function(password, salt) { |
||||
|
var hmacSHA1 = function (key) { |
||||
|
var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1); |
||||
|
this.encrypt = function () { |
||||
|
return hasher.encrypt.apply(hasher, arguments); |
||||
|
}; |
||||
|
}; |
||||
|
return sjcl.misc.pbkdf2(password, salt, 10000, 32 * 8, hmacSHA1); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
Takes password string and plaintext bitArray |
||||
|
options: |
||||
|
iv |
||||
|
encryption_salt |
||||
|
html_salt |
||||
|
Returns ciphertext bitArray |
||||
|
*/ |
||||
|
RNCryptor.Encrypt = function(password, plaintext, options) { |
||||
|
options = options || {} |
||||
|
var encryption_salt = options["encryption_salt"] || sjcl.random.randomWords(8 / 4); // FIXME: Need to seed PRNG
|
||||
|
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt); |
||||
|
|
||||
|
var hmac_salt = options["hmac_salt"] || sjcl.random.randomWords(8 / 4); |
||||
|
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt); |
||||
|
|
||||
|
var iv = options["iv"] || sjcl.random.randomWords(16 / 4); |
||||
|
|
||||
|
var version = sjcl.codec.hex.toBits("03"); |
||||
|
var options = sjcl.codec.hex.toBits("01"); |
||||
|
|
||||
|
var message = sjcl.bitArray.concat(version, options); |
||||
|
message = sjcl.bitArray.concat(message, encryption_salt); |
||||
|
message = sjcl.bitArray.concat(message, hmac_salt); |
||||
|
message = sjcl.bitArray.concat(message, iv); |
||||
|
|
||||
|
var aes = new sjcl.cipher.aes(encryption_key); |
||||
|
// sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
||||
|
var encrypted = sjcl.mode.cbc.encrypt(aes, plaintext, iv); |
||||
|
|
||||
|
message = sjcl.bitArray.concat(message, encrypted); |
||||
|
|
||||
|
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message); |
||||
|
message = sjcl.bitArray.concat(message, hmac); |
||||
|
|
||||
|
return sjcl.codec.utf8String.fromBits(message); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
Takes password string and message (ciphertext) bitArray |
||||
|
options: |
||||
|
iv |
||||
|
encryption_salt |
||||
|
html_salt |
||||
|
Returns plaintext bitArray |
||||
|
*/ |
||||
|
RNCryptor.Decrypt = function(password, message, options) { |
||||
|
options = options || {} |
||||
|
|
||||
|
var version = sjcl.bitArray.extract(message, 0 * 8, 8); |
||||
|
var options = sjcl.bitArray.extract(message, 1 * 8, 8); |
||||
|
|
||||
|
var encryption_salt = sjcl.bitArray.bitSlice(message, 2 * 8, 10 * 8); |
||||
|
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt); |
||||
|
|
||||
|
var hmac_salt = sjcl.bitArray.bitSlice(message, 10 * 8, 18 * 8); |
||||
|
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt); |
||||
|
|
||||
|
var iv = sjcl.bitArray.bitSlice(message, 18 * 8, 34 * 8); |
||||
|
|
||||
|
var ciphertext_end = sjcl.bitArray.bitLength(message) - (32 * 8); |
||||
|
|
||||
|
var ciphertext = sjcl.bitArray.bitSlice(message, 34 * 8, ciphertext_end); |
||||
|
|
||||
|
var hmac = sjcl.bitArray.bitSlice(message, ciphertext_end); |
||||
|
|
||||
|
var expected_hmac = new sjcl.misc.hmac(hmac_key).encrypt(sjcl.bitArray.bitSlice(message, 0, ciphertext_end)); |
||||
|
|
||||
|
// .equal is of consistent time
|
||||
|
if (! sjcl.bitArray.equal(hmac, expected_hmac)) { |
||||
|
throw new sjcl.exception.corrupt("HMAC mismatch or bad password."); |
||||
|
} |
||||
|
|
||||
|
var aes = new sjcl.cipher.aes(encryption_key); |
||||
|
// sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
||||
|
var decrypted = sjcl.mode.cbc.decrypt(aes, ciphertext, iv); |
||||
|
|
||||
|
return sjcl.codec.utf8String.fromBits(decrypted); |
||||
|
} |
||||
|
|
||||
|
export {RNCryptor} |
@ -1 +1 @@ |
|||||
{"version":3,"file":"modify.js","sourceRoot":"","sources":["../../../api/network/modify.ts"],"names":[],"mappings":";;AAAA,6BAA4B;AAE5B,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAC,6BAA6B,CAAC,CAAC,CAAA;AAC7E,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAA;AAExC,SAAgB,aAAa,CAAC,OAAO;IACnC,IAAG,OAAO,CAAC,IAAI,KAAG,QAAQ,CAAC,UAAU,EAAE;QACrC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAChC,iCAAiC;QACjC,cAAc;QACd,gBAAgB;QAChB,iCAAiC;QACjC,cAAc;QAEd,sCAAsC;KACvC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAZD,sCAYC"} |
{"version":3,"file":"modify.js","sourceRoot":"","sources":["../../../api/network/modify.ts"],"names":[],"mappings":";;AAAA,6BAA4B;AAC5B,kDAA0C;AAE1C,OAAO,CAAC,GAAG,CAAC,WAAW,EAAC,mBAAS,CAAC,CAAA;AAElC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAC,6BAA6B,CAAC,CAAC,CAAA;AAC7E,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAA;AAGxC,6CAA6C;AAG7C,SAAgB,aAAa,CAAC,OAAO;IACnC,IAAG,OAAO,CAAC,IAAI,KAAG,QAAQ,CAAC,UAAU,EAAE;QACrC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAEhC,iCAAiC;QACjC,cAAc;QACd,gBAAgB;QAChB,iCAAiC;QACjC,cAAc;QAEd,sCAAsC;KACvC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAbD,sCAaC"} |
@ -0,0 +1,5 @@ |
|||||
|
"use strict"; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
const rncryptor_1 = require("./rncryptor"); |
||||
|
exports.default = rncryptor_1.RNCryptor; |
||||
|
//# sourceMappingURL=index.js.map
|
@ -0,0 +1 @@ |
|||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../api/utils/rncryptor/index.js"],"names":[],"mappings":";;AAEA,2CAAqC;AAErC,kBAAe,qBAAS,CAAA"} |
@ -0,0 +1,78 @@ |
|||||
|
"use strict"; |
||||
|
Object.defineProperty(exports, "__esModule", { value: true }); |
||||
|
var sjcl = require('sjcl'); |
||||
|
var RNCryptor = {}; |
||||
|
exports.RNCryptor = RNCryptor; |
||||
|
/* |
||||
|
Takes password string and salt WordArray |
||||
|
Returns key bitArray |
||||
|
*/ |
||||
|
RNCryptor.KeyForPassword = function (password, salt) { |
||||
|
var hmacSHA1 = function (key) { |
||||
|
var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1); |
||||
|
this.encrypt = function () { |
||||
|
return hasher.encrypt.apply(hasher, arguments); |
||||
|
}; |
||||
|
}; |
||||
|
return sjcl.misc.pbkdf2(password, salt, 10000, 32 * 8, hmacSHA1); |
||||
|
}; |
||||
|
/* |
||||
|
Takes password string and plaintext bitArray |
||||
|
options: |
||||
|
iv |
||||
|
encryption_salt |
||||
|
html_salt |
||||
|
Returns ciphertext bitArray |
||||
|
*/ |
||||
|
RNCryptor.Encrypt = function (password, plaintext, options) { |
||||
|
options = options || {}; |
||||
|
var encryption_salt = options["encryption_salt"] || sjcl.random.randomWords(8 / 4); // FIXME: Need to seed PRNG
|
||||
|
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt); |
||||
|
var hmac_salt = options["hmac_salt"] || sjcl.random.randomWords(8 / 4); |
||||
|
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt); |
||||
|
var iv = options["iv"] || sjcl.random.randomWords(16 / 4); |
||||
|
var version = sjcl.codec.hex.toBits("03"); |
||||
|
var options = sjcl.codec.hex.toBits("01"); |
||||
|
var message = sjcl.bitArray.concat(version, options); |
||||
|
message = sjcl.bitArray.concat(message, encryption_salt); |
||||
|
message = sjcl.bitArray.concat(message, hmac_salt); |
||||
|
message = sjcl.bitArray.concat(message, iv); |
||||
|
var aes = new sjcl.cipher.aes(encryption_key); |
||||
|
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."](); |
||||
|
var encrypted = sjcl.mode.cbc.encrypt(aes, plaintext, iv); |
||||
|
message = sjcl.bitArray.concat(message, encrypted); |
||||
|
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message); |
||||
|
message = sjcl.bitArray.concat(message, hmac); |
||||
|
return message; |
||||
|
}; |
||||
|
/* |
||||
|
Takes password string and message (ciphertext) bitArray |
||||
|
options: |
||||
|
iv |
||||
|
encryption_salt |
||||
|
html_salt |
||||
|
Returns plaintext bitArray |
||||
|
*/ |
||||
|
RNCryptor.Decrypt = function (password, message, options) { |
||||
|
options = options || {}; |
||||
|
var version = sjcl.bitArray.extract(message, 0 * 8, 8); |
||||
|
var options = sjcl.bitArray.extract(message, 1 * 8, 8); |
||||
|
var encryption_salt = sjcl.bitArray.bitSlice(message, 2 * 8, 10 * 8); |
||||
|
var encryption_key = RNCryptor.KeyForPassword(password, encryption_salt); |
||||
|
var hmac_salt = sjcl.bitArray.bitSlice(message, 10 * 8, 18 * 8); |
||||
|
var hmac_key = RNCryptor.KeyForPassword(password, hmac_salt); |
||||
|
var iv = sjcl.bitArray.bitSlice(message, 18 * 8, 34 * 8); |
||||
|
var ciphertext_end = sjcl.bitArray.bitLength(message) - (32 * 8); |
||||
|
var ciphertext = sjcl.bitArray.bitSlice(message, 34 * 8, ciphertext_end); |
||||
|
var hmac = sjcl.bitArray.bitSlice(message, ciphertext_end); |
||||
|
var expected_hmac = new sjcl.misc.hmac(hmac_key).encrypt(sjcl.bitArray.bitSlice(message, 0, ciphertext_end)); |
||||
|
// .equal is of consistent time
|
||||
|
if (!sjcl.bitArray.equal(hmac, expected_hmac)) { |
||||
|
throw new sjcl.exception.corrupt("HMAC mismatch or bad password."); |
||||
|
} |
||||
|
var aes = new sjcl.cipher.aes(encryption_key); |
||||
|
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."](); |
||||
|
var decrypted = sjcl.mode.cbc.decrypt(aes, ciphertext, iv); |
||||
|
return decrypted; |
||||
|
}; |
||||
|
//# sourceMappingURL=rncryptor.js.map
|
@ -0,0 +1 @@ |
|||||
|
{"version":3,"file":"rncryptor.js","sourceRoot":"","sources":["../../../../api/utils/rncryptor/rncryptor.js"],"names":[],"mappings":"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"} |
File diff suppressed because it is too large
Loading…
Reference in new issue