feat: add sending and receiving of libsession encrypted msgs

still a wip though
pull/2873/head
Audric Ackermann 2 years ago
parent 90f4dd761c
commit 08164f2fd0

@ -21,7 +21,10 @@ import { from_hex } from 'libsodium-wrappers-sumo';
import { SignalService } from '../protobuf';
import { getMessageQueue } from '../session';
import { getConversationController } from '../session/conversations';
import { ClosedGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import {
ClosedGroupV3VisibleMessage,
ClosedGroupVisibleMessage,
} from '../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import { PubKey } from '../session/types';
import { ToastUtils, UserUtils } from '../session/utils';
import { BlockedNumberController } from '../util';
@ -221,7 +224,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public isClosedGroupV3(): boolean {
return Boolean(this.get('type') === ConversationTypeEnum.GROUPV3 && this.id.startsWith('03'));
return Boolean(
this.get('type') === ConversationTypeEnum.GROUPV3 && PubKey.isClosedGroupV2(this.id)
);
}
public isPrivate() {
@ -1701,6 +1706,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// we are trying to send a message to someone. Make sure this convo is not hidden
await this.unhideIfNeeded(true);
// TODO break down those functions (sendMessage and retrySend into smaller functions and narrow the VisibleMessageParams to preview, etc. with checks of types)
// an OpenGroupV2 message is just a visible message
const chatMessageParams: VisibleMessageParams = {
body,
@ -1761,8 +1767,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (this.isPrivate()) {
if (this.isMe()) {
chatMessageParams.syncTarget = this.id;
const chatMessageMe = new VisibleMessage(chatMessageParams);
const chatMessageMe = new VisibleMessage({ ...chatMessageParams, syncTarget: this.id });
await getMessageQueue().sendSyncMessage({
namespace: SnodeNamespaces.Default,
@ -1798,6 +1803,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return;
}
if (this.isClosedGroupV3()) {
// we need the return await so that errors are caught in the catch {}
await this.sendMessageToGroupV3(chatMessageParams);
return;
}
if (this.isClosedGroup()) {
const chatMessageMediumGroup = new VisibleMessage(chatMessageParams);
const closedGroupVisibleMessage = new ClosedGroupVisibleMessage({
@ -1819,6 +1830,20 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
}
private async sendMessageToGroupV3(chatMessageParams: VisibleMessageParams) {
const visibleMessage = new VisibleMessage(chatMessageParams);
const groupVisibleMessage = new ClosedGroupV3VisibleMessage({
chatMessage: visibleMessage,
destination: this.id,
namespace: SnodeNamespaces.ClosedGroupMessages,
});
// we need the return await so that errors are caught in the catch {}
await getMessageQueue().sendToGroupV3({
message: groupVisibleMessage,
});
}
private async sendBlindedMessageRequest(messageParams: VisibleMessageParams) {
const ourSignKeyBytes = await UserUtils.getUserED25519KeyPairBytes();
const groupUrl = this.getSogsOriginMessage();

@ -87,7 +87,7 @@ export async function handleClosedGroupControlMessage(
}`
);
if (PubKey.isClosedGroupV3(envelope.source)) {
if (PubKey.isClosedGroupV2(envelope.source)) {
window?.log?.warn(
'Message ignored; closed group v3 updates cannot come from SignalService.DataMessage.ClosedGroupControlMessage '
);
@ -168,7 +168,7 @@ function sanityCheckNewGroup(
return false;
}
if (PubKey.isClosedGroupV3(hexGroupPublicKey)) {
if (PubKey.isClosedGroupV2(hexGroupPublicKey)) {
window?.log?.warn('sanityCheckNewGroup: got a v3 new group as a ClosedGroupControlMessage. ');
return false;
}
@ -520,7 +520,7 @@ async function performIfValid(
const groupPublicKey = envelope.source;
const sender = envelope.senderIdentity;
if (PubKey.isClosedGroupV3(groupPublicKey)) {
if (PubKey.isClosedGroupV2(groupPublicKey)) {
window?.log?.warn(
'Message ignored; closed group v3 updates cannot come from SignalService.DataMessage.ClosedGroupControlMessage '
);

@ -33,7 +33,11 @@ import { Storage } from '../util/storage';
import { handleCallMessage } from './callMessage';
import { getAllCachedECKeyPair, sentAtMoreRecentThanWrapper } from './closedGroups';
import { ECKeyPair } from './keypairs';
import { ContactsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface';
import {
ContactsWrapperActions,
MetaGroupWrapperActions,
} from '../webworker/workers/browser/libsession_worker_interface';
import { PreConditionFailed } from '../session/utils/errors';
export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageHash: string) {
try {
@ -54,6 +58,18 @@ export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageH
}
}
async function decryptForGroupV2(envelope: EnvelopePlus) {
window?.log?.info('received closed group message v2');
// try {
const groupPk = envelope.source;
if (!PubKey.isClosedGroupV2(groupPk)) {
throw new PreConditionFailed('decryptForGroupV2: not a 03 prefixed group');
}
return await MetaGroupWrapperActions.decryptMessage(groupPk, envelope.content);
// } catch (e) {}
}
async function decryptForClosedGroup(envelope: EnvelopePlus) {
// case .closedGroupCiphertext: for ios
window?.log?.info('received closed group message');
@ -266,7 +282,11 @@ async function decrypt(envelope: EnvelopePlus): Promise<any> {
plaintext = await decryptEnvelopeWithOurKey(envelope);
break;
case SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE:
if (PubKey.isClosedGroupV2(envelope.source)) {
plaintext = await decryptForGroupV2(envelope);
} else {
plaintext = await decryptForClosedGroup(envelope);
}
break;
default:
assertUnreachable(envelope.type, `Unknown message type:${envelope.type}`);

@ -195,7 +195,7 @@ export async function handleSwarmDataMessage(
);
const isGroupMessage = !!envelope.senderIdentity;
const isGroupV3Message = isGroupMessage && PubKey.isClosedGroupV3(envelope.source);
const isGroupV3Message = isGroupMessage && PubKey.isClosedGroupV2(envelope.source);
let typeOfConvo = ConversationTypeEnum.PRIVATE;
if (isGroupV3Message) {
typeOfConvo = ConversationTypeEnum.GROUPV3;

@ -4,6 +4,12 @@ import { processOnionRequestErrorAtDestination, SnodeResponse } from './onions';
import { snodeRpc } from './sessionRpc';
import { NotEmptyArrayOfBatchResults, SnodeApiSubRequests } from './SnodeRequestTypes';
function logSubRequests(requests: Array<SnodeApiSubRequests>) {
return requests.map(m =>
m.method === 'retrieve' || m.method === 'store' ? `${m.method}-${m.params.namespace}` : m.method
);
}
/**
* This is the equivalent to the batch send on sogs. The target node runs each sub request and returns a list of all the sub status and bodies.
* If the global status code is not 200, an exception is thrown.
@ -23,7 +29,7 @@ export async function doSnodeBatchRequest(
): Promise<NotEmptyArrayOfBatchResults> {
console.warn(
`doSnodeBatchRequest "${method}":`,
JSON.stringify(subRequests.map(m => m.method))
JSON.stringify(logSubRequests(subRequests))
// subRequests
);
const result = await snodeRpc({

@ -93,7 +93,7 @@ async function retrieveRequestForGroup({
namespace: SnodeNamespaces;
retrieveParam: RetrieveParams;
}) {
if (!PubKey.isClosedGroupV3(groupPk)) {
if (!PubKey.isClosedGroupV2(groupPk)) {
throw new Error('retrieveRequestForGroup: not a 03 group');
}
if (!SnodeNamespace.isGroupNamespace(namespace)) {
@ -147,7 +147,7 @@ async function buildRetrieveRequest(
return retrieveRequestForLegacyGroup({ namespace, ourPubkey, pubkey, retrieveParam });
}
if (PubKey.isClosedGroupV3(pubkey)) {
if (PubKey.isClosedGroupV2(pubkey)) {
if (!SnodeNamespace.isGroupNamespace(namespace)) {
// either config or messages namespaces for 03 groups
throw new Error(`tried to poll from a non 03 group namespace ${namespace}`);

@ -77,7 +77,7 @@ function isSigParamsForGroupAdmin(
sigParams: SnodeSigParamsAdminGroup | SnodeSigParamsUs
): sigParams is SnodeSigParamsAdminGroup {
const asGr = sigParams as SnodeSigParamsAdminGroup;
return PubKey.isClosedGroupV3(asGr.groupPk) && !!asGr.privKey;
return PubKey.isClosedGroupV2(asGr.groupPk) && !!asGr.privKey;
}
async function getSnodeShared(params: SnodeSigParamsAdminGroup | SnodeSigParamsUs) {

@ -191,7 +191,7 @@ export class SwarmPolling {
window?.log?.debug(
`Polling for ${loggingId}; timeout: ${convoPollingTimeout}; diff: ${diff} `
);
if (PubKey.isClosedGroupV3(key)) {
if (PubKey.isClosedGroupV2(key)) {
return this.pollOnceForKey([key, ConversationTypeEnum.GROUPV3]);
}
return this.pollOnceForKey([key, ConversationTypeEnum.GROUP]);
@ -263,7 +263,7 @@ export class SwarmPolling {
} else if (
type === ConversationTypeEnum.GROUPV3 &&
confMessages?.length &&
PubKey.isClosedGroupV3(pubkey)
PubKey.isClosedGroupV2(pubkey)
) {
await SwarmPollingGroupConfig.handleGroupSharedConfigMessages(confMessages, pubkey);
}

@ -96,7 +96,7 @@ export class ConversationController {
throw new TypeError(`'type' must be 'private' or 'group' or 'groupv3' but got: '${type}'`);
}
if (type === ConversationTypeEnum.GROUPV3 && !PubKey.isClosedGroupV3(id)) {
if (type === ConversationTypeEnum.GROUPV3 && !PubKey.isClosedGroupV2(id)) {
throw new Error(
'required v3 closed group` ` but the pubkey does not match the 03 prefix for them'
);
@ -226,7 +226,7 @@ export class ConversationController {
// if we were kicked or sent our left message, we have nothing to do more with that group.
// Just delete everything related to it, not trying to add update message or send a left message.
await this.removeGroupOrCommunityFromDBAndRedux(groupId);
if (PubKey.isClosedGroupV3(groupId)) {
if (PubKey.isClosedGroupV2(groupId)) {
await remove03GroupFromWrappers(groupId);
} else {
await removeLegacyGroupFromWrappers(groupId);

@ -1,9 +1,13 @@
import { GroupPubkeyType } from 'libsession_util_nodejs';
import { MessageEncrypter, concatUInt8Array, getSodiumRenderer } from '.';
import { Data } from '../../data/data';
import { SignalService } from '../../protobuf';
import { assertUnreachable } from '../../types/sqlSharedTypes';
import { MetaGroupWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface';
import { PubKey } from '../types';
import { concatUInt8Array, getSodiumRenderer, MessageEncrypter } from '.';
import { fromHexToArray } from '../utils/String';
import { Data } from '../../data/data';
import { UserUtils } from '../utils';
import { fromHexToArray } from '../utils/String';
import { SigningFailed } from '../utils/errors';
import { addMessagePadding } from './BufferPadding';
export { concatUInt8Array, getSodiumRenderer };
@ -13,57 +17,106 @@ type EncryptResult = {
cipherText: Uint8Array;
};
async function encryptWithLibSession(destination: GroupPubkeyType, plainText: Uint8Array) {
try {
return MetaGroupWrapperActions.encryptMessage(destination, plainText, true);
} catch (e) {
window.log.warn('encrypt message for group failed with', e.message);
throw new SigningFailed(e.message);
}
}
async function encryptForLegacyGroup(destination: PubKey, plainText: Uint8Array) {
const hexEncryptionKeyPair = await Data.getLatestClosedGroupEncryptionKeyPair(destination.key);
if (!hexEncryptionKeyPair) {
window?.log?.warn("Couldn't get key pair for closed group during encryption");
throw new Error("Couldn't get key pair for closed group");
}
const destinationX25519Pk = PubKey.cast(hexEncryptionKeyPair.publicHex);
const cipherTextClosedGroup = await MessageEncrypter.encryptUsingSessionProtocol(
destinationX25519Pk,
plainText
);
return {
envelopeType: SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE,
cipherText: cipherTextClosedGroup,
};
}
/**
* Encrypt `plainTextBuffer` with given `encryptionType` for `device`.
* Encrypt `plainTextBuffer` with given `encryptionType` for `destination`.
*
* @param device The device `PubKey` to encrypt for.
* @param destination The device `PubKey` to encrypt for.
* @param plainTextBuffer The unpadded plaintext buffer. It will be padded
* @param encryptionType The type of encryption.
* @returns The envelope type and the base64 encoded cipher text
*/
export async function encrypt(
device: PubKey,
destination: PubKey,
plainTextBuffer: Uint8Array,
encryptionType: SignalService.Envelope.Type
): Promise<EncryptResult> {
const { CLOSED_GROUP_MESSAGE, SESSION_MESSAGE } = SignalService.Envelope.Type;
const plainTextPadded = addMessagePadding(plainTextBuffer);
switch (encryptionType) {
case SESSION_MESSAGE: {
// if (destination.isPrivate || destination.isUS) {
const cipherText = await MessageEncrypter.encryptUsingSessionProtocol(
PubKey.cast(destination.key),
plainTextPadded
);
return { envelopeType: SESSION_MESSAGE, cipherText };
// }
if (encryptionType !== CLOSED_GROUP_MESSAGE && encryptionType !== SESSION_MESSAGE) {
throw new Error(`Invalid encryption type:${encryptionType}`);
}
const encryptForClosedGroup = encryptionType === CLOSED_GROUP_MESSAGE;
const plainText = addMessagePadding(plainTextBuffer);
if (encryptForClosedGroup) {
// window?.log?.info(
// 'Encrypting message with SessionProtocol and envelope type is CLOSED_GROUP_MESSAGE'
// if (destination.isGroupV2 || destination.isLegacyGroup) {
// throw new PreConditionFailed(
// 'Encryption with SESSION_MESSAGE only work with destination private or us'
// );
// }
// assertUnreachable(
// destination,
// 'Encryption with SESSION_MESSAGE only work with destination private or us'
// );
const hexEncryptionKeyPair = await Data.getLatestClosedGroupEncryptionKeyPair(device.key);
if (!hexEncryptionKeyPair) {
window?.log?.warn("Couldn't get key pair for closed group during encryption");
throw new Error("Couldn't get key pair for closed group");
}
const hexPubFromECKeyPair = PubKey.cast(hexEncryptionKeyPair.publicHex);
const cipherTextClosedGroup = await MessageEncrypter.encryptUsingSessionProtocol(
hexPubFromECKeyPair,
plainText
);
case CLOSED_GROUP_MESSAGE: {
const groupPk = destination.key;
if (PubKey.isClosedGroupV2(groupPk)) {
return {
envelopeType: CLOSED_GROUP_MESSAGE,
cipherText: cipherTextClosedGroup,
cipherText: await encryptWithLibSession(groupPk, plainTextBuffer),
};
}
const cipherText = await MessageEncrypter.encryptUsingSessionProtocol(device, plainText);
return { envelopeType: SESSION_MESSAGE, cipherText };
// if (destination.isLegacyGroup) {
return encryptForLegacyGroup(destination, plainTextPadded); // not padding it again, it is already done by libsession
// }
// if (
// destination.isBlinded ||
// destination.isBlinded ||
// destination.isPrivate ||
// destination.isUS
// ) {
// throw new PreConditionFailed(
// 'Encryption with CLOSED_GROUP_MESSAGE only work with destination groupv2 or legacy group'
// );
// }
// assertUnreachable(
// destination,
// 'Encryption with CLOSED_GROUP_MESSAGE only work with destination groupv2 or legacy group'
// );
}
default:
assertUnreachable(encryptionType, '');
}
}
export async function encryptUsingSessionProtocol(
recipientHexEncodedX25519PublicKey: PubKey,
destinationX25519Pk: PubKey,
plaintext: Uint8Array
): Promise<Uint8Array> {
const userED25519KeyPairHex = await UserUtils.getUserED25519KeyPair();
@ -76,9 +129,9 @@ export async function encryptUsingSessionProtocol(
}
const sodium = await getSodiumRenderer();
// window?.log?.info('encryptUsingSessionProtocol for ', recipientHexEncodedX25519PublicKey.key);
const recipientX25519PublicKey = recipientHexEncodedX25519PublicKey.withoutPrefixToArray();
const recipientX25519PublicKey = fromHexToArray(
PubKey.removePrefixIfNeeded(destinationX25519Pk.key)
);
const userED25519PubKeyBytes = fromHexToArray(userED25519KeyPairHex.pubKey);
const userED25519SecretKeyBytes = fromHexToArray(userED25519KeyPairHex.privKey);

@ -60,7 +60,7 @@ export async function initiateClosedGroupUpdate(
groupName: string,
members: Array<string>
) {
const isGroupV3 = PubKey.isClosedGroupV3(groupId);
const isGroupV3 = PubKey.isClosedGroupV2(groupId);
const convo = await getConversationController().getOrCreateAndWait(
groupId,
isGroupV3 ? ConversationTypeEnum.GROUPV3 : ConversationTypeEnum.GROUP
@ -206,7 +206,7 @@ function buildGroupDiff(convo: ConversationModel, update: GroupInfo): GroupDiff
export async function updateOrCreateClosedGroup(details: GroupInfo) {
const { id, expireTimer } = details;
const isV3 = PubKey.isClosedGroupV3(id);
const isV3 = PubKey.isClosedGroupV2(id);
const conversation = await getConversationController().getOrCreateAndWait(
id,

@ -3,6 +3,9 @@ import { PubKey } from '../../../types';
import { StringUtils } from '../../../utils';
import { VisibleMessage } from './VisibleMessage';
import { ClosedGroupMessage } from '../controlMessage/group/ClosedGroupMessage';
import { DataMessage } from '../DataMessage';
import { GroupPubkeyType } from 'libsession_util_nodejs';
import { SnodeNamespaces } from '../../../apis/snode_api/namespaces';
interface ClosedGroupVisibleMessageParams {
identifier?: string;
@ -24,7 +27,7 @@ export class ClosedGroupVisibleMessage extends ClosedGroupMessage {
throw new Error('ClosedGroupVisibleMessage: groupId must be set');
}
if (PubKey.isClosedGroupV3(PubKey.cast(params.groupId).key)) {
if (PubKey.isClosedGroupV2(PubKey.cast(params.groupId).key)) {
throw new Error('GroupContext should not be used anymore with closed group v3');
}
}
@ -45,3 +48,36 @@ export class ClosedGroupVisibleMessage extends ClosedGroupMessage {
return dataProto;
}
}
type WithDestinationGroupPk = { destination: GroupPubkeyType };
type WithGroupMessageNamespace = { namespace: SnodeNamespaces.ClosedGroupMessages };
export class ClosedGroupV3VisibleMessage extends DataMessage {
private readonly chatMessage: VisibleMessage;
public readonly destination: GroupPubkeyType;
public readonly namespace: SnodeNamespaces.ClosedGroupMessages;
constructor(
params: Pick<ClosedGroupVisibleMessageParams, 'chatMessage' | 'identifier'> &
WithDestinationGroupPk &
WithGroupMessageNamespace
) {
super({
timestamp: params.chatMessage.timestamp,
identifier: params.identifier ?? params.chatMessage.identifier,
});
this.chatMessage = params.chatMessage;
if (!PubKey.isClosedGroupV2(params.destination)) {
throw new Error('ClosedGroupV3VisibleMessage only work with 03-groups destination');
}
this.destination = params.destination;
this.namespace = params.namespace;
}
public dataProto(): SignalService.DataMessage {
// expireTimer is set in the dataProto in this call directly
const dataProto = this.chatMessage.dataProto();
return dataProto;
}
}

@ -1,28 +1,28 @@
import { AbortController } from 'abort-controller';
import { PendingMessageCache } from './PendingMessageCache';
import { JobQueue, MessageUtils, UserUtils } from '../utils';
import { PubKey, RawMessage } from '../types';
import { MessageSender } from '.';
import { ClosedGroupMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMessage';
import { ConfigurationMessage } from '../messages/outgoing/controlMessage/ConfigurationMessage';
import { ClosedGroupMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMessage';
import { ClosedGroupNameChangeMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNameChangeMessage';
import { PubKey, RawMessage } from '../types';
import { JobQueue, MessageUtils, UserUtils } from '../utils';
import { PendingMessageCache } from './PendingMessageCache';
import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage';
import { MessageSentHandler } from './MessageSentHandler';
import { ContentMessage } from '../messages/outgoing';
import { ExpirationTimerUpdateMessage } from '../messages/outgoing/controlMessage/ExpirationTimerUpdateMessage';
import { ClosedGroupAddedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupAddedMembersMessage';
import { ClosedGroupEncryptionPairMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairMessage';
import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage';
import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage';
import { ClosedGroupRemovedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage';
import { ClosedGroupVisibleMessage } from '../messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import {
ClosedGroupV3VisibleMessage,
ClosedGroupVisibleMessage,
} from '../messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import { SyncMessageType } from '../utils/sync/syncUtils';
import { MessageSentHandler } from './MessageSentHandler';
import { OpenGroupRequestCommonType } from '../apis/open_group_api/opengroupV2/ApiUtil';
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
import { CallMessage } from '../messages/outgoing/controlMessage/CallMessage';
import { OpenGroupMessageV2 } from '../apis/open_group_api/opengroupV2/OpenGroupMessageV2';
import { sendSogsReactionOnionV4 } from '../apis/open_group_api/sogsv3/sogsV3SendReaction';
import {
@ -30,7 +30,10 @@ import {
SnodeNamespacesLegacyGroup,
SnodeNamespacesUser,
} from '../apis/snode_api/namespaces';
import { CallMessage } from '../messages/outgoing/controlMessage/CallMessage';
import { SharedConfigMessage } from '../messages/outgoing/controlMessage/SharedConfigMessage';
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
type ClosedGroupMessageType =
| ClosedGroupVisibleMessage
@ -196,6 +199,26 @@ export class MessageQueue {
return this.sendToPubKey(PubKey.cast(destinationPubKey), message, namespace, sentCb, true);
}
public async sendToGroupV3({
message,
sentCb,
}: {
message: ClosedGroupV3VisibleMessage;
sentCb?: (message: RawMessage) => Promise<void>;
}): Promise<void> {
if (!message.destination) {
throw new Error('Invalid group message passed in sendToGroupV3.');
}
return this.sendToPubKey(
PubKey.cast(message.destination),
message,
message.namespace,
sentCb,
true
);
}
public async sendSyncMessage({
namespace,
message,

@ -7,6 +7,7 @@ import _, { isEmpty, isNil, isString, sample, toNumber } from 'lodash';
import pRetry from 'p-retry';
import { Data } from '../../data/data';
import { SignalService } from '../../protobuf';
import { UserGroupsWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface';
import { OpenGroupRequestCommonType } from '../apis/open_group_api/opengroupV2/ApiUtil';
import { OpenGroupMessageV2 } from '../apis/open_group_api/opengroupV2/OpenGroupMessageV2';
import {
@ -40,7 +41,6 @@ import { RawMessage } from '../types/RawMessage';
import { UserUtils } from '../utils';
import { fromUInt8ArrayToBase64 } from '../utils/String';
import { EmptySwarmError } from '../utils/errors';
import { UserGroupsWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface';
// ================ SNODE STORE ================
@ -196,12 +196,15 @@ async function getSignatureParamsFromNamespace(item: StoreOnNodeParamsNoSig, des
});
}
if (SnodeNamespace.isGroupConfigNamespace(item.namespace)) {
if (!PubKey.isClosedGroupV3(destination)) {
if (
SnodeNamespace.isGroupConfigNamespace(item.namespace) ||
item.namespace === SnodeNamespaces.ClosedGroupMessages
) {
if (!PubKey.isClosedGroupV2(destination)) {
throw new Error('sendMessagesDataToSnode: groupconfig namespace required a 03 pubkey');
}
const group = await UserGroupsWrapperActions.getGroup(destination);
const groupSecretKey = group?.secretKey;
const groupSecretKey = group?.secretKey; // TODO we will need to the user auth at some point
if (isNil(groupSecretKey) || isEmpty(groupSecretKey)) {
throw new Error(`sendMessagesDataToSnode: failed to find group admin secret key in wrapper`);
}
@ -303,7 +306,7 @@ type SharedEncryptAndWrap = {
type EncryptAndWrapMessage = {
plainTextBuffer: Uint8Array;
destination: string;
namespace: number | null;
namespace: number;
} & SharedEncryptAndWrap;
type EncryptAndWrapMessageResults = {
@ -342,23 +345,11 @@ async function encryptMessageAndWrap(
const data = wrapEnvelope(envelope);
const data64 = ByteBuffer.wrap(data).toString('base64');
// override the namespaces if those are unset in the incoming messages
// right when we upgrade from not having namespaces stored in the outgoing cached messages our messages won't have a namespace associated.
// So we need to keep doing the lookup of where they should go if the namespace is not set.
const overridenNamespace = !isNil(namespace)
? namespace
: getConversationController()
.get(recipient.key)
?.isClosedGroup()
? SnodeNamespaces.LegacyClosedGroup
: SnodeNamespaces.Default;
return {
data64,
networkTimestamp,
data,
namespace: overridenNamespace,
namespace,
ttl,
identifier,
isSyncMessage: syncMessage,

@ -26,6 +26,60 @@ export enum KeyPrefixType {
groupV3 = '03',
}
// export type GroupV2PubKey = {
// key: GroupPubkeyType; // 03 prefix for groups v2
// isGroupV2: true;
// isLegacyGroup: false;
// isPrivate: false;
// isUS: false;
// isBlinded: false;
// };
// export type PrivatePubkey = {
// key: PubkeyType; // 05 prefix for private conversations
// isGroupV2: false;
// isLegacyGroup: false;
// isPrivate: true;
// isUS: false;
// isBlinded: false;
// };
// export type UsPubkey = {
// key: PubkeyType; // 05 prefix for note to self
// isGroupV2: false;
// isLegacyGroup: false;
// isPrivate: false;
// isUS: true;
// isBlinded: false;
// };
// export type PrivateBlindedPubkey = {
// key: BlindedPubkeyType; // 15 prefix for blinded pubkeys
// isGroupV2: false;
// isLegacyGroup: false;
// isPrivate: true;
// isUS: false;
// isBlinded: true;
// };
// export type LegacyGroupPubkey = {
// key: PubkeyType; // 05 prefix for legacy closed group
// isGroupV2: false;
// isLegacyGroup: true;
// isPrivate: false;
// isUS: false;
// isBlinded: false;
// };
// export type PubKeyRecord =
// | UsPubkey
// | PrivatePubkey
// | GroupV2PubKey
// | LegacyGroupPubkey
// | PrivateBlindedPubkey;
// TODO make that Pubkey class more useful, add fields for what types of pubkey it is (group, legacy group, private)
export class PubKey {
public static readonly PUBKEY_LEN = 66;
public static readonly PUBKEY_LEN_NO_PREFIX = PubKey.PUBKEY_LEN - 2;
@ -233,31 +287,12 @@ export class PubKey {
return fromHexToArray(this.key);
}
public withoutPrefixToArray(): Uint8Array {
return fromHexToArray(PubKey.removePrefixIfNeeded(this.key));
}
public static isBlinded(key: string) {
return key.startsWith(KeyPrefixType.blinded15) || key.startsWith(KeyPrefixType.blinded25);
}
public static isClosedGroupV3(key: string): key is GroupPubkeyType {
public static isClosedGroupV2(key: string): key is GroupPubkeyType {
const regex = new RegExp(`^${KeyPrefixType.groupV3}${PubKey.HEX}{64}$`);
return regex.test(key);
}
public static isHexOnly(str: string) {
return new RegExp(`^${PubKey.HEX}*$`).test(str);
}
/**
*
* @returns true if that string is a valid group (as in closed group) pubkey.
* i.e. returns true if length is 66, prefix is 05 only, and it's hex characters only
*/
public static isValidGroupPubkey(pubkey: string): boolean {
return (
pubkey.length === 66 && pubkey.startsWith(KeyPrefixType.standard) && this.isHexOnly(pubkey)
);
}
}

@ -7,7 +7,7 @@ export type RawMessage = {
device: string;
ttl: number;
encryption: SignalService.Envelope.Type;
namespace: SnodeNamespaces | null; // allowing null as when we upgrade, we might have messages awaiting sending which won't have a namespace
namespace: SnodeNamespaces;
};
// For building RawMessages from JSON

@ -66,3 +66,17 @@ export class HTTPError extends Error {
}
}
}
class BaseError extends Error {
public readonly context?: Object;
constructor(message: string, context?: Object) {
super(message);
this.name = this.constructor.name;
this.context = context;
}
}
export class SigningFailed extends BaseError {}
export class InvalidSigningType extends BaseError {}
export class GroupV2SigningFailed extends SigningFailed {}
export class PreConditionFailed extends BaseError {}

@ -2,7 +2,7 @@
import { GroupPubkeyType } from 'libsession_util_nodejs';
import { isArray, isEmpty, isNumber, isString } from 'lodash';
import { UserUtils } from '../..';
import { ConfigDumpData } from '../../../../data/configDump/configDump';
import { stringify } from '../../../../types/sqlSharedTypes';
import { ReleasedFeatures } from '../../../../util/releaseFeature';
import { isSignInByLinking } from '../../../../util/storage';
import { MetaGroupWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface';
@ -29,7 +29,6 @@ import {
PersistedJob,
RunJobResult,
} from '../PersistedJob';
import { stringify } from '../../../../types/sqlSharedTypes';
const defaultMsBetweenRetries = 15000; // a long time between retries, to avoid running multiple jobs at the same time, when one was postponed at the same time as one already planned (5s)
const defaultMaxAttempts = 2;
@ -118,30 +117,7 @@ async function buildAndSaveDumpsToDB(
}
await MetaGroupWrapperActions.metaConfirmPushed(...toConfirm);
const metaNeedsDump = await MetaGroupWrapperActions.needsDump(groupPk);
// save the concatenated dumps as a single entry in the DB if any of the dumps had a need for dump
if (metaNeedsDump) {
const dump = await MetaGroupWrapperActions.metaDump(groupPk);
await ConfigDumpData.saveConfigDump({
data: dump,
publicKey: groupPk,
variant: `MetaGroupConfig-${groupPk}`,
});
}
}
async function saveDumpsNeededToDB(groupPk: GroupPubkeyType) {
const needsDump = await MetaGroupWrapperActions.needsDump(groupPk);
if (!needsDump) {
return;
}
const dump = await MetaGroupWrapperActions.metaDump(groupPk);
await ConfigDumpData.saveConfigDump({
data: dump,
publicKey: groupPk,
variant: `MetaGroupConfig-${groupPk}`,
});
return LibSessionUtil.saveMetaGroupDumpToDb(groupPk);
}
class GroupSyncJob extends PersistedJob<GroupSyncPersistedData> {
@ -181,12 +157,12 @@ class GroupSyncJob extends PersistedJob<GroupSyncPersistedData> {
return RunJobResult.PermanentFailure;
}
if (!PubKey.isClosedGroupV3(thisJobDestination)) {
if (!PubKey.isClosedGroupV2(thisJobDestination)) {
return RunJobResult.PermanentFailure;
}
// save the dumps to DB even before trying to push them, so at least we have an up to date dumps in the DB in case of crash, no network etc
await saveDumpsNeededToDB(thisJobDestination);
await LibSessionUtil.saveMetaGroupDumpToDb(thisJobDestination);
const newGroupsReleased = await ReleasedFeatures.checkIsNewGroupsReleased();
// if the feature flag is not enabled, we want to keep updating the dumps, but just not sync them.

@ -227,7 +227,7 @@ async function pendingChangesForGroup(
groupPk: GroupPubkeyType
): Promise<GroupSingleDestinationChanges> {
const results = new Array<PendingChangesForGroup>();
if (!PubKey.isClosedGroupV3(groupPk)) {
if (!PubKey.isClosedGroupV2(groupPk)) {
throw new Error(`pendingChangesForGroup only works for user or 03 group pubkeys`);
}
// one of the wrapper behind the metagroup needs a push
@ -319,6 +319,22 @@ async function markAsPushed(variant: ConfigWrapperUser, seqno: number, hash: str
return GenericWrapperActions.needsDump(variant);
}
/**
* If a dump is needed for that metagroup wrapper, dump it to the Database
*/
async function saveMetaGroupDumpToDb(groupPk: GroupPubkeyType) {
const metaNeedsDump = await MetaGroupWrapperActions.needsDump(groupPk);
// save the concatenated dumps as a single entry in the DB if any of the dumps had a need for dump
if (metaNeedsDump) {
const dump = await MetaGroupWrapperActions.metaDump(groupPk);
await ConfigDumpData.saveConfigDump({
data: dump,
publicKey: groupPk,
variant: `MetaGroupConfig-${groupPk}`,
});
}
}
export const LibSessionUtil = {
initializeLibSessionUtilWrappers,
userVariantToUserKind,
@ -327,4 +343,5 @@ export const LibSessionUtil = {
pendingChangesForGroup,
userKindToVariant,
markAsPushed,
saveMetaGroupDumpToDb,
};

@ -35,7 +35,7 @@ function isLegacyGroupToStoreInWrapper(convo: ConversationModel): boolean {
}
function isGroupToStoreInWrapper(convo: ConversationModel): boolean {
return convo.isGroup() && PubKey.isClosedGroupV3(convo.id) && convo.isActive(); // TODO should we filter by left/kicked or they are on the wrapper itself?
return convo.isGroup() && PubKey.isClosedGroupV2(convo.id) && convo.isActive(); // TODO should we filter by left/kicked or they are on the wrapper itself?
}
/**
@ -78,7 +78,7 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise
const convoType: UserGroupsType = isCommunityToStoreInWrapper(foundConvo)
? 'Community'
: PubKey.isClosedGroupV3(convoId)
: PubKey.isClosedGroupV2(convoId)
? 'Group'
: 'LegacyGroup';

Loading…
Cancel
Save