Merge branch 'develop' into luke/fix-join-part-collapsing-causality

Conflicts:
	src/components/structures/MessagePanel.js
This commit is contained in:
Luke Barnard 2017-01-24 14:22:18 +00:00
commit bd106968ce
124 changed files with 1843 additions and 657 deletions

5
test/.eslintrc.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
env: {
mocha: true,
},
}

View file

@ -158,4 +158,85 @@ describe('MessageComposerInput', () => {
expect(['__', '**']).toContain(spy.args[0][1]);
});
it('should not entity-encode " in Markdown mode', () => {
const spy = sinon.spy(client, 'sendTextMessage');
mci.enableRichtext(false);
addTextToDraft('"');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('"');
});
it('should escape characters without other markup in Markdown mode', () => {
const spy = sinon.spy(client, 'sendTextMessage');
mci.enableRichtext(false);
addTextToDraft('\\*escaped\\*');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('*escaped*');
});
it('should escape characters with other markup in Markdown mode', () => {
const spy = sinon.spy(client, 'sendHtmlMessage');
mci.enableRichtext(false);
addTextToDraft('\\*escaped\\* *italic*');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('\\*escaped\\* *italic*');
expect(spy.args[0][2]).toEqual('*escaped* <em>italic</em>');
});
it('should not convert -_- into a horizontal rule in Markdown mode', () => {
const spy = sinon.spy(client, 'sendTextMessage');
mci.enableRichtext(false);
addTextToDraft('-_-');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('-_-');
});
it('should not strip <del> tags in Markdown mode', () => {
const spy = sinon.spy(client, 'sendHtmlMessage');
mci.enableRichtext(false);
addTextToDraft('<del>striked-out</del>');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('<del>striked-out</del>');
expect(spy.args[0][2]).toEqual('<del>striked-out</del>');
});
it('should not strike-through ~~~ in Markdown mode', () => {
const spy = sinon.spy(client, 'sendTextMessage');
mci.enableRichtext(false);
addTextToDraft('~~~striked-out~~~');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('~~~striked-out~~~');
});
it('should not mark single unmarkedup paragraphs as HTML in Markdown mode', () => {
const spy = sinon.spy(client, 'sendTextMessage');
mci.enableRichtext(false);
addTextToDraft('Lorem ipsum dolor sit amet, consectetur adipiscing elit.');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit.');
});
it('should not mark two unmarkedup paragraphs as HTML in Markdown mode', () => {
const spy = sinon.spy(client, 'sendTextMessage');
mci.enableRichtext(false);
addTextToDraft('Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nFusce congue sapien sed neque molestie volutpat.');
mci.handleReturn(sinon.stub());
expect(spy.calledOnce).toEqual(true);
expect(spy.args[0][1]).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nFusce congue sapien sed neque molestie volutpat.');
});
});

View file

