Uint8Array migration
This commit is contained in:
parent
daf75190b8
commit
4ef0bf96cc
137 changed files with 2202 additions and 3170 deletions
|
@ -3,141 +3,160 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import * as Bytes from '../Bytes';
|
||||
import * as Curve from '../Curve';
|
||||
import * as Crypto from '../Crypto';
|
||||
import TSCrypto, { PaddedLengths } from '../textsecure/Crypto';
|
||||
import {
|
||||
PaddedLengths,
|
||||
encryptProfileItemWithPadding,
|
||||
decryptProfileName,
|
||||
encryptProfile,
|
||||
decryptProfile,
|
||||
getRandomBytes,
|
||||
constantTimeEqual,
|
||||
generateRegistrationId,
|
||||
deriveSecrets,
|
||||
encryptDeviceName,
|
||||
decryptDeviceName,
|
||||
deriveAccessKey,
|
||||
getAccessKeyVerifier,
|
||||
verifyAccessKey,
|
||||
deriveMasterKeyFromGroupV1,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric,
|
||||
hmacSha256,
|
||||
verifyHmacSha256,
|
||||
uuidToBytes,
|
||||
bytesToUuid,
|
||||
} from '../Crypto';
|
||||
|
||||
describe('Crypto', () => {
|
||||
describe('encrypting and decrypting profile data', () => {
|
||||
const NAME_PADDED_LENGTH = 53;
|
||||
describe('encrypting and decrypting profile names', () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', async () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', () => {
|
||||
const name = 'Alice';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), name);
|
||||
assert.strictEqual(Bytes.toString(given), name);
|
||||
});
|
||||
|
||||
it('handles a given name of the max, 53 characters', async () => {
|
||||
it('handles a given name of the max, 53 characters', () => {
|
||||
const name = '01234567890123456789012345678901234567890123456789123';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), name);
|
||||
assert.strictEqual(Bytes.toString(given), name);
|
||||
assert.strictEqual(family, null);
|
||||
});
|
||||
|
||||
it('handles family/given name of the max, 53 characters', async () => {
|
||||
it('handles family/given name of the max, 53 characters', () => {
|
||||
const name =
|
||||
'01234567890123456789\u000001234567890123456789012345678912';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(Bytes.toString(given), '01234567890123456789');
|
||||
assert.strictEqual(
|
||||
Crypto.stringFromBytes(given),
|
||||
'01234567890123456789'
|
||||
);
|
||||
assert.strictEqual(
|
||||
family && Crypto.stringFromBytes(family),
|
||||
family && Bytes.toString(family),
|
||||
'01234567890123456789012345678912'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles a string with family/given name', async () => {
|
||||
it('handles a string with family/given name', () => {
|
||||
const name = 'Alice\0Jones';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), 'Alice');
|
||||
assert.strictEqual(family && Crypto.stringFromBytes(family), 'Jones');
|
||||
assert.strictEqual(Bytes.toString(given), 'Alice');
|
||||
assert.strictEqual(family && Bytes.toString(family), 'Jones');
|
||||
});
|
||||
|
||||
it('works for empty string', async () => {
|
||||
const name = Crypto.bytesFromString('');
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
it('works for empty string', () => {
|
||||
const name = Bytes.fromString('');
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
name,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(given.byteLength, 0);
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), '');
|
||||
assert.strictEqual(Bytes.toString(given), '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('encrypting and decrypting profile avatars', () => {
|
||||
it('encrypts and decrypts', async () => {
|
||||
const buffer = Crypto.bytesFromString('This is an avatar');
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString('This is an avatar');
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfile(buffer, key);
|
||||
const encrypted = encryptProfile(buffer, key);
|
||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||
|
||||
const decrypted = await TSCrypto.decryptProfile(encrypted, key);
|
||||
assert(Crypto.constantTimeEqual(buffer, decrypted));
|
||||
const decrypted = decryptProfile(encrypted, key);
|
||||
assert(constantTimeEqual(buffer, decrypted));
|
||||
});
|
||||
|
||||
it('throws when decrypting with the wrong key', async () => {
|
||||
const buffer = Crypto.bytesFromString('This is an avatar');
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const badKey = Crypto.getRandomBytes(32);
|
||||
it('throws when decrypting with the wrong key', () => {
|
||||
const buffer = Bytes.fromString('This is an avatar');
|
||||
const key = getRandomBytes(32);
|
||||
const badKey = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfile(buffer, key);
|
||||
const encrypted = encryptProfile(buffer, key);
|
||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||
await assert.isRejected(
|
||||
TSCrypto.decryptProfile(encrypted, badKey),
|
||||
assert.throws(
|
||||
() => decryptProfile(encrypted, badKey),
|
||||
'Failed to decrypt profile data. Most likely the profile key has changed.'
|
||||
);
|
||||
});
|
||||
|
@ -147,7 +166,7 @@ describe('Crypto', () => {
|
|||
describe('generateRegistrationId', () => {
|
||||
it('generates an integer between 0 and 16383 (inclusive)', () => {
|
||||
for (let i = 0; i < 100; i += 1) {
|
||||
const id = Crypto.generateRegistrationId();
|
||||
const id = generateRegistrationId();
|
||||
assert.isAtLeast(id, 0);
|
||||
assert.isAtMost(id, 16383);
|
||||
assert(Number.isInteger(id));
|
||||
|
@ -157,27 +176,27 @@ describe('Crypto', () => {
|
|||
|
||||
describe('deriveSecrets', () => {
|
||||
it('derives key parts via HKDF', () => {
|
||||
const input = Crypto.getRandomBytes(32);
|
||||
const salt = Crypto.getRandomBytes(32);
|
||||
const info = Crypto.bytesFromString('Hello world');
|
||||
const result = Crypto.deriveSecrets(input, salt, info);
|
||||
const input = getRandomBytes(32);
|
||||
const salt = getRandomBytes(32);
|
||||
const info = Bytes.fromString('Hello world');
|
||||
const result = deriveSecrets(input, salt, info);
|
||||
assert.lengthOf(result, 3);
|
||||
result.forEach(part => {
|
||||
// This is a smoke test; HKDF is tested as part of @signalapp/signal-client.
|
||||
assert.instanceOf(part, ArrayBuffer);
|
||||
assert.instanceOf(part, Uint8Array);
|
||||
assert.strictEqual(part.byteLength, 32);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessKey/profileKey', () => {
|
||||
it('verification roundtrips', async () => {
|
||||
const profileKey = await Crypto.getRandomBytes(32);
|
||||
const accessKey = await Crypto.deriveAccessKey(profileKey);
|
||||
it('verification roundtrips', () => {
|
||||
const profileKey = getRandomBytes(32);
|
||||
const accessKey = deriveAccessKey(profileKey);
|
||||
|
||||
const verifier = await Crypto.getAccessKeyVerifier(accessKey);
|
||||
const verifier = getAccessKeyVerifier(accessKey);
|
||||
|
||||
const correct = await Crypto.verifyAccessKey(accessKey, verifier);
|
||||
const correct = verifyAccessKey(accessKey, verifier);
|
||||
|
||||
assert.strictEqual(correct, true);
|
||||
});
|
||||
|
@ -208,12 +227,12 @@ describe('Crypto', () => {
|
|||
];
|
||||
|
||||
vectors.forEach((vector, index) => {
|
||||
it(`vector ${index}`, async () => {
|
||||
const gv1 = Crypto.hexToArrayBuffer(vector.gv1);
|
||||
it(`vector ${index}`, () => {
|
||||
const gv1 = Bytes.fromHex(vector.gv1);
|
||||
const expectedHex = vector.masterKey;
|
||||
|
||||
const actual = await Crypto.deriveMasterKeyFromGroupV1(gv1);
|
||||
const actualHex = Crypto.arrayBufferToHex(actual);
|
||||
const actual = deriveMasterKeyFromGroupV1(gv1);
|
||||
const actualHex = Bytes.toHex(actual);
|
||||
|
||||
assert.strictEqual(actualHex, expectedHex);
|
||||
});
|
||||
|
@ -221,34 +240,30 @@ describe('Crypto', () => {
|
|||
});
|
||||
|
||||
describe('symmetric encryption', () => {
|
||||
it('roundtrips', async () => {
|
||||
it('roundtrips', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const decrypted = await Crypto.decryptSymmetric(key, encrypted);
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
const decrypted = decryptSymmetric(key, encrypted);
|
||||
|
||||
const equal = Crypto.constantTimeEqual(plaintext, decrypted);
|
||||
const equal = constantTimeEqual(plaintext, decrypted);
|
||||
if (!equal) {
|
||||
throw new Error('The output and input did not match!');
|
||||
}
|
||||
});
|
||||
|
||||
it('roundtrip fails if nonce is modified', async () => {
|
||||
it('roundtrip fails if nonce is modified', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const uintArray = new Uint8Array(encrypted);
|
||||
uintArray[2] += 2;
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
encrypted[2] += 2;
|
||||
|
||||
try {
|
||||
await Crypto.decryptSymmetric(
|
||||
key,
|
||||
Crypto.typedArrayToArrayBuffer(uintArray)
|
||||
);
|
||||
decryptSymmetric(key, encrypted);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -260,20 +275,16 @@ describe('Crypto', () => {
|
|||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
|
||||
it('roundtrip fails if mac is modified', async () => {
|
||||
it('roundtrip fails if mac is modified', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const uintArray = new Uint8Array(encrypted);
|
||||
uintArray[uintArray.length - 3] += 2;
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
encrypted[encrypted.length - 3] += 2;
|
||||
|
||||
try {
|
||||
await Crypto.decryptSymmetric(
|
||||
key,
|
||||
Crypto.typedArrayToArrayBuffer(uintArray)
|
||||
);
|
||||
decryptSymmetric(key, encrypted);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -285,20 +296,16 @@ describe('Crypto', () => {
|
|||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
|
||||
it('roundtrip fails if encrypted contents are modified', async () => {
|
||||
it('roundtrip fails if encrypted contents are modified', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const uintArray = new Uint8Array(encrypted);
|
||||
uintArray[35] += 9;
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
encrypted[35] += 9;
|
||||
|
||||
try {
|
||||
await Crypto.decryptSymmetric(
|
||||
key,
|
||||
Crypto.typedArrayToArrayBuffer(uintArray)
|
||||
);
|
||||
decryptSymmetric(key, encrypted);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -312,33 +319,24 @@ describe('Crypto', () => {
|
|||
});
|
||||
|
||||
describe('encrypted device name', () => {
|
||||
it('roundtrips', async () => {
|
||||
it('roundtrips', () => {
|
||||
const deviceName = 'v1.19.0 on Windows 10';
|
||||
const identityKey = Curve.generateKeyPair();
|
||||
|
||||
const encrypted = await Crypto.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKey.pubKey
|
||||
);
|
||||
const decrypted = await Crypto.decryptDeviceName(
|
||||
encrypted,
|
||||
identityKey.privKey
|
||||
);
|
||||
const encrypted = encryptDeviceName(deviceName, identityKey.pubKey);
|
||||
const decrypted = decryptDeviceName(encrypted, identityKey.privKey);
|
||||
|
||||
assert.strictEqual(decrypted, deviceName);
|
||||
});
|
||||
|
||||
it('fails if iv is changed', async () => {
|
||||
it('fails if iv is changed', () => {
|
||||
const deviceName = 'v1.19.0 on Windows 10';
|
||||
const identityKey = Curve.generateKeyPair();
|
||||
|
||||
const encrypted = await Crypto.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKey.pubKey
|
||||
);
|
||||
encrypted.syntheticIv = Crypto.getRandomBytes(16);
|
||||
const encrypted = encryptDeviceName(deviceName, identityKey.pubKey);
|
||||
encrypted.syntheticIv = getRandomBytes(16);
|
||||
try {
|
||||
await Crypto.decryptDeviceName(encrypted, identityKey.privKey);
|
||||
decryptDeviceName(encrypted, identityKey.privKey);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -348,46 +346,15 @@ describe('Crypto', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('attachment encryption', () => {
|
||||
it('roundtrips', async () => {
|
||||
const staticKeyPair = Curve.generateKeyPair();
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const path =
|
||||
'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa';
|
||||
|
||||
const encrypted = await Crypto.encryptAttachment(
|
||||
staticKeyPair.pubKey.slice(1),
|
||||
path,
|
||||
plaintext
|
||||
);
|
||||
const decrypted = await Crypto.decryptAttachment(
|
||||
staticKeyPair.privKey,
|
||||
path,
|
||||
encrypted
|
||||
);
|
||||
|
||||
const equal = Crypto.constantTimeEqual(plaintext, decrypted);
|
||||
if (!equal) {
|
||||
throw new Error('The output and input did not match!');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyHmacSha256', () => {
|
||||
it('rejects if their MAC is too short', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourMac = await Crypto.hmacSha256(key, plaintext);
|
||||
it('rejects if their MAC is too short', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourMac = hmacSha256(key, plaintext);
|
||||
const theirMac = ourMac.slice(0, -1);
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
ourMac.byteLength
|
||||
);
|
||||
verifyHmacSha256(plaintext, key, theirMac, ourMac.byteLength);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -395,19 +362,14 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC length');
|
||||
});
|
||||
|
||||
it('rejects if their MAC is too long', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourMac = await Crypto.hmacSha256(key, plaintext);
|
||||
const theirMac = Crypto.concatenateBytes(ourMac, new Uint8Array([0xff]));
|
||||
it('rejects if their MAC is too long', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourMac = hmacSha256(key, plaintext);
|
||||
const theirMac = Bytes.concatenate([ourMac, new Uint8Array([0xff])]);
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
ourMac.byteLength
|
||||
);
|
||||
verifyHmacSha256(plaintext, key, theirMac, ourMac.byteLength);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -415,19 +377,14 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC length');
|
||||
});
|
||||
|
||||
it('rejects if our MAC is shorter than the specified length', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourMac = await Crypto.hmacSha256(key, plaintext);
|
||||
it('rejects if our MAC is shorter than the specified length', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourMac = hmacSha256(key, plaintext);
|
||||
const theirMac = ourMac;
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
ourMac.byteLength + 1
|
||||
);
|
||||
verifyHmacSha256(plaintext, key, theirMac, ourMac.byteLength + 1);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -435,20 +392,15 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC length');
|
||||
});
|
||||
|
||||
it("rejects if the MACs don't match", async () => {
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourKey = Crypto.getRandomBytes(32);
|
||||
const ourMac = await Crypto.hmacSha256(ourKey, plaintext);
|
||||
const theirKey = Crypto.getRandomBytes(32);
|
||||
const theirMac = await Crypto.hmacSha256(theirKey, plaintext);
|
||||
it("rejects if the MACs don't match", () => {
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourKey = getRandomBytes(32);
|
||||
const ourMac = hmacSha256(ourKey, plaintext);
|
||||
const theirKey = getRandomBytes(32);
|
||||
const theirMac = hmacSha256(theirKey, plaintext);
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
ourKey,
|
||||
theirMac,
|
||||
ourMac.byteLength
|
||||
);
|
||||
verifyHmacSha256(plaintext, ourKey, theirMac, ourMac.byteLength);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -456,11 +408,11 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC');
|
||||
});
|
||||
|
||||
it('resolves with undefined if the MACs match exactly', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const theirMac = await Crypto.hmacSha256(key, plaintext);
|
||||
const result = await Crypto.verifyHmacSha256(
|
||||
it('resolves with undefined if the MACs match exactly', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const theirMac = hmacSha256(key, plaintext);
|
||||
const result = verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
|
@ -469,11 +421,11 @@ describe('Crypto', () => {
|
|||
assert.isUndefined(result);
|
||||
});
|
||||
|
||||
it('resolves with undefined if the first `length` bytes of the MACs match', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const theirMac = (await Crypto.hmacSha256(key, plaintext)).slice(0, -5);
|
||||
const result = await Crypto.verifyHmacSha256(
|
||||
it('resolves with undefined if the first `length` bytes of the MACs match', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const theirMac = hmacSha256(key, plaintext).slice(0, -5);
|
||||
const result = verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
|
@ -483,102 +435,86 @@ describe('Crypto', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('uuidToArrayBuffer', () => {
|
||||
const { uuidToArrayBuffer } = Crypto;
|
||||
|
||||
it('converts valid UUIDs to ArrayBuffers', () => {
|
||||
const expectedResult = Crypto.typedArrayToArrayBuffer(
|
||||
new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
])
|
||||
);
|
||||
describe('uuidToBytes', () => {
|
||||
it('converts valid UUIDs to Uint8Arrays', () => {
|
||||
const expectedResult = new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('226e4402-7ffc-4543-85c9-4622c50a5b14'),
|
||||
uuidToBytes('226e4402-7ffc-4543-85c9-4622c50a5b14'),
|
||||
expectedResult
|
||||
);
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('226E4402-7FFC-4543-85C9-4622C50A5B14'),
|
||||
uuidToBytes('226E4402-7FFC-4543-85C9-4622C50A5B14'),
|
||||
expectedResult
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty ArrayBuffer for strings of the wrong length', () => {
|
||||
assert.deepEqual(uuidToArrayBuffer(''), new ArrayBuffer(0));
|
||||
assert.deepEqual(uuidToArrayBuffer('abc'), new ArrayBuffer(0));
|
||||
it('returns an empty Uint8Array for strings of the wrong length', () => {
|
||||
assert.deepEqual(uuidToBytes(''), new Uint8Array(0));
|
||||
assert.deepEqual(uuidToBytes('abc'), new Uint8Array(0));
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('032deadf0d5e4ee78da28e75b1dfb284'),
|
||||
new ArrayBuffer(0)
|
||||
uuidToBytes('032deadf0d5e4ee78da28e75b1dfb284'),
|
||||
new Uint8Array(0)
|
||||
);
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'),
|
||||
new ArrayBuffer(0)
|
||||
uuidToBytes('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'),
|
||||
new Uint8Array(0)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayBufferToUuid', () => {
|
||||
const { arrayBufferToUuid } = Crypto;
|
||||
|
||||
it('converts valid ArrayBuffers to UUID strings', () => {
|
||||
const buf = Crypto.typedArrayToArrayBuffer(
|
||||
new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
])
|
||||
);
|
||||
describe('bytesToUuid', () => {
|
||||
it('converts valid Uint8Arrays to UUID strings', () => {
|
||||
const buf = new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
arrayBufferToUuid(buf),
|
||||
bytesToUuid(buf),
|
||||
'226e4402-7ffc-4543-85c9-4622c50a5b14'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns undefined if passed an all-zero buffer', () => {
|
||||
assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(16)));
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array(16)));
|
||||
});
|
||||
|
||||
it('returns undefined if passed the wrong number of bytes', () => {
|
||||
assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(0)));
|
||||
assert.isUndefined(
|
||||
arrayBufferToUuid(
|
||||
Crypto.typedArrayToArrayBuffer(new Uint8Array([0x22]))
|
||||
)
|
||||
);
|
||||
assert.isUndefined(
|
||||
arrayBufferToUuid(
|
||||
Crypto.typedArrayToArrayBuffer(new Uint8Array(Array(17).fill(0x22)))
|
||||
)
|
||||
);
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array(0)));
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array([0x22])));
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array(Array(17).fill(0x22))));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue