/**
* Copyright © 2016-2020 Threema GmbH (https://threema.ch/).
*
* This file is part of Threema Web.
*
* Threema Web is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* This program 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 Affero
* General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Threema Web. If not, see .
*/
import {
censor,
BaseConfidential,
ConfidentialArray,
ConfidentialIceCandidate,
ConfidentialObjectValues,
ConfidentialWireMessage
} from '../../src/helpers/confidential';
// tslint:disable:no-reference
///
class UnlistedClass {}
/**
* A confidential subclass for testing purposes.
*/
class TestConfidential extends BaseConfidential {
public readonly uncensored: string = 'uncensored';
public censored(): string {
return 'censored';
}
}
describe('Confidential Helpers', () => {
describe('censor function', () => {
it('handles null and undefined', () => {
expect(censor(null)).toBe(null);
expect(censor(undefined)).toBe(undefined);
});
it('handles an object implementing the Confidential interface', () => {
expect(censor(new TestConfidential())).toBe('censored');
});
it('handles booleans', () => {
expect(censor(true)).toBe('[Boolean]');
expect(censor(false)).toBe('[Boolean]');
});
it('handles numbers', () => {
expect(censor(0)).toBe('[Number]');
expect(censor(42)).toBe('[Number]');
expect(censor(-1337)).toBe('[Number]');
});
it('handles strings', () => {
expect(censor('test')).toBe('[String: length=4]');
expect(censor('')).toBe('[String: length=0]');
});
it('handles binary types', () => {
const buffer = new ArrayBuffer(10);
const array = new Uint8Array(buffer, 2, 6);
const blob = new Blob([JSON.stringify({ a: 10 })], { type: 'application/json'} );
expect(censor(buffer)).toBe('[ArrayBuffer: length=10]');
expect(censor(array)).toBe('[Uint8Array: length=6, offset=2]');
expect(censor(blob)).toBe(`[Blob: length=${blob.size}, type=application/json]`);
});
it('handles arrays', () => {
expect(censor([
null,
undefined,
new TestConfidential(),
false,
42,
'test',
new Uint8Array(10),
])).toEqual([
null,
undefined,
'censored',
'[Boolean]',
'[Number]',
'[String: length=4]',
'[Uint8Array: length=10, offset=0]',
]);
});
it('handles arrays recursively', () => {
expect(censor([
'test',
[1, false],
])).toEqual([
'[String: length=4]',
['[Number]', '[Boolean]'],
]);
});
it('handles objects', () => {
expect(censor({
null: null,
undefined: undefined,
confidential: new TestConfidential(),
boolean: false,
number: 42,
string: 'test',
uint8array: new Uint8Array(10),
})).toEqual({
null: null,
undefined: undefined,
confidential: 'censored',
boolean: '[Boolean]',
number: '[Number]',
string: '[String: length=4]',
uint8array: '[Uint8Array: length=10, offset=0]',
});
});
it('handles objects recursively', () => {
expect(censor({
boolean: false,
object: {
foo: 'bar',
},
})).toEqual({
boolean: '[Boolean]',
object: {
foo: '[String: length=3]',
},
});
});
it('handles class instances', () => {
expect(censor(new UnlistedClass())).toBe('[UnlistedClass]');
});
});
describe('ConfidentialArray', () => {
it('subclass of BaseConfidential', () => {
expect(ConfidentialArray.prototype instanceof BaseConfidential).toBeTruthy();
});
it('sanitises all items', () => {
const array = new ConfidentialArray([new TestConfidential(), new TestConfidential()]);
expect(array.uncensored).toEqual(['uncensored', 'uncensored']);
expect(array.censored()).toEqual(['censored', 'censored']);
});
it('sanitises all items recursively', () => {
const array = new ConfidentialArray([
new TestConfidential(),
new ConfidentialArray([new TestConfidential()]),
]);
expect(array.uncensored).toEqual(['uncensored', ['uncensored']]);
expect(array.censored()).toEqual(['censored', ['censored']]);
});
});
describe('ConfidentialObjectValues', () => {
it('subclass of BaseConfidential', () => {
expect(ConfidentialObjectValues.prototype instanceof BaseConfidential).toBeTruthy();
});
it('returns underlying object directly when unveiling', () => {
const object = {};
const confidential = new ConfidentialObjectValues(object);
expect(confidential.uncensored).toBe(object);
});
it('sanitises all object values', () => {
const object = {
boolean: false,
object: {
foo: 'bar',
},
};
const confidential = new ConfidentialObjectValues(object);
expect(confidential.uncensored).toBe(object);
expect(confidential.censored()).toEqual({
boolean: '[Boolean]',
object: {
foo: '[String: length=3]',
},
});
});
});
describe('ConfidentialWireMessage', () => {
it('subclass of BaseConfidential', () => {
expect(ConfidentialWireMessage.prototype instanceof BaseConfidential).toBeTruthy();
});
it('returns underlying message directly when unveiling', () => {
const message = {
type: 'request/food',
subType: 'dessert',
};
const confidential = new ConfidentialWireMessage(message);
expect(confidential.uncensored).toBe(message);
});
it("handles 'args' and 'data' being undefined", () => {
const message = {
type: 'request/food',
subType: 'dessert',
};
const confidential = new ConfidentialWireMessage(message);
expect(confidential.censored()).toEqual(message);
});
it("sanitises 'args' and 'data' fields", () => {
const message = {
type: 'request/food',
subType: 'dessert',
args: 'arrrrrrgggsss',
data: {
preference: ['ice cream', 'chocolate'],
priority: Number.POSITIVE_INFINITY,
},
};
const confidential = new ConfidentialWireMessage(message);
expect(confidential.censored()).toEqual({
type: 'request/food',
subType: 'dessert',
args: '[String: length=13]',
data: {
preference: ['[String: length=9]', '[String: length=9]'],
priority: '[Number]',
},
});
});
});
describe('ConfidentialIceCandidate', () => {
it('subclass of BaseConfidential', () => {
expect(ConfidentialIceCandidate.prototype instanceof BaseConfidential).toBeTruthy();
});
it('returns underlying ICE candidate directly when unveiling', () => {
const input = 'cannot be bothered to use valid SDP here';
const confidential = new ConfidentialIceCandidate(input);
expect(confidential.uncensored).toBe(input);
});
it('returns underlying ICE candidate directly if it cannot be parsed', () => {
const input = 'certainly invalid';
const confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(input);
});
it('does not censor mDNS concealed candidates', () => {
const input = 'candidate:1 1 UDP 1234 aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.local 1337 typ host';
const confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(input);
});
it('censors host candidates', () => {
// IPv4
let input = 'candidate:1 1 UDP 1234 192.168.0.42 1337 typ host';
let expected = 'candidate:1 1 UDP 1234 192.168.*.* 1337 typ host';
let confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(expected);
// IPv6
input = 'candidate:1 1 UDP 1234 fe80::1 1337 typ host';
expected = 'candidate:1 1 UDP 1234 fe80::* 1337 typ host';
confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(expected);
});
it('censors srflx candidates', () => {
const input = 'candidate:1 1 UDP 1234 1.2.3.4 42 typ srflx raddr 192.168.0.42 rport 1337';
const expected = 'candidate:1 1 UDP 1234 1.2.*.* 42 typ srflx raddr 192.168.*.* rport 1337';
const confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(expected);
});
it('censors relay candidates', () => {
// IPv4
let input = 'candidate:1 1 UDP 1234 1.2.3.4 42 typ relay raddr 192.168.0.42 rport 1337';
let expected = 'candidate:1 1 UDP 1234 1.2.3.4 42 typ relay raddr 192.168.*.* rport 1337';
let confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(expected);
// IPv6
input = 'candidate:1 1 UDP 1234 2a02:1:2::3 42 typ relay raddr 2a02:dead:beef::1 rport 1337';
expected = 'candidate:1 1 UDP 1234 2a02:1:2::3 42 typ relay raddr 2a02:*:*::* rport 1337';
confidential = new ConfidentialIceCandidate(input);
expect(confidential.censored()).toBe(expected);
});
});
});