@ -0,0 +1,116 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import * as MegolmExportEncryption from 'utils/MegolmExportEncryption';
import * as testUtils from '../test-utils';
import expect from 'expect';
const TEST_VECTORS=[
[
"plain",
"password",
"-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\ncissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----"
],
[
"Hello, World",
"betterpassword",
"-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\nKYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----"
],
[
"alphanumericallyalphanumericallyalphanumericallyalphanumerically",
"SWORDFISH",
"-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\nMgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----"
],
[
"alphanumericallyalphanumericallyalphanumericallyalphanumerically",
"passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword",
"-----BEGIN MEGOLM SESSION DATA-----\nAf//////////////////////////////////////////AAAD6IAZJy7IQ7Y0idqSw/bmpngEEVVh\ngsH+8ptgqxw6ZVWQnohr8JsuwH9SwGtiebZuBu5smPCO+RFVWH2cQYslZijXv/BEH/txvhUrrtCd\nbWnSXS9oymiqwUIGs08sXI33ZA==\n-----END MEGOLM SESSION DATA-----"
]
]
;
function stringToArray(s) {
return new TextEncoder().encode(s).buffer;
}
describe('MegolmExportEncryption', function() {
before(function() {
// if we don't have subtlecrypto, go home now
if (!window.crypto.subtle && !window.crypto.webkitSubtle) {
this.skip();
}
})
beforeEach(function() {
testUtils.beforeEach(this);
});
describe('decrypt', function() {
it('should handle missing header', function() {
const input=stringToArray(`-----`);
expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
.toThrow('Header line not found');
});
it('should handle missing trailer', function() {
const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA-----
-----`);
expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
.toThrow('Trailer line not found');
});
it('should decrypt a range of inputs', function(done) {
function next(i) {
if (i >= TEST_VECTORS.length) {
done();
return;
}
const [plain, password, input] = TEST_VECTORS[i];
return MegolmExportEncryption.decryptMegolmKeyFile(
stringToArray(input), password
).then((decrypted) => {
expect(decrypted).toEqual(plain);
return next(i+1);
})
};
return next(0).catch(done);
});
});
describe('encrypt', function() {
it('should round-trip', function(done) {
const input =
'words words many words in plain text here'.repeat(100);
const password = 'my super secret passphrase';
return MegolmExportEncryption.encryptMegolmKeyFile(
input, password, {kdf_rounds: 1000},
).then((ciphertext) => {
return MegolmExportEncryption.decryptMegolmKeyFile(
ciphertext, password
);
}).then((plaintext) => {
expect(plaintext).toEqual(input);
done();
}).catch(done);
});
});
});

View file

@ -0,0 +1,117 @@
#!/usr/bin/env python
from __future__ import print_function
import base64
import json
import struct
from cryptography.hazmat import backends
from cryptography.hazmat.primitives import ciphers, hashes, hmac
from cryptography.hazmat.primitives.kdf import pbkdf2
from cryptography.hazmat.primitives.ciphers import algorithms, modes
backend = backends.default_backend()
def parse_u128(s):
a, b = struct.unpack(">QQ", s)
return (a << 64) | b
def encrypt_ctr(key, iv, plaintext, counter_bits=64):
alg = algorithms.AES(key)
# Some AES-CTR implementations treat some parts of the IV as a nonce (which
# remains constant throughought encryption), and some as a counter (which
# increments every block, ie 16 bytes, and wraps after a while). Different
# implmententations use different amounts of the IV for each part.
#
# The python cryptography library uses the whole IV as a counter; to make
# it match other implementations with a given counter size, we manually
# implement wrapping the counter.
# number of AES blocks between each counter wrap
limit = 1 << counter_bits
# parse IV as a 128-bit int
parsed_iv = parse_u128(iv)
# split IV into counter and nonce
counter = parsed_iv & (limit - 1)
nonce = parsed_iv & ~(limit - 1)
# encrypt up to the first counter wraparound
size = 16 * (limit - counter)
encryptor = ciphers.Cipher(
alg,
modes.CTR(iv),
backend=backend
).encryptor()
input = plaintext[:size]
result = encryptor.update(input) + encryptor.finalize()
offset = size
# do remaining data starting with a counter of zero
iv = struct.pack(">QQ", nonce >> 64, nonce & ((1 << 64) - 1))
size = 16 * limit
while offset < len(plaintext):
encryptor = ciphers.Cipher(
alg,
modes.CTR(iv),
backend=backend
).encryptor()
input = plaintext[offset:offset+size]
result += encryptor.update(input) + encryptor.finalize()
offset += size
return result
def hmac_sha256(key, message):
h = hmac.HMAC(key, hashes.SHA256(), backend=backend)
h.update(message)
return h.finalize()
def encrypt(key, iv, salt, plaintext, iterations=1000):
"""
Returns:
(bytes) ciphertext
"""
if len(salt) != 16:
raise Exception("Expected 128 bits of salt - got %i bits" % len((salt) * 8))
if len(iv) != 16:
raise Exception("Expected 128 bits of IV - got %i bits" % (len(iv) * 8))
sha = hashes.SHA512()
kdf = pbkdf2.PBKDF2HMAC(sha, 64, salt, iterations, backend)
k = kdf.derive(key)
aes_key = k[0:32]
sha_key = k[32:]
packed_file = (
b"\x01" # version
+ salt
+ iv
+ struct.pack(">L", iterations)
+ encrypt_ctr(aes_key, iv, plaintext)
)
packed_file += hmac_sha256(sha_key, packed_file)
return (
b"-----BEGIN MEGOLM SESSION DATA-----\n" +
base64.encodestring(packed_file) +
b"-----END MEGOLM SESSION DATA-----"
)
def gen(password, iv, salt, plaintext, iterations=1000):
ciphertext = encrypt(
password.encode('utf-8'), iv, salt, plaintext.encode('utf-8'), iterations
)
return (plaintext, password, ciphertext.decode('utf-8'))
print (json.dumps([
gen("password", b"\x88"*16, b"saltsaltsaltsalt", "plain", 10),
gen("betterpassword", b"\xFF"*8 + b"\x00"*8, b"moresaltmoresalt", "Hello, World"),
gen("SWORDFISH", b"\xFF"*8 + b"\x00"*8, b"yessaltygoodness", "alphanumerically" * 4),
gen("password"*32, b"\xFF"*16, b"\xFF"*16, "alphanumerically" * 4),
], indent=4))