Padded attachments, attachments v2

* Handle incoming padded attachments
* Attachments v2 - multipart form POST, and direct CDN GET access
* Pad outgoing attachments before encryption (disabled for now)
This commit is contained in:
Scott Nonnenberg 2019-05-08 13:14:52 -07:00
parent 4a8e0bd466
commit 26a3342d2a
7 changed files with 519 additions and 105 deletions

View file

@ -1234,17 +1234,19 @@ MessageReceiver.prototype.extend({
window.Signal.Crypto.base64ToArrayBuffer(digest)
);
if (!size || size !== data.byteLength) {
if (!size) {
throw new Error(
`downloadAttachment: Size ${size} did not match downloaded attachment size ${
`downloadAttachment: Size was not provided, actual size was ${
data.byteLength
}`
);
}
const typedArray = window.Signal.Crypto.getFirstBytes(data, size);
return {
..._.omit(attachment, 'digest', 'key'),
data,
data: window.Signal.Crypto.typedArrayToArrayBuffer(typedArray),
};
},
handleAttachment(attachment) {

View file

@ -155,60 +155,80 @@ function MessageSender(username, password) {
this.pendingMessages = {};
}
const DISABLE_PADDING = true;
MessageSender.prototype = {
constructor: MessageSender,
// makeAttachmentPointer :: Attachment -> Promise AttachmentPointerProto
makeAttachmentPointer(attachment) {
_getAttachmentSizeBucket(size) {
return Math.max(
541,
Math.floor(1.05 ** Math.ceil(Math.log(size) / Math.log(1.05)))
);
},
getPaddedAttachment(data) {
if (DISABLE_PADDING) {
return data;
}
const size = data.byteLength;
const paddedSize = this._getAttachmentSizeBucket(size);
const padding = window.Signal.Crypto.getZeroes(paddedSize - size);
return window.Signal.Crypto.concatenateBytes(data, padding);
},
async makeAttachmentPointer(attachment) {
if (typeof attachment !== 'object' || attachment == null) {
return Promise.resolve(undefined);
}
if (
!(attachment.data instanceof ArrayBuffer) &&
!ArrayBuffer.isView(attachment.data)
) {
return Promise.reject(
new TypeError(
`\`attachment.data\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof attachment.data}`
)
const { data, size } = attachment;
if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
throw new Error(
`makeAttachmentPointer: data was a '${typeof data}' instead of ArrayBuffer/ArrayBufferView`
);
}
if (data.byteLength !== size) {
throw new Error(
`makeAttachmentPointer: Size ${size} did not match data.byteLength ${
data.byteLength
}`
);
}
const proto = new textsecure.protobuf.AttachmentPointer();
proto.key = libsignal.crypto.getRandomBytes(64);
const padded = this.getPaddedAttachment(data);
const key = libsignal.crypto.getRandomBytes(64);
const iv = libsignal.crypto.getRandomBytes(16);
return textsecure.crypto
.encryptAttachment(attachment.data, proto.key, iv)
.then(result =>
this.server.putAttachment(result.ciphertext).then(id => {
proto.id = id;
proto.contentType = attachment.contentType;
proto.digest = result.digest;
if (attachment.size) {
proto.size = attachment.size;
}
if (attachment.fileName) {
proto.fileName = attachment.fileName;
}
if (attachment.flags) {
proto.flags = attachment.flags;
}
if (attachment.width) {
proto.width = attachment.width;
}
if (attachment.height) {
proto.height = attachment.height;
}
if (attachment.caption) {
proto.caption = attachment.caption;
}
const result = await textsecure.crypto.encryptAttachment(padded, key, iv);
const id = await this.server.putAttachment(result.ciphertext);
return proto;
})
);
const proto = new textsecure.protobuf.AttachmentPointer();
proto.id = id;
proto.contentType = attachment.contentType;
proto.key = key;
proto.size = attachment.size;
proto.digest = result.digest;
if (attachment.fileName) {
proto.fileName = attachment.fileName;
}
if (attachment.flags) {
proto.flags = attachment.flags;
}
if (attachment.width) {
proto.width = attachment.width;
}
if (attachment.height) {
proto.height = attachment.height;
}
if (attachment.caption) {
proto.caption = attachment.caption;
}
return proto;
},
queueJobForNumber(number, runJob) {
@ -1124,6 +1144,8 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
this.makeProxiedRequest = sender.makeProxiedRequest.bind(sender);
this.getProxiedSize = sender.getProxiedSize.bind(sender);
this.getMessageProto = sender.getMessageProto.bind(sender);
this._getAttachmentSizeBucket = sender._getAttachmentSizeBucket.bind(sender);
};
textsecure.MessageSender.prototype = {

View file

@ -45,6 +45,7 @@
<script type="text/javascript" src="websocket-resources_test.js"></script>
<script type="text/javascript" src="task_with_timeout_test.js"></script>
<script type="text/javascript" src="account_manager_test.js"></script>
<script type="text/javascript" src="sendmessage_test.js"></script>
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
<!-- NOTE: blanket doesn't support modern syntax and will choke until we find a replacement. :0( -->

View file

@ -0,0 +1,335 @@
/* global textsecure, WebAPI */
/* eslint-disable no-console */
const BUCKET_SIZES = [
541,
568,
596,
626,
657,
690,
725,
761,
799,
839,
881,
925,
972,
1020,
1071,
1125,
1181,
1240,
1302,
1367,
1436,
1507,
1583,
1662,
1745,
1832,
1924,
2020,
2121,
2227,
2339,
2456,
2579,
2708,
2843,
2985,
3134,
3291,
3456,
3629,
3810,
4001,
4201,
4411,
4631,
4863,
5106,
5361,
5629,
5911,
6207,
6517,
6843,
7185,
7544,
7921,
8318,
8733,
9170,
9629,
10110,
10616,
11146,
11704,
12289,
12903,
13549,
14226,
14937,
15684,
16469,
17292,
18157,
19065,
20018,
21019,
22070,
23173,
24332,
25549,
26826,
28167,
29576,
31054,
32607,
34238,
35950,
37747,
39634,
41616,
43697,
45882,
48176,
50585,
53114,
55770,
58558,
61486,
64561,
67789,
71178,
74737,
78474,
82398,
86518,
90843,
95386,
100155,
105163,
110421,
115942,
121739,
127826,
134217,
140928,
147975,
155373,
163142,
171299,
179864,
188858,
198300,
208215,
218626,
229558,
241036,
253087,
265742,
279029,
292980,
307629,
323011,
339161,
356119,
373925,
392622,
412253,
432866,
454509,
477234,
501096,
526151,
552458,
580081,
609086,
639540,
671517,
705093,
740347,
777365,
816233,
857045,
899897,
944892,
992136,
1041743,
1093831,
1148522,
1205948,
1266246,
1329558,
1396036,
1465838,
1539130,
1616086,
1696890,
1781735,
1870822,
1964363,
2062581,
2165710,
2273996,
2387695,
2507080,
2632434,
2764056,
2902259,
3047372,
3199740,
3359727,
3527714,
3704100,
3889305,
4083770,
4287958,
4502356,
4727474,
4963848,
5212040,
5472642,
5746274,
6033588,
6335268,
6652031,
6984633,
7333864,
7700558,
8085585,
8489865,
8914358,
9360076,
9828080,
10319484,
10835458,
11377231,
11946092,
12543397,
13170567,
13829095,
14520550,
15246578,
16008907,
16809352,
17649820,
18532311,
19458926,
20431872,
21453466,
22526139,
23652446,
24835069,
26076822,
27380663,
28749697,
30187181,
31696540,
33281368,
34945436,
36692708,
38527343,
40453710,
42476396,
44600216,
46830227,
49171738,
51630325,
54211841,
56922433,
59768555,
62756983,
65894832,
69189573,
72649052,
76281505,
80095580,
84100359,
88305377,
92720646,
97356678,
102224512,
107335738,
];
describe('sendmessage', () => {
let originalWebAPIConnect = null;
let sendmessage = null;
before(() => {
originalWebAPIConnect = WebAPI.connect;
WebAPI.connect = () => null;
sendmessage = new textsecure.MessageSender();
});
after(() => {
WebAPI.connect = originalWebAPIConnect;
});
describe('#_getAttachmentSizeBucket', () => {
it('properly calculates first bucket', () => {
for (let size = 0, max = BUCKET_SIZES[0]; size < max; size += 1) {
assert.strictEqual(
BUCKET_SIZES[0],
sendmessage._getAttachmentSizeBucket(size)
);
}
});
it('properly calculates entire table', () => {
let count = 0;
for (let i = 0, max = BUCKET_SIZES.length - 1; i < max; i += 1) {
// Exact
if (
BUCKET_SIZES[i] !==
sendmessage._getAttachmentSizeBucket(BUCKET_SIZES[i])
) {
count += 1;
console.log(
`${
BUCKET_SIZES[i]
} does not equal ${sendmessage._getAttachmentSizeBucket(
BUCKET_SIZES[i]
)}`
);
}
// Just under
if (
BUCKET_SIZES[i] !==
sendmessage._getAttachmentSizeBucket(BUCKET_SIZES[i] - 1)
) {
count += 1;
console.log(
`${
BUCKET_SIZES[i]
} does not equal ${sendmessage._getAttachmentSizeBucket(
BUCKET_SIZES[i] - 1
)}`
);
}
// Just over
if (
BUCKET_SIZES[i + 1] !==
sendmessage._getAttachmentSizeBucket(BUCKET_SIZES[i] + 1)
) {
count += 1;
console.log(
`${
BUCKET_SIZES[i + 1]
} does not equal ${sendmessage._getAttachmentSizeBucket(
BUCKET_SIZES[i] + 1
)}`
);
}
}
console.log(`Failures: ${count}`);
assert.strictEqual(count, 0);
});
});
});