Verify sticker data in getDataFromLink

This commit is contained in:
Evan Hahn 2020-08-26 17:16:59 -05:00 committed by Josh Perez
parent 333feaa81e
commit aaed0db2e5
5 changed files with 178 additions and 13 deletions

View file

@ -10,4 +10,6 @@ export function downloadStickerPack(
} }
): Promise<void>; ): Promise<void>;
export function isPackIdValid(packId: unknown): packId is string;
export function redactPackId(packId: string): string; export function redactPackId(packId: string): string;

View file

@ -5,7 +5,8 @@
navigator, navigator,
reduxStore, reduxStore,
reduxActions, reduxActions,
URL URL,
URLSearchParams
*/ */
const BLESSED_PACKS = { const BLESSED_PACKS = {
@ -27,10 +28,11 @@ const BLESSED_PACKS = {
}, },
}; };
const VALID_PACK_ID_REGEXP = /^[0-9a-f]{32}$/i;
const { isNumber, pick, reject, groupBy, values } = require('lodash'); const { isNumber, pick, reject, groupBy, values } = require('lodash');
const pMap = require('p-map'); const pMap = require('p-map');
const Queue = require('p-queue').default; const Queue = require('p-queue').default;
const qs = require('qs');
const { makeLookup } = require('../../ts/util/makeLookup'); const { makeLookup } = require('../../ts/util/makeLookup');
const { const {
@ -65,6 +67,7 @@ module.exports = {
load, load,
maybeDeletePack, maybeDeletePack,
downloadQueuedPacks, downloadQueuedPacks,
isPackIdValid,
redactPackId, redactPackId,
removeEphemeralPack, removeEphemeralPack,
savePackMetadata, savePackMetadata,
@ -90,18 +93,36 @@ async function load() {
} }
function getDataFromLink(link) { function getDataFromLink(link) {
const { hash } = new URL(link); let url;
try {
url = new URL(link);
} catch (err) {
return null;
}
const { hash } = url;
if (!hash) { if (!hash) {
return null; return null;
} }
const data = hash.slice(1); let params;
const params = qs.parse(data); try {
params = new URLSearchParams(hash.slice(1));
} catch (err) {
return null;
}
return { const id = params.get('pack_id');
id: params.pack_id, if (!isPackIdValid(id)) {
key: params.pack_key, return null;
}; }
const key = params.get('pack_key');
if (!key) {
return null;
}
return { id, key };
} }
function getInstalledStickerPacks() { function getInstalledStickerPacks() {
@ -231,6 +252,10 @@ function getInitialState() {
return initialState; return initialState;
} }
function isPackIdValid(packId) {
return typeof packId === 'string' && VALID_PACK_ID_REGEXP.test(packId);
}
function redactPackId(packId) { function redactPackId(packId) {
return `[REDACTED]${packId.slice(-3)}`; return `[REDACTED]${packId.slice(-3)}`;
} }

View file

@ -2963,11 +2963,13 @@
pack.status === 'downloaded' || pack.status === 'downloaded' ||
pack.status === 'installed'); pack.status === 'installed');
let id; const dataFromLink = window.Signal.Stickers.getDataFromLink(url);
let key; if (!dataFromLink) {
return null;
}
const { id, key } = dataFromLink;
try { try {
({ id, key } = window.Signal.Stickers.getDataFromLink(url));
const keyBytes = window.Signal.Crypto.bytesFromHexString(key); const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes); const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);

View file

@ -3,6 +3,142 @@
const { Stickers } = Signal; const { Stickers } = Signal;
describe('Stickers', () => { describe('Stickers', () => {
describe('getDataFromLink', () => {
it('returns null for invalid URLs', () => {
assert.isNull(Stickers.getDataFromLink('https://'));
assert.isNull(Stickers.getDataFromLink('signal.art/addstickers/'));
});
it("returns null for URLs that don't have a hash", () => {
assert.isNull(
Stickers.getDataFromLink('https://signal.art/addstickers/')
);
assert.isNull(
Stickers.getDataFromLink('https://signal.art/addstickers/#')
);
});
it('returns null when no key or pack ID is found', () => {
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a'
)
);
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key='
)
);
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
)
);
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e&pack_id='
)
);
});
it('returns null when the pack ID is invalid', () => {
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=garbage&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
)
);
});
it('returns null if the ID or key are passed as arrays', () => {
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id[]=c8c83285b547872ac4c589d64a6edd6a&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
)
);
assert.isNull(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key[]=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
)
);
});
it('parses the ID and key from the hash', () => {
assert.deepEqual(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e'
),
{
id: 'c8c83285b547872ac4c589d64a6edd6a',
key:
'59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e',
}
);
});
it('ignores additional hash parameters', () => {
assert.deepEqual(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e&pack_foo=bar'
),
{
id: 'c8c83285b547872ac4c589d64a6edd6a',
key:
'59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e',
}
);
});
it('only parses the first ID and key from the hash if more than one is supplied', () => {
assert.deepEqual(
Stickers.getDataFromLink(
'https://signal.art/addstickers/#pack_id=c8c83285b547872ac4c589d64a6edd6a&pack_key=59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e&pack_id=extra&pack_key=extra'
),
{
id: 'c8c83285b547872ac4c589d64a6edd6a',
key:
'59bb3a8860f0e6a5a83a5337a015c8d55ecd2193f82d77202f3b8112a845636e',
}
);
});
});
describe('isPackIdValid', () => {
it('returns false for non-strings', () => {
assert.isFalse(Stickers.isPackIdValid(undefined));
assert.isFalse(Stickers.isPackIdValid(null));
assert.isFalse(Stickers.isPackIdValid(123));
assert.isFalse(Stickers.isPackIdValid(123));
assert.isFalse(
Stickers.isPackIdValid(['b9439fa5fdc8b9873fe64f01b88b8ccf'])
);
assert.isFalse(
// eslint-disable-next-line no-new-wrappers
Stickers.isPackIdValid(new String('b9439fa5fdc8b9873fe64f01b88b8ccf'))
);
});
it('returns false for invalid pack IDs', () => {
assert.isFalse(Stickers.isPackIdValid(''));
assert.isFalse(
Stickers.isPackIdValid('x9439fa5fdc8b9873fe64f01b88b8ccf')
);
assert.isFalse(
// This is one character too short.
Stickers.isPackIdValid('b9439fa5fdc8b9873fe64f01b88b8cc')
);
assert.isFalse(
// This is one character too long.
Stickers.isPackIdValid('b9439fa5fdc8b9873fe64f01b88b8ccfa')
);
});
it('returns true for valid pack IDs', () => {
assert.isTrue(Stickers.isPackIdValid('b9439fa5fdc8b9873fe64f01b88b8ccf'));
assert.isTrue(Stickers.isPackIdValid('3eff225a1036a58a7530b312dd92f8d8'));
assert.isTrue(Stickers.isPackIdValid('DDFD48B8097DA7A4E928192B10963F6A'));
});
});
describe('redactPackId', () => { describe('redactPackId', () => {
it('redacts pack IDs', () => { it('redacts pack IDs', () => {
assert.strictEqual( assert.strictEqual(

View file

@ -271,7 +271,7 @@
"rule": "jQuery-load(", "rule": "jQuery-load(",
"path": "js/modules/stickers.js", "path": "js/modules/stickers.js",
"line": "async function load() {", "line": "async function load() {",
"lineNumber": 77, "lineNumber": 80,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2019-04-26T17:48:30.675Z" "updated": "2019-04-26T17:48:30.675Z"
}, },