session-desktop/ts/test/session/unit/crypto/OpenGroupAuthentication_tes...

494 lines
12 KiB
TypeScript

import chai, { expect } from 'chai';
import chaiBytes from 'chai-bytes';
import { SogsBlinding } from '../../../../session/apis/open_group_api/sogsv3/sogsBlinding';
import { ByteKeyPair } from '../../../../session/utils/User';
import { to_hex } from 'libsodium-wrappers-sumo';
import { fromBase64, fromHex } from 'bytebuffer';
import { StringUtils } from '../../../../session/utils';
import { concatUInt8Array, getSodiumRenderer } from '../../../../session/crypto';
import { KeyPrefixType } from '../../../../session/types';
chai.use(chaiBytes);
// tslint:disable-next-line: max-func-body-length
describe('OpenGroupAuthentication', () => {
const secondPartPrivKey = new Uint8Array([
186,
198,
231,
30,
253,
125,
250,
74,
131,
201,
142,
210,
79,
37,
74,
178,
194,
103,
249,
204,
219,
23,
42,
82,
128,
160,
68,
74,
210,
78,
137,
204,
]);
const signingKeysA: ByteKeyPair = {
// 881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4
privKeyBytes: new Uint8Array([
192,
16,
216,
158,
204,
186,
245,
209,
198,
209,
157,
247,
102,
198,
238,
223,
150,
93,
74,
40,
165,
111,
135,
201,
252,
129,
158,
219,
89,
137,
109,
217,
...secondPartPrivKey,
]),
// 057aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d
pubKeyBytes: secondPartPrivKey,
};
const signingKeysB: ByteKeyPair = {
privKeyBytes: new Uint8Array([
130,
56,
83,
227,
58,
149,
251,
148,
119,
85,
180,
81,
17,
190,
245,
33,
219,
6,
246,
238,
110,
61,
191,
133,
244,
223,
32,
32,
121,
172,
138,
198,
215,
25,
249,
139,
235,
31,
251,
12,
100,
87,
84,
131,
231,
45,
87,
251,
204,
133,
20,
3,
118,
71,
29,
47,
245,
62,
216,
163,
254,
248,
195,
109,
]),
pubKeyBytes: new Uint8Array([
215,
25,
249,
139,
235,
31,
251,
12,
100,
87,
84,
131,
231,
45,
87,
251,
204,
133,
20,
3,
118,
71,
29,
47,
245,
62,
216,
163,
254,
248,
195,
109,
]),
};
const serverPubKey = new Uint8Array(
fromHex('c3b3c6f32f0ab5a57f853cc4f30f5da7fda5624b0c77b3fb0829de562ada081d').toArrayBuffer()
);
const ts = 1642472103;
const method = 'GET';
const path = '/room/the-best-room/messages/recent?limit=25';
const nonce = new Uint8Array(fromBase64('CdB5nyKVmQGCw6s0Bvv8Ww==').toArrayBuffer());
const body = 'hello 🎂';
// tslint:disable-next-line: max-func-body-length
describe('HeaderCreation', () => {
describe('Blinded Headers', () => {
it('should produce correct X-SOGS-Nonce', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: true,
body: null,
});
expect(headers['X-SOGS-Nonce']).to.be.equal('CdB5nyKVmQGCw6s0Bvv8Ww==');
});
it('should produce correct X-SOGS-Pubkey', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: true,
body: null,
});
expect(headers['X-SOGS-Pubkey']).to.be.equal(
'1598932d4bccbe595a8789d7eb1629cefc483a0eaddc7e20e8fe5c771efafd9af5'
);
});
it('should produce correct X-SOGS-Timestamp', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: true,
body: null,
});
expect(headers['X-SOGS-Timestamp']).to.be.equal('1642472103');
});
it('should produce correct X-SOGS-Signature without body', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: true,
body: null,
});
expect(headers['X-SOGS-Signature']).to.be.equal(
'gYqpWZX6fnF4Gb2xQM3xaXs0WIYEI49+B8q4mUUEg8Rw0ObaHUWfoWjMHMArAtP9QlORfiydsKWz1o6zdPVeCQ=='
);
});
it('should produce correct X-SOGS-Signature with body', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: true,
body,
});
expect(headers['X-SOGS-Signature']).to.be.equal(
'hZCg5pEoy9t98umaY6fNarzcLP5UKUF8chz5mIjwwrRIQLy1kinRoYcNPdFOpJu8heA0val4viymXRTp1DGeBg=='
);
});
});
describe('Unblinded Headers', () => {
it('should produce correct X-SOGS-Nonce', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: false,
body: null,
});
expect(headers['X-SOGS-Nonce']).to.be.equal('CdB5nyKVmQGCw6s0Bvv8Ww==');
});
it('should produce correct X-SOGS-Pubkey', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: false,
body: null,
});
expect(headers['X-SOGS-Pubkey']).to.be.equal(
'00bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc'
);
});
it('should produce correct X-SOGS-Timestamp', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: false,
body: null,
});
expect(headers['X-SOGS-Timestamp']).to.be.equal('1642472103');
});
it('should produce correct X-SOGS-Signature without body', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: false,
body: null,
});
expect(headers['X-SOGS-Signature']).to.be.equal(
'xxLpXHbomAJMB9AtGMyqvBsXrdd2040y+Ol/IKzElWfKJa3EYZRv1GLO6CTLhrDFUwVQe8PPltyGs54Kd7O5Cg=='
);
});
it('should produce correct X-SOGS-Signature with body', async () => {
const headers = await SogsBlinding.getOpenGroupHeaders({
signingKeys: signingKeysA,
serverPK: serverPubKey,
nonce,
method,
path,
timestamp: ts,
blinded: false,
body,
});
expect(headers['X-SOGS-Signature']).to.be.equal(
'uumZNee7NUb0lVufegjzgjjnj4pe3kAe6OYw7iVTJrcxxxdIxUCgt5/xliBWqPlgY6ReUZRAuptNa4nprv7nCA=='
);
});
});
});
describe('Blinded Message Encryption', () => {
it('Should encrypt blinded message correctly', async () => {
const dataUint = new Uint8Array(StringUtils.encode(body, 'utf8'));
const data = await SogsBlinding.encryptBlindedMessage({
rawData: dataUint,
senderSigningKey: signingKeysA,
serverPubKey,
recipientSigningKey: signingKeysB,
});
if (data) {
const decrypted = await decryptBlindedMessage(
data,
signingKeysA,
signingKeysB,
serverPubKey
);
expect(decrypted?.messageText).to.be.equal(body);
expect(decrypted?.senderED25519PubKey).to.be.equal(to_hex(signingKeysA.pubKeyBytes));
}
});
});
// tslint:disable-next-line: no-empty
describe('Message Decryption', () => {
it.skip('Message Decryption', () => {
// TODO: update input and expected output
});
});
describe('V4Requests', () => {
it.skip('Should bencode POST/PUT request with body successfully', () => {
// TODO: update input and expected output
// const bencoded = encodeV4Request(postDataToEncoded);
// expect(bencoded).to.be.equal(
// 'l100:{"method":"POST","endpoint":"/room/test-room/pin/123","headers":{"Content-Type":"application/json"}}2:{}e'
// );
});
it.skip('Should bencode GET request without body successfully', () => {
// TODO: change ot accept request info and expect uint8 array output
// const bencoded = encodeV4Request(getDataToEncode);
// expect(bencoded).to.be.equal('l45:{"method":"GET","endpoint":"/room/test-room"}e');
});
it.skip('Should decode bencoded response successfully', () => {
// TODO: update input and expected output
// const bencoded = decodeV4Response(responseToDecode);
// console.error({ bencoded });
});
});
});
/**
* This function is actually just used for testing and is useless IRL.
* We should probably move it somewhere else.
*
* The function you are looking for is `decryptWithSessionBlindingProtocol`
* @param data The data to be decrypted from the sender
* @param aSignKeyBytes the sender's keypair bytes
* @param bSignKeyBytes the receivers keypair bytes
* @param serverPubKey the server the message is sent to
*/
const decryptBlindedMessage = async (
data: Uint8Array,
aSignKeyBytes: ByteKeyPair,
bSignKeyBytes: ByteKeyPair,
serverPubKey: Uint8Array
): Promise<| {
messageText: string;
senderED25519PubKey: string;
senderSessionId: string;
}
| undefined> => {
const sodium = await getSodiumRenderer();
const aBlindingValues = SogsBlinding.getBlindingValues(serverPubKey, aSignKeyBytes, sodium);
const bBlindingValues = SogsBlinding.getBlindingValues(serverPubKey, bSignKeyBytes, sodium);
const { publicKey: kA } = aBlindingValues;
const { a: b, publicKey: kB } = bBlindingValues;
const k = sodium.crypto_core_ed25519_scalar_reduce(sodium.crypto_generichash(64, serverPubKey));
const decryptKey = sodium.crypto_generichash(
32,
concatUInt8Array(sodium.crypto_scalarmult_ed25519_noclamp(b, kA), kA, kB)
);
const version = data[0];
if (version !== 0) {
window?.log?.error(
'decryptBlindedMessage - Dropping message due to unsupported encryption version'
);
return;
}
const nonceLength = sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
const ciphertextIncoming = data.slice(1, data.length - nonceLength);
const nonceIncoming = data.slice(data.length - nonceLength);
const plaintextIncoming = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null,
ciphertextIncoming,
null,
nonceIncoming,
decryptKey
);
if (plaintextIncoming.length <= 32) {
// throw Error;
window?.log?.error('decryptBlindedMessage: plaintext insufficient length');
return;
}
const msg = plaintextIncoming.slice(0, plaintextIncoming.length - 32);
const senderEdpk = plaintextIncoming.slice(plaintextIncoming.length - 32);
if (to_hex(kA) !== to_hex(sodium.crypto_scalarmult_ed25519_noclamp(k, senderEdpk))) {
throw Error;
}
const messageText = StringUtils.decode(msg, 'utf8');
const senderSessionId = `${KeyPrefixType.standard}${to_hex(
sodium.crypto_sign_ed25519_pk_to_curve25519(senderEdpk)
)}`;
const senderED25519PubKey = to_hex(senderEdpk);
return {
messageText,
senderED25519PubKey,
senderSessionId,
};
};