You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
425 lines
15 KiB
TypeScript
425 lines
15 KiB
TypeScript
import { expect, use } from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import { UserGroupsGet } from 'libsession_util_nodejs';
|
|
import Sinon from 'sinon';
|
|
import { HexString } from '../../../../node/hexStrings';
|
|
import { getSodiumNode } from '../../../../node/sodiumNode';
|
|
import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces';
|
|
import { SnodeGroupSignature } from '../../../../session/apis/snode_api/signature/groupSignature';
|
|
import { SnodeSignature } from '../../../../session/apis/snode_api/signature/snodeSignatures';
|
|
import { WithSignature } from '../../../../session/apis/snode_api/types';
|
|
import { concatUInt8Array } from '../../../../session/crypto';
|
|
import { UserUtils } from '../../../../session/utils';
|
|
import { fromBase64ToArray, fromHexToArray } from '../../../../session/utils/String';
|
|
import { NetworkTime } from '../../../../util/NetworkTime';
|
|
|
|
use(chaiAsPromised);
|
|
|
|
const validGroupPk = '030442ca9b758eefe0c42370696688b28f48f44bf44941fae4f3d5b41f6358c41d';
|
|
const privKeyUint = concatUInt8Array(
|
|
fromHexToArray('4db38882cf0a0fffcbb971eb2b1420c92bc836c6946cd97bdc0c2787b806549d'),
|
|
fromHexToArray(validGroupPk.slice(2))
|
|
); // len 64
|
|
|
|
const userEd25519Keypair = {
|
|
pubKey: 'bdd5eaf00eaf965ca63b7e8b119d8122d4647ffd5bb58daa1f78dfc54dd53989',
|
|
privKey:
|
|
'b0e12943e22e8f71774c2c4205fed59800000000000000000000000000000000bdd5eaf00eaf965ca63b7e8b119d8122d4647ffd5bb58daa1f78dfc54dd53989',
|
|
};
|
|
|
|
// Keep the line below as we might need it for tests, and it is linked to the values above
|
|
// const _currentUserSubAccountAuthData = fromHexToArray(
|
|
// eslint-disable-next-line max-len
|
|
// '03030000cdbc07f46c4b322767675240d5945e902c75f0d3c46f36735b93773577d69e037c5d75d378a8e7183f9012b39bc27de7f81afe9c7000aa924fbcad8a7e6f12fec809adae65a1c427feb9c4b1ad453df403079f62203aa0563533b2b114f31b07'
|
|
// );
|
|
|
|
function getEmptyUserGroup() {
|
|
return {
|
|
secretKey: null,
|
|
authData: null,
|
|
invitePending: false,
|
|
joinedAtSeconds: 1234,
|
|
kicked: false,
|
|
name: '1243',
|
|
priority: 0,
|
|
pubkeyHex: validGroupPk,
|
|
} as UserGroupsGet;
|
|
}
|
|
|
|
const hardcodedTimestamp = 1234;
|
|
|
|
async function verifySig(ret: WithSignature & { pubkey: string }, verificationData: string) {
|
|
const without03 =
|
|
ret.pubkey.startsWith('03') || ret.pubkey.startsWith('05') ? ret.pubkey.slice(2) : ret.pubkey;
|
|
const pk = HexString.fromHexString(without03);
|
|
const sodium = await getSodiumNode();
|
|
const verified = sodium.crypto_sign_verify_detached(
|
|
fromBase64ToArray(ret.signature),
|
|
verificationData,
|
|
pk
|
|
);
|
|
|
|
if (!verified) {
|
|
throw new Error('sig failed to be verified');
|
|
}
|
|
}
|
|
|
|
describe('SnodeSignature', () => {
|
|
afterEach(() => {
|
|
Sinon.restore();
|
|
});
|
|
|
|
describe('getSnodeGroupAdminSignatureParams', () => {
|
|
beforeEach(() => {
|
|
Sinon.stub(NetworkTime, 'now').returns(hardcodedTimestamp);
|
|
});
|
|
|
|
describe('retrieve', () => {
|
|
it('retrieve namespace ClosedGroupInfo', async () => {
|
|
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
|
|
method: 'retrieve',
|
|
namespace: SnodeNamespaces.ClosedGroupInfo,
|
|
group: {
|
|
authData: null,
|
|
pubkeyHex: validGroupPk,
|
|
secretKey: privKeyUint,
|
|
},
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
|
|
const verificationData = `retrieve${SnodeNamespaces.ClosedGroupInfo}${hardcodedTimestamp}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it('retrieve namespace ClosedGroupKeys', async () => {
|
|
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
|
|
method: 'retrieve',
|
|
namespace: SnodeNamespaces.ClosedGroupKeys,
|
|
group: {
|
|
authData: null,
|
|
pubkeyHex: validGroupPk,
|
|
secretKey: privKeyUint,
|
|
},
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
|
|
const verificationData = `retrieve${SnodeNamespaces.ClosedGroupKeys}${hardcodedTimestamp}`;
|
|
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it('retrieve namespace ClosedGroupMessages', async () => {
|
|
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
|
|
method: 'retrieve',
|
|
namespace: SnodeNamespaces.ClosedGroupMessages,
|
|
group: {
|
|
authData: null,
|
|
pubkeyHex: validGroupPk,
|
|
secretKey: privKeyUint,
|
|
},
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
|
|
const verificationData = `retrieve${SnodeNamespaces.ClosedGroupMessages}${hardcodedTimestamp}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
});
|
|
|
|
describe('store', () => {
|
|
it('store namespace ClosedGroupInfo', async () => {
|
|
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
|
|
method: 'store',
|
|
namespace: SnodeNamespaces.ClosedGroupInfo,
|
|
group: {
|
|
authData: null,
|
|
pubkeyHex: validGroupPk,
|
|
secretKey: privKeyUint,
|
|
},
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
|
|
|
|
const verificationData = `store${SnodeNamespaces.ClosedGroupInfo}${hardcodedTimestamp}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it('store namespace ClosedGroupKeys', async () => {
|
|
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
|
|
method: 'store',
|
|
namespace: SnodeNamespaces.ClosedGroupKeys,
|
|
group: {
|
|
authData: null,
|
|
pubkeyHex: validGroupPk,
|
|
secretKey: privKeyUint,
|
|
},
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
|
|
const verificationData = `store${SnodeNamespaces.ClosedGroupKeys}${hardcodedTimestamp}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it('store namespace ClosedGroupMessages', async () => {
|
|
const ret = await SnodeGroupSignature.getSnodeGroupSignature({
|
|
method: 'store',
|
|
namespace: SnodeNamespaces.ClosedGroupMessages,
|
|
group: {
|
|
authData: null,
|
|
pubkeyHex: validGroupPk,
|
|
secretKey: privKeyUint,
|
|
},
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
expect(ret.timestamp).to.be.eq(hardcodedTimestamp);
|
|
const verificationData = `store${SnodeNamespaces.ClosedGroupMessages}${hardcodedTimestamp}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getGroupSignatureByHashesParams', () => {
|
|
beforeEach(() => {
|
|
Sinon.stub(NetworkTime, 'now').returns(hardcodedTimestamp);
|
|
});
|
|
|
|
describe('delete', () => {
|
|
it('can sign a delete with admin secretkey', async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const group = getEmptyUserGroup();
|
|
|
|
const ret = await SnodeGroupSignature.getGroupSignatureByHashesParams({
|
|
method: 'delete',
|
|
groupPk: validGroupPk,
|
|
messagesHashes: hashes,
|
|
group: { ...group, secretKey: privKeyUint },
|
|
});
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
expect(ret.messages).to.be.deep.eq(hashes);
|
|
|
|
const verificationData = `delete${hashes.join('')}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it.skip('can sign a delete with authData if adminSecretKey is empty', async () => {
|
|
// we can't really test this atm. We'd need the full env of wrapper setup as we need need for the subaccountSign itself, part of the wrapper
|
|
// const hashes = ['hash4321', 'hash4221'];
|
|
// const group = getEmptyUserGroup();
|
|
// const ret = await SnodeGroupSignature.getGroupSignatureByHashesParams({
|
|
// method: 'delete',
|
|
// groupPk: validGroupPk,
|
|
// messagesHashes: hashes,
|
|
// group: { ...group, authData: currentUserSubAccountAuthData },
|
|
// });
|
|
// expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
// expect(ret.messages).to.be.deep.eq(hashes);
|
|
// const verificationData = `delete${hashes.join('')}`;
|
|
// await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it('throws if none are set', async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
|
|
const group = getEmptyUserGroup();
|
|
const fn = async () =>
|
|
SnodeGroupSignature.getGroupSignatureByHashesParams({
|
|
method: 'delete',
|
|
groupPk: validGroupPk,
|
|
messagesHashes: hashes,
|
|
group,
|
|
});
|
|
expect(fn).to.throw;
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('generateUpdateExpiryGroupSignature', () => {
|
|
it('throws if groupPk not given', async () => {
|
|
const func = async () => {
|
|
return SnodeGroupSignature.generateUpdateExpiryGroupSignature({
|
|
group: { pubkeyHex: null as any, secretKey: privKeyUint, authData: null },
|
|
messagesHashes: ['[;p['],
|
|
shortenOrExtend: '',
|
|
expiryMs: hardcodedTimestamp,
|
|
});
|
|
};
|
|
await expect(func()).to.be.rejectedWith(
|
|
'generateUpdateExpiryGroupSignature groupPk is empty'
|
|
);
|
|
});
|
|
|
|
it('throws if groupPrivKey is empty', async () => {
|
|
const func = async () => {
|
|
return SnodeGroupSignature.generateUpdateExpiryGroupSignature({
|
|
group: {
|
|
pubkeyHex: validGroupPk as any,
|
|
secretKey: new Uint8Array() as any,
|
|
authData: null,
|
|
},
|
|
|
|
messagesHashes: ['[;p['],
|
|
shortenOrExtend: '',
|
|
expiryMs: hardcodedTimestamp,
|
|
});
|
|
};
|
|
await expect(func()).to.be.rejectedWith(
|
|
'retrieveRequestForGroup: needs either groupSecretKey or authData'
|
|
);
|
|
});
|
|
|
|
it('works with valid pubkey and priv key', async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const expiryMs = hardcodedTimestamp;
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
|
|
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
|
|
messagesHashes: hashes,
|
|
shortenOrExtend: '',
|
|
expiryMs,
|
|
});
|
|
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
const verificationData = `expire${shortenOrExtend}${expiryMs}${hashes.join('')}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
|
|
it('fails with invalid timestamp', async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const expiryMs = hardcodedTimestamp;
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
|
|
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
|
|
messagesHashes: hashes,
|
|
shortenOrExtend: '',
|
|
expiryMs,
|
|
});
|
|
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
const verificationData = `expire${shortenOrExtend}${expiryMs}1${hashes.join('')}`;
|
|
const func = async () => verifySig(ret, verificationData);
|
|
await expect(func()).rejectedWith('sig failed to be verified');
|
|
});
|
|
|
|
it('fails with invalid hashes', async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const expiryMs = hardcodedTimestamp;
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
|
|
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
|
|
messagesHashes: hashes,
|
|
shortenOrExtend: '',
|
|
expiryMs,
|
|
});
|
|
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
const overriddenHash = hashes.slice();
|
|
overriddenHash[0] = '1111';
|
|
const verificationData = `expire${shortenOrExtend}${expiryMs}${overriddenHash.join('')}`;
|
|
const func = async () => verifySig(ret, verificationData);
|
|
await expect(func()).rejectedWith('sig failed to be verified');
|
|
});
|
|
|
|
it('fails with invalid number of hashes', async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const expiryMs = hardcodedTimestamp;
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeGroupSignature.generateUpdateExpiryGroupSignature({
|
|
group: { pubkeyHex: validGroupPk, secretKey: privKeyUint, authData: null },
|
|
messagesHashes: hashes,
|
|
shortenOrExtend: '',
|
|
expiryMs,
|
|
});
|
|
|
|
expect(ret.pubkey).to.be.eq(validGroupPk);
|
|
|
|
const overriddenHash = [hashes[0]];
|
|
const verificationData = `expire${shortenOrExtend}${expiryMs}${overriddenHash.join('')}`;
|
|
const func = async () => verifySig(ret, verificationData);
|
|
await expect(func()).rejectedWith('sig failed to be verified');
|
|
});
|
|
});
|
|
|
|
describe('generateUpdateExpiryOurSignature', () => {
|
|
it('throws if our ed keypair is not set', async () => {
|
|
Sinon.stub(UserUtils, 'getUserED25519KeyPair').resolves(null as any);
|
|
|
|
const func = async () => {
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const shortenOrExtend = '';
|
|
return SnodeSignature.generateUpdateExpiryOurSignature({
|
|
messagesHashes: hashes,
|
|
shortenOrExtend,
|
|
timestamp: hardcodedTimestamp,
|
|
});
|
|
};
|
|
|
|
await expect(func()).to.be.rejectedWith(
|
|
'getSnodeSignatureParams "expiry": User has no getUserED25519KeyPair()'
|
|
);
|
|
});
|
|
|
|
it('throws if invalid hashes', async () => {
|
|
Sinon.stub(UserUtils, 'getUserED25519KeyPair').resolves(userEd25519Keypair);
|
|
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeSignature.generateUpdateExpiryOurSignature({
|
|
messagesHashes: hashes,
|
|
shortenOrExtend,
|
|
timestamp: hardcodedTimestamp,
|
|
});
|
|
const overriddenHash = [hashes[0]];
|
|
const verificationData = `expire${shortenOrExtend}${hardcodedTimestamp}${overriddenHash.join(
|
|
''
|
|
)}`;
|
|
|
|
const func = async () => {
|
|
return verifySig(ret, verificationData);
|
|
};
|
|
await expect(func()).to.be.rejectedWith('sig failed to be verified');
|
|
});
|
|
|
|
it('throws if invalid timestamp', async () => {
|
|
Sinon.stub(UserUtils, 'getUserED25519KeyPair').resolves(userEd25519Keypair);
|
|
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeSignature.generateUpdateExpiryOurSignature({
|
|
messagesHashes: hashes,
|
|
shortenOrExtend,
|
|
timestamp: hardcodedTimestamp,
|
|
});
|
|
const verificationData = `expire${shortenOrExtend}${hardcodedTimestamp}123${hashes.join('')}`;
|
|
|
|
const func = async () => {
|
|
return verifySig(ret, verificationData);
|
|
};
|
|
await expect(func()).to.be.rejectedWith('sig failed to be verified');
|
|
});
|
|
|
|
it('works with valid pubkey and priv key', async () => {
|
|
Sinon.stub(UserUtils, 'getUserED25519KeyPair').resolves(userEd25519Keypair);
|
|
|
|
const hashes = ['hash4321', 'hash4221'];
|
|
const timestamp = hardcodedTimestamp;
|
|
const shortenOrExtend = '';
|
|
const ret = await SnodeSignature.generateUpdateExpiryOurSignature({
|
|
messagesHashes: hashes,
|
|
shortenOrExtend: '',
|
|
timestamp,
|
|
});
|
|
|
|
expect(ret.pubkey).to.be.eq(userEd25519Keypair.pubKey);
|
|
|
|
const verificationData = `expire${shortenOrExtend}${timestamp}${hashes.join('')}`;
|
|
await verifySig(ret, verificationData);
|
|
});
|
|
});
|
|
});
|