Handle both given and family name in decrypted profile name
* Decrypt given and family names from profile name string * Handle both given and family name from decrypted profile name * Ensure we properly handle profiles with no family name
This commit is contained in:
parent
4f50c0b093
commit
11266cb775
9 changed files with 326 additions and 39 deletions
|
@ -9,7 +9,7 @@
|
|||
const PROFILE_IV_LENGTH = 12; // bytes
|
||||
const PROFILE_KEY_LENGTH = 32; // bytes
|
||||
const PROFILE_TAG_LENGTH = 128; // bits
|
||||
const PROFILE_NAME_PADDED_LENGTH = 26; // bytes
|
||||
const PROFILE_NAME_PADDED_LENGTH = 53; // bytes
|
||||
|
||||
function verifyDigest(data, theirDigest) {
|
||||
return crypto.subtle.digest({ name: 'SHA-256' }, data).then(ourDigest => {
|
||||
|
@ -208,18 +208,39 @@
|
|||
'base64'
|
||||
).toArrayBuffer();
|
||||
return textsecure.crypto.decryptProfile(data, key).then(decrypted => {
|
||||
// unpad
|
||||
const padded = new Uint8Array(decrypted);
|
||||
let i;
|
||||
for (i = padded.length; i > 0; i -= 1) {
|
||||
if (padded[i - 1] !== 0x00) {
|
||||
|
||||
// Given name is the start of the string to the first null character
|
||||
let givenEnd;
|
||||
for (givenEnd = 0; givenEnd < padded.length; givenEnd += 1) {
|
||||
if (padded[givenEnd] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dcodeIO.ByteBuffer.wrap(padded)
|
||||
.slice(0, i)
|
||||
.toArrayBuffer();
|
||||
// Family name is the next chunk of non-null characters after that first null
|
||||
let familyEnd;
|
||||
for (
|
||||
familyEnd = givenEnd + 1;
|
||||
familyEnd < padded.length;
|
||||
familyEnd += 1
|
||||
) {
|
||||
if (padded[familyEnd] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const foundFamilyName = familyEnd > givenEnd + 1;
|
||||
|
||||
return {
|
||||
given: dcodeIO.ByteBuffer.wrap(padded)
|
||||
.slice(0, givenEnd)
|
||||
.toArrayBuffer(),
|
||||
family: foundFamilyName
|
||||
? dcodeIO.ByteBuffer.wrap(padded)
|
||||
.slice(givenEnd + 1, familyEnd)
|
||||
.toArrayBuffer()
|
||||
: null,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global libsignal, textsecure */
|
||||
|
||||
describe('encrypting and decrypting profile data', () => {
|
||||
const NAME_PADDED_LENGTH = 26;
|
||||
const NAME_PADDED_LENGTH = 53;
|
||||
describe('encrypting and decrypting profile names', () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', () => {
|
||||
const name = 'Alice';
|
||||
|
@ -14,11 +14,78 @@ describe('encrypting and decrypting profile data', () => {
|
|||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(decrypted => {
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
name
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('handles a given name of the max, 53 characters', () => {
|
||||
const name = '01234567890123456789012345678901234567890123456789123';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
name
|
||||
);
|
||||
assert.strictEqual(family, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('handles family/given name of the max, 53 characters', () => {
|
||||
const name = '01234567890123456789\u000001234567890123456789012345678912';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
'01234567890123456789'
|
||||
);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(family).toString('utf8'),
|
||||
'01234567890123456789012345678912'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('handles a string with family/given name', () => {
|
||||
const name = 'Alice\0Jones';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
'Alice'
|
||||
);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(family).toString('utf8'),
|
||||
'Jones'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -32,10 +99,11 @@ describe('encrypting and decrypting profile data', () => {
|
|||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(decrypted => {
|
||||
assert.strictEqual(decrypted.byteLength, 0);
|
||||
.then(({ given, family }) => {
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(given.byteLength, 0);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||
dcodeIO.ByteBuffer.wrap(given).toString('utf8'),
|
||||
''
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue