add test for the MessageEncrypter using Session protocol

pull/1421/head
Audric Ackermann 4 years ago
parent a511ceec55
commit adf0d03d35
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -173,6 +173,7 @@
"bower": "1.8.2",
"chai": "4.1.2",
"chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2",
"css-loader": "^3.6.0",
"dashdash": "1.14.1",
"electron": "8.2.0",
@ -334,4 +335,4 @@
"!dev-app-update.yml"
]
}
}
}

@ -195,6 +195,7 @@ class ActionsPanelPrivate extends React.Component<Props> {
if (userED25519KeyPairHex) {
return;
}
window.showResetSessionIdDialog();
}
}

@ -203,7 +203,6 @@ async function decryptWithSessionProtocol(
const plaintext = plaintextWithMetadata.subarray(0, plainTextEnd);
// 3. ) Verify the signature
// FIXME, why don't we have a sodium.crypto_sign_verify ?
const isValid = sodium.crypto_sign_verify_detached(
signature,
concatUInt8Array(

@ -6,6 +6,7 @@ import { encryptWithSenderKey } from '../../session/medium_group/ratchet';
import { PubKey } from '../types';
import { StringUtils } from '../utils';
import { concatUInt8Array, getSodium } from '.';
export { concatUInt8Array, getSodium };
/**
* Add padding to a message buffer
@ -95,13 +96,16 @@ export async function encryptUsingSessionProtocol(
);
// merge all arrays into one
const data = concatUInt8Array(
const dataForSign = concatUInt8Array(
plaintext,
userED25519PubKeyBytes,
recipientX25519PublicKey
);
const signature = sodium.crypto_sign(data, userED25519SecretKeyBytes);
const signature = sodium.crypto_sign_detached(
dataForSign,
userED25519SecretKeyBytes
);
if (!signature) {
throw new Error("Couldn't sign message");
}

@ -1,13 +1,23 @@
import { expect } from 'chai';
import chai, { expect } from 'chai';
import * as crypto from 'crypto';
import * as sinon from 'sinon';
import { MessageEncrypter } from '../../../../session/crypto';
import {
concatUInt8Array,
getSodium,
MessageEncrypter,
} from '../../../../session/crypto';
import { EncryptionType } from '../../../../session/types/EncryptionType';
import { Stubs, TestUtils } from '../../../test-utils';
import { UserUtil } from '../../../../util';
import { SignalService } from '../../../../protobuf';
import * as Ratchet from '../../../../session/medium_group/ratchet';
import { StringUtils } from '../../../../session/utils';
import chaiBytes from 'chai-bytes';
import { PubKey } from '../../../../session/types';
import { fromHex, toHex } from '../../../../session/utils/String';
chai.use(chaiBytes);
// tslint:disable-next-line: max-func-body-length
describe('MessageEncrypter', () => {
@ -61,9 +71,9 @@ describe('MessageEncrypter', () => {
data,
EncryptionType.MediumGroup
);
expect(result.envelopeType).to.deep.equal(
SignalService.Envelope.Type.MEDIUM_GROUP_CIPHERTEXT
);
chai
.expect(result.envelopeType)
.to.deep.equal(SignalService.Envelope.Type.MEDIUM_GROUP_CIPHERTEXT);
});
});
@ -79,10 +89,9 @@ describe('MessageEncrypter', () => {
data,
EncryptionType.Fallback
);
expect(spy.called).to.equal(
true,
'FallbackSessionCipher.encrypt should be called.'
);
chai
.expect(spy.called)
.to.equal(true, 'FallbackSessionCipher.encrypt should be called.');
});
it('should pass the padded message body to encrypt', async () => {
@ -99,7 +108,7 @@ describe('MessageEncrypter', () => {
const paddedData = MessageEncrypter.padPlainTextBuffer(data);
const firstArgument = new Uint8Array(spy.args[0][0]);
expect(firstArgument).to.deep.equal(paddedData);
chai.expect(firstArgument).to.deep.equal(paddedData);
});
it('should return an UNIDENTIFIED SENDER envelope type', async () => {
@ -109,9 +118,9 @@ describe('MessageEncrypter', () => {
data,
EncryptionType.Fallback
);
expect(result.envelopeType).to.deep.equal(
SignalService.Envelope.Type.UNIDENTIFIED_SENDER
);
chai
.expect(result.envelopeType)
.to.deep.equal(SignalService.Envelope.Type.UNIDENTIFIED_SENDER);
});
});
});
@ -136,13 +145,232 @@ describe('MessageEncrypter', () => {
senderDevice: 1,
});
expect(device).to.equal(user.key);
expect(certificate.toJSON()).to.deep.equal(
expectedCertificate.toJSON()
);
chai.expect(device).to.equal(user.key);
chai
.expect(certificate.toJSON())
.to.deep.equal(expectedCertificate.toJSON());
spy.restore();
}
});
});
// tslint:disable-next-line: max-func-body-length
describe('Session Protocol', () => {
let sandboxSessionProtocol: sinon.SinonSandbox;
const ourUserEd25516Keypair = {
pubKey:
'37e1631b002de498caf7c5c1712718bde7f257c6dadeed0c21abf5e939e6c309',
privKey:
'be1d11154ff9b6de77873f0b6b0bcc460000000000000000000000000000000037e1631b002de498caf7c5c1712718bde7f257c6dadeed0c21abf5e939e6c309',
};
const ourIdentityKeypair = {
pubKey: new Uint8Array([
5,
44,
2,
168,
162,
203,
50,
66,
136,
81,
30,
221,
57,
245,
1,
148,
162,
194,
255,
47,
134,
104,
180,
207,
188,
18,
71,
62,
58,
107,
23,
92,
97,
]),
privKey: new Uint8Array([
200,
45,
226,
75,
253,
235,
213,
108,
187,
188,
217,
9,
51,
105,
65,
15,
97,
36,
233,
33,
21,
31,
7,
90,
145,
30,
52,
254,
47,
162,
192,
105,
]),
};
beforeEach(async () => {
sandboxSessionProtocol = sinon.createSandbox();
sandboxSessionProtocol
.stub(UserUtil, 'getUserED25519KeyPair')
.resolves(ourUserEd25516Keypair);
sandboxSessionProtocol
.stub(UserUtil, 'getIdentityKeyPair')
.resolves(ourIdentityKeypair);
});
afterEach(() => {
sandboxSessionProtocol.restore();
});
it('should pass the correct data for sodium crypto_sign', async () => {
const keypair = await UserUtil.getUserED25519KeyPair();
const recipient = TestUtils.generateFakePubKey();
const sodium = await getSodium();
const cryptoSignDetachedSpy = sandboxSessionProtocol.spy(
sodium,
'crypto_sign_detached'
);
const plainText = '123456';
const plainTextBytes = new Uint8Array(
StringUtils.encode(plainText, 'utf8')
);
const userED25519PubKeyBytes = new Uint8Array(
// tslint:disable: no-non-null-assertion
StringUtils.fromHex(keypair!.pubKey)
);
const recipientX25519PublicKeyWithoutPrefix = PubKey.remove05PrefixIfNeeded(
recipient.key
);
const recipientX25519PublicKey = new Uint8Array(
StringUtils.fromHex(recipientX25519PublicKeyWithoutPrefix)
);
await MessageEncrypter.encryptUsingSessionProtocol(
recipient,
plainTextBytes
);
const [
dataForSign,
userED25519SecretKeyBytes,
] = cryptoSignDetachedSpy.args[0];
const userEdPrivkeyBytes = new Uint8Array(
StringUtils.fromHex(keypair!.privKey)
);
expect(userED25519SecretKeyBytes).to.equalBytes(userEdPrivkeyBytes);
// dataForSign must be plaintext | userED25519PubKeyBytes | recipientX25519PublicKey
expect(
(dataForSign as Uint8Array).subarray(0, plainTextBytes.length)
).to.equalBytes(plainTextBytes);
expect(
(dataForSign as Uint8Array).subarray(
plainTextBytes.length,
plainTextBytes.length + userED25519PubKeyBytes.length
)
).to.equalBytes(userED25519PubKeyBytes);
// the recipient pubkey must have its 05 prefix removed
expect(
(dataForSign as Uint8Array).subarray(
plainTextBytes.length + userED25519PubKeyBytes.length
)
).to.equalBytes(recipientX25519PublicKey);
});
it('should return valid decodable ciphertext', async () => {
// for testing, we encode a message to ourself
const userX25519KeyPair = await UserUtil.getIdentityKeyPair();
const userEd25519KeyPair = await UserUtil.getUserED25519KeyPair();
const plainTextBytes = new Uint8Array(
StringUtils.encode('123456789', 'utf8')
);
const sodium = await getSodium();
const recipientX25519PrivateKey = userX25519KeyPair!.privKey;
const recipientX25519PublicKeyHex = toHex(userX25519KeyPair!.pubKey);
const recipientX25519PublicKeyWithoutPrefix = PubKey.remove05PrefixIfNeeded(
recipientX25519PublicKeyHex
);
const recipientX25519PublicKey = new PubKey(
recipientX25519PublicKeyWithoutPrefix
);
const ciphertext = await MessageEncrypter.encryptUsingSessionProtocol(
recipientX25519PublicKey,
plainTextBytes
);
// decrypt content
const plaintextWithMetadata = sodium.crypto_box_seal_open(
ciphertext,
new Uint8Array(fromHex(recipientX25519PublicKey.key)),
new Uint8Array(recipientX25519PrivateKey)
);
// get message parts
const signatureSize = sodium.crypto_sign_BYTES;
const ed25519PublicKeySize = sodium.crypto_sign_PUBLICKEYBYTES;
const signatureStart = plaintextWithMetadata.byteLength - signatureSize;
const signature = plaintextWithMetadata.subarray(signatureStart);
const pubkeyStart =
plaintextWithMetadata.byteLength -
(signatureSize + ed25519PublicKeySize);
const pubkeyEnd = plaintextWithMetadata.byteLength - signatureSize;
// this should be ours ed25519 pubkey
const senderED25519PublicKey = plaintextWithMetadata.subarray(
pubkeyStart,
pubkeyEnd
);
const plainTextEnd =
plaintextWithMetadata.byteLength -
(signatureSize + ed25519PublicKeySize);
const plaintextDecoded = plaintextWithMetadata.subarray(0, plainTextEnd);
expect(plaintextDecoded).to.equalBytes(plainTextBytes);
expect(senderED25519PublicKey).to.equalBytes(userEd25519KeyPair!.pubKey);
// verify the signature is valid
const dataForVerify = concatUInt8Array(
plaintextDecoded,
senderED25519PublicKey,
new Uint8Array(fromHex(recipientX25519PublicKey.key))
);
const isValid = sodium.crypto_sign_verify_detached(
signature,
dataForVerify,
senderED25519PublicKey
);
expect(isValid).to.be.equal(true, 'the signature cannot be verified');
});
});
});

@ -25,7 +25,7 @@ export async function getPrimary(): Promise<PrimaryPubKey> {
}
/**
* This return the stored x25519 identity keypair for that user
* This return the stored x25519 identity keypair for the current logged in user
*/
export async function getIdentityKeyPair(): Promise<KeyPair | undefined> {
const item = await getItemById('identityKey');

@ -2044,6 +2044,11 @@ chai-as-promised@^7.1.1:
dependencies:
check-error "^1.0.2"
chai-bytes@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/chai-bytes/-/chai-bytes-0.1.2.tgz#c297e81d47eb3106af0676ded5bb5e0c9f981db3"
integrity sha512-0ol6oJS0y1ozj6AZK8n1pyv1/G+l44nqUJygAkK1UrYl+IOGie5vcrEdrAlwmLYGIA9NVvtHWosPYwWWIXf/XA==
chai@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c"

Loading…
Cancel
Save