Encryption support for backup and restore
Also moved to the _ prefix in backup.js for all private methods exported for testing.
This commit is contained in:
parent
6d8f4b7b6e
commit
cea42bde7d
6 changed files with 509 additions and 85 deletions
|
@ -1,77 +1,157 @@
|
|||
'use strict';
|
||||
|
||||
describe('Backup', function() {
|
||||
describe('sanitizeFileName', function() {
|
||||
describe('_sanitizeFileName', function() {
|
||||
it('leaves a basic string alone', function() {
|
||||
var initial = 'Hello, how are you #5 (\'fine\' + great).jpg';
|
||||
var expected = initial;
|
||||
assert.strictEqual(Signal.Backup.sanitizeFileName(initial), expected);
|
||||
assert.strictEqual(Signal.Backup._sanitizeFileName(initial), expected);
|
||||
});
|
||||
|
||||
it('replaces all unknown characters', function() {
|
||||
var initial = '!@$%^&*=';
|
||||
var expected = '________';
|
||||
assert.strictEqual(Signal.Backup.sanitizeFileName(initial), expected);
|
||||
assert.strictEqual(Signal.Backup._sanitizeFileName(initial), expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trimFileName', function() {
|
||||
describe('_trimFileName', function() {
|
||||
it('handles a file with no extension', function() {
|
||||
var initial = '0123456789012345678901234567890123456789';
|
||||
var expected = '012345678901234567890123456789';
|
||||
assert.strictEqual(Signal.Backup.trimFileName(initial), expected);
|
||||
assert.strictEqual(Signal.Backup._trimFileName(initial), expected);
|
||||
});
|
||||
|
||||
it('handles a file with a long extension', function() {
|
||||
var initial = '0123456789012345678901234567890123456789.01234567890123456789';
|
||||
var expected = '012345678901234567890123456789';
|
||||
assert.strictEqual(Signal.Backup.trimFileName(initial), expected);
|
||||
assert.strictEqual(Signal.Backup._trimFileName(initial), expected);
|
||||
});
|
||||
|
||||
it('handles a file with a normal extension', function() {
|
||||
var initial = '01234567890123456789012345678901234567890123456789.jpg';
|
||||
var expected = '012345678901234567890123.jpg';
|
||||
assert.strictEqual(Signal.Backup.trimFileName(initial), expected);
|
||||
assert.strictEqual(Signal.Backup._trimFileName(initial), expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExportAttachmentFileName', function() {
|
||||
describe('_getExportAttachmentFileName', function() {
|
||||
it('uses original filename if attachment has one', function() {
|
||||
var message = {
|
||||
body: 'something',
|
||||
};
|
||||
var index = 0;
|
||||
var attachment = {
|
||||
fileName: 'blah.jpg'
|
||||
};
|
||||
var expected = 'blah.jpg';
|
||||
assert.strictEqual(Signal.Backup.getExportAttachmentFileName(attachment), expected);
|
||||
|
||||
var actual = Signal.Backup._getExportAttachmentFileName(
|
||||
message,
|
||||
index,
|
||||
attachment
|
||||
);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('uses attachment id if no filename', function() {
|
||||
var message = {
|
||||
body: 'something',
|
||||
};
|
||||
var index = 0;
|
||||
var attachment = {
|
||||
id: '123'
|
||||
};
|
||||
var expected = '123';
|
||||
assert.strictEqual(Signal.Backup.getExportAttachmentFileName(attachment), expected);
|
||||
|
||||
var actual = Signal.Backup._getExportAttachmentFileName(
|
||||
message,
|
||||
index,
|
||||
attachment
|
||||
);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('uses filename and contentType if available', function() {
|
||||
var message = {
|
||||
body: 'something',
|
||||
};
|
||||
var index = 0;
|
||||
var attachment = {
|
||||
id: '123',
|
||||
contentType: 'image/jpeg'
|
||||
};
|
||||
var expected = '123.jpeg';
|
||||
assert.strictEqual(Signal.Backup.getExportAttachmentFileName(attachment), expected);
|
||||
|
||||
var actual = Signal.Backup._getExportAttachmentFileName(
|
||||
message,
|
||||
index,
|
||||
attachment
|
||||
);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('handles strange contentType', function() {
|
||||
var message = {
|
||||
body: 'something',
|
||||
};
|
||||
var index = 0;
|
||||
var attachment = {
|
||||
id: '123',
|
||||
contentType: 'something'
|
||||
};
|
||||
var expected = '123.something';
|
||||
assert.strictEqual(Signal.Backup.getExportAttachmentFileName(attachment), expected);
|
||||
|
||||
var actual = Signal.Backup._getExportAttachmentFileName(
|
||||
message,
|
||||
index,
|
||||
attachment
|
||||
);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConversationDirName', function() {
|
||||
describe('_getAnonymousAttachmentFileName', function() {
|
||||
it('uses message id', function() {
|
||||
var message = {
|
||||
id: 'id-45',
|
||||
body: 'something',
|
||||
};
|
||||
var index = 0;
|
||||
var attachment = {
|
||||
fileName: 'blah.jpg'
|
||||
};
|
||||
var expected = 'id-45';
|
||||
|
||||
var actual = Signal.Backup._getAnonymousAttachmentFileName(
|
||||
message,
|
||||
index,
|
||||
attachment
|
||||
);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('appends index if it is above zero', function() {
|
||||
var message = {
|
||||
id: 'id-45',
|
||||
body: 'something',
|
||||
};
|
||||
var index = 1;
|
||||
var attachment = {
|
||||
fileName: 'blah.jpg'
|
||||
};
|
||||
var expected = 'id-45-1';
|
||||
|
||||
var actual = Signal.Backup._getAnonymousAttachmentFileName(
|
||||
message,
|
||||
index,
|
||||
attachment
|
||||
);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getConversationDirName', function() {
|
||||
it('uses name if available', function() {
|
||||
var conversation = {
|
||||
active_at: 123,
|
||||
|
@ -79,7 +159,7 @@ describe('Backup', function() {
|
|||
id: 'id'
|
||||
};
|
||||
var expected = '123 (012345678901234567890123456789 id)';
|
||||
assert.strictEqual(Signal.Backup.getConversationDirName(conversation), expected);
|
||||
assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
|
||||
});
|
||||
|
||||
it('uses just id if name is not available', function() {
|
||||
|
@ -88,20 +168,20 @@ describe('Backup', function() {
|
|||
id: 'id'
|
||||
};
|
||||
var expected = '123 (id)';
|
||||
assert.strictEqual(Signal.Backup.getConversationDirName(conversation), expected);
|
||||
assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
|
||||
});
|
||||
|
||||
it('uses never for missing active_at', function() {
|
||||
it('uses inactive for missing active_at', function() {
|
||||
var conversation = {
|
||||
name: 'name',
|
||||
id: 'id'
|
||||
};
|
||||
var expected = 'never (name id)';
|
||||
assert.strictEqual(Signal.Backup.getConversationDirName(conversation), expected);
|
||||
var expected = 'inactive (name id)';
|
||||
assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConversationLoggingName', function() {
|
||||
describe('_getConversationLoggingName', function() {
|
||||
it('uses plain id if conversation is private', function() {
|
||||
var conversation = {
|
||||
active_at: 123,
|
||||
|
@ -109,7 +189,7 @@ describe('Backup', function() {
|
|||
type: 'private'
|
||||
};
|
||||
var expected = '123 (id)';
|
||||
assert.strictEqual(Signal.Backup.getConversationLoggingName(conversation), expected);
|
||||
assert.strictEqual(Signal.Backup._getConversationLoggingName(conversation), expected);
|
||||
});
|
||||
|
||||
it('uses just id if name is not available', function() {
|
||||
|
@ -119,16 +199,16 @@ describe('Backup', function() {
|
|||
type: 'group'
|
||||
};
|
||||
var expected = '123 ([REDACTED_GROUP]pId)';
|
||||
assert.strictEqual(Signal.Backup.getConversationLoggingName(conversation), expected);
|
||||
assert.strictEqual(Signal.Backup._getConversationLoggingName(conversation), expected);
|
||||
});
|
||||
|
||||
it('uses never for missing active_at', function() {
|
||||
it('uses inactive for missing active_at', function() {
|
||||
var conversation = {
|
||||
id: 'id',
|
||||
type: 'private'
|
||||
};
|
||||
var expected = 'never (id)';
|
||||
assert.strictEqual(Signal.Backup.getConversationLoggingName(conversation), expected);
|
||||
var expected = 'inactive (id)';
|
||||
assert.strictEqual(Signal.Backup._getConversationLoggingName(conversation), expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
83
test/crypto_test.js
Normal file
83
test/crypto_test.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
describe('Crypto', function() {
|
||||
it('roundtrip symmetric encryption succeeds', async function() {
|
||||
var message = 'this is my message';
|
||||
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
|
||||
var key = textsecure.crypto.getRandomBytes(32);
|
||||
|
||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||
var decrypted = await Signal.Crypto.decryptSymmetric(key, encrypted);
|
||||
|
||||
var equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted);
|
||||
if (!equal) {
|
||||
throw new Error('The output and input did not match!');
|
||||
}
|
||||
});
|
||||
|
||||
it('roundtrip fails if nonce is modified', async function() {
|
||||
var message = 'this is my message';
|
||||
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
|
||||
var key = textsecure.crypto.getRandomBytes(32);
|
||||
|
||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||
var uintArray = new Uint8Array(encrypted);
|
||||
uintArray[2] = 9;
|
||||
|
||||
try {
|
||||
var decrypted = await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'decryptSymmetric: Failed to decrypt; MAC verification failed'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
|
||||
it('fails if mac is modified', async function() {
|
||||
var message = 'this is my message';
|
||||
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
|
||||
var key = textsecure.crypto.getRandomBytes(32);
|
||||
|
||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||
var uintArray = new Uint8Array(encrypted);
|
||||
uintArray[uintArray.length - 3] = 9;
|
||||
|
||||
try {
|
||||
var decrypted = await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'decryptSymmetric: Failed to decrypt; MAC verification failed'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
|
||||
it('fails if encrypted contents are modified', async function() {
|
||||
var message = 'this is my message';
|
||||
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
|
||||
var key = textsecure.crypto.getRandomBytes(32);
|
||||
|
||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||
var uintArray = new Uint8Array(encrypted);
|
||||
uintArray[35] = 9;
|
||||
|
||||
try {
|
||||
var decrypted = await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'decryptSymmetric: Failed to decrypt; MAC verification failed'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
});
|
|
@ -640,6 +640,7 @@
|
|||
<script type="text/javascript" src="emoji_util_test.js"></script>
|
||||
<script type="text/javascript" src="reliable_trigger_test.js"></script>
|
||||
<script type="text/javascript" src="backup_test.js"></script>
|
||||
<script type="text/javascript" src="crypto_test.js"></script>
|
||||
<script type="text/javascript" src="database_test.js"></script>
|
||||
<script type="text/javascript" src="i18n_test.js"></script>
|
||||
<script type="text/javascript" src="spellcheck_test.js"></script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue