make sure profileKey is a hex string in all convos

This commit is contained in:
audric 2021-07-27 16:41:15 +10:00
parent 9f62d6577c
commit fce86989f0
9 changed files with 69 additions and 138 deletions

View file

@ -55,60 +55,6 @@ function buildAvatarUpdater({ field }) {
}
const maybeUpdateAvatar = buildAvatarUpdater({ field: 'avatar' });
const maybeUpdateProfileAvatar = buildAvatarUpdater({
field: 'profileAvatar',
});
async function upgradeToVersion2(conversation, options) {
if (conversation.version >= 2) {
return conversation;
}
const { writeNewAttachmentData } = options;
if (!isFunction(writeNewAttachmentData)) {
throw new Error('Conversation.upgradeToVersion2: writeNewAttachmentData must be a function');
}
let { avatar, profileAvatar, profileKey } = conversation;
if (avatar && avatar.data) {
avatar = {
hash: await computeHash(avatar.data),
path: await writeNewAttachmentData(avatar.data),
};
}
if (profileAvatar && profileAvatar.data) {
profileAvatar = {
hash: await computeHash(profileAvatar.data),
path: await writeNewAttachmentData(profileAvatar.data),
};
}
if (profileKey && profileKey.byteLength) {
profileKey = arrayBufferToBase64(profileKey);
}
return {
...conversation,
version: 2,
avatar,
profileAvatar,
profileKey,
};
}
async function migrateConversation(conversation, options = {}) {
if (!conversation) {
return conversation;
}
if (!isNumber(conversation.version)) {
// eslint-disable-next-line no-param-reassign
conversation.version = 1;
}
return upgradeToVersion2(conversation, options);
}
async function deleteExternalFiles(conversation, options = {}) {
if (!conversation) {
@ -133,9 +79,7 @@ async function deleteExternalFiles(conversation, options = {}) {
module.exports = {
deleteExternalFiles,
migrateConversation,
maybeUpdateAvatar,
maybeUpdateProfileAvatar,
createLastMessageUpdate,
arrayBufferToBase64,
};

View file

@ -41,7 +41,7 @@ import {
import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
import { IMAGE_JPEG } from '../types/MIME';
import { FSv2 } from '../fileserver';
import { fromBase64ToArray, toHex } from '../session/utils/String';
import { fromBase64ToArray, fromHexToArray, toHex } from '../session/utils/String';
import { SessionButtonColor } from '../components/session/SessionButton';
import { perfEnd, perfStart } from '../session/utils/Performance';
import { ReplyingToMessageProps } from '../components/session/conversation/SessionCompositionBox';
@ -350,17 +350,21 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
return;
}
let profileKey;
let profileKey: Uint8Array | null;
let decryptedAvatarData;
if (newAvatarDecrypted) {
// Encrypt with a new key every time
profileKey = window.libsignal.crypto.getRandomBytes(32);
profileKey = window.libsignal.crypto.getRandomBytes(32) as Uint8Array;
decryptedAvatarData = newAvatarDecrypted;
} else {
// this is a reupload. no need to generate a new profileKey
profileKey = window.textsecure.storage.get('profileKey');
const ourConvoProfileKey =
getConversationController()
.get(UserUtils.getOurPubKeyStrFromCache())
?.get('profileKey') || null;
profileKey = ourConvoProfileKey ? fromHexToArray(ourConvoProfileKey) : null;
if (!profileKey) {
window.log.info('our profileKey not found');
window.log.info('our profileKey not found. Not reuploading our avatar');
return;
}
const currentAttachmentPath = ourConvo.getAvatarPath();
@ -412,7 +416,6 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
const displayName = ourConvo.get('profileName');
// write the profileKey even if it did not change
window.storage.put('profileKey', profileKey);
ourConvo.set({ profileKey: toHex(profileKey) });
// Replace our temporary image with the attachment pointer from the server:
// this commits already

View file

@ -20,7 +20,7 @@ import {
saveMessages,
updateConversation,
} from '../../ts/data/data';
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer, toHex } from '../session/utils/String';
import {
actions as conversationActions,
conversationChanged,
@ -91,6 +91,9 @@ export interface ConversationAttributes {
nickname?: string;
profile?: any;
profileAvatar?: any;
/**
* Consider this being a hex string if it set
*/
profileKey?: string;
triggerNotificationsFor: ConversationNotificationSettingType;
isTrustedForAttachmentDownload: boolean;
@ -128,6 +131,9 @@ export interface ConversationAttributesOptionals {
nickname?: string;
profile?: any;
profileAvatar?: any;
/**
* Consider this being a hex string if it set
*/
profileKey?: string;
triggerNotificationsFor?: ConversationNotificationSettingType;
isTrustedForAttachmentDownload?: boolean;
@ -1194,15 +1200,28 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
await this.commit();
}
}
public async setProfileKey(profileKey: string) {
/**
* profileKey MUST be a hex string
* @param profileKey MUST be a hex string
*/
public async setProfileKey(profileKey?: Uint8Array, autoCommit = true) {
const re = /[0-9A-Fa-f]*/g;
if (!profileKey) {
return;
}
const profileKeyHex = toHex(profileKey);
// profileKey is a string so we can compare it directly
if (this.get('profileKey') !== profileKey) {
if (this.get('profileKey') !== profileKeyHex) {
this.set({
profileKey,
profileKey: profileKeyHex,
});
await this.commit();
if (autoCommit) {
await this.commit();
}
}
}

View file

@ -34,11 +34,6 @@ async function handleOurProfileUpdate(
return;
}
if (profileKey?.length) {
window?.log?.info('Saving our profileKey from configuration message');
// TODO not sure why we keep our profileKey in storage AND in our conversaio
window.textsecure.storage.put('profileKey', profileKey);
}
const lokiProfile = {
displayName,
profilePicture,

View file

@ -20,11 +20,12 @@ import {
} from '../../ts/data/data';
import { ConversationModel, ConversationTypeEnum } from '../models/conversation';
import { allowOnlyOneAtATime } from '../session/utils/Promise';
import { toHex } from '../session/utils/String';
export async function updateProfileOneAtATime(
conversation: ConversationModel,
profile: SignalService.DataMessage.ILokiProfile,
profileKey: any
profileKey?: Uint8Array | null // was any
) {
if (!conversation?.id) {
window?.log?.warn('Cannot update profile with empty convoid');
@ -39,13 +40,18 @@ export async function updateProfileOneAtATime(
async function updateProfile(
conversation: ConversationModel,
profile: SignalService.DataMessage.ILokiProfile,
profileKey: any
profileKey?: Uint8Array | null // was any
) {
const { dcodeIO, textsecure, Signal } = window;
// Retain old values unless changed:
const newProfile = conversation.get('profile') || {};
if (!profileKey) {
window.log.warn("No need to try to update profile. We don't have a profile key");
return;
}
newProfile.displayName = profile.displayName;
if (profile.profilePicture) {
@ -79,7 +85,7 @@ async function updateProfile(
});
// Only update the convo if the download and decrypt is a success
conversation.set('avatarPointer', profile.profilePicture);
conversation.set('profileKey', profileKey);
conversation.set('profileKey', toHex(profileKey));
({ path } = upgraded);
} catch (e) {
window?.log?.error(`Could not decrypt profile image: ${e}`);
@ -422,18 +428,15 @@ export const isDuplicate = (
async function handleProfileUpdate(
profileKeyBuffer: Uint8Array,
convoId: string,
convoType: ConversationTypeEnum,
isIncoming: boolean
) {
const profileKey = StringUtils.decode(profileKeyBuffer, 'base64');
if (!isIncoming) {
// We update our own profileKey if it's different from what we have
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const me = getConversationController().getOrCreate(ourNumber, ConversationTypeEnum.PRIVATE);
// Will do the save for us if needed
await me.setProfileKey(profileKey);
await me.setProfileKey(profileKeyBuffer);
} else {
const sender = await getConversationController().getOrCreateAndWait(
convoId,
@ -441,7 +444,7 @@ async function handleProfileUpdate(
);
// Will do the save for us
await sender.setProfileKey(profileKey);
await sender.setProfileKey(profileKeyBuffer);
}
}
@ -582,7 +585,7 @@ export async function handleMessageEvent(event: MessageEvent): Promise<void> {
return;
}
if (message.profileKey?.length) {
await handleProfileUpdate(message.profileKey, conversationId, type, isIncoming);
await handleProfileUpdate(message.profileKey, conversationId, isIncoming);
}
const msg = createMessage(data, isIncoming);

View file

@ -204,16 +204,14 @@ function handleLinkPreviews(messageBody: string, messagePreview: any, message: M
}
async function processProfileKey(
source: string,
conversation: ConversationModel,
sendingDeviceConversation: ConversationModel,
profileKeyBuffer: Uint8Array
profileKeyBuffer?: Uint8Array
) {
const profileKey = StringUtils.decode(profileKeyBuffer, 'base64');
if (conversation.isPrivate()) {
await conversation.setProfileKey(profileKey);
await conversation.setProfileKey(profileKeyBuffer);
} else {
await sendingDeviceConversation.setProfileKey(profileKey);
await sendingDeviceConversation.setProfileKey(profileKeyBuffer);
}
}
@ -360,12 +358,7 @@ async function handleRegularMessage(
}
if (dataMessage.profileKey) {
await processProfileKey(
source,
conversation,
sendingDeviceConversation,
dataMessage.profileKey
);
await processProfileKey(conversation, sendingDeviceConversation, dataMessage.profileKey);
}
// we just received a message from that user so we reset the typing indicator for this convo

View file

@ -273,42 +273,6 @@ async function handleDecryptedEnvelope(envelope: EnvelopePlus, plaintext: ArrayB
}
}
/**
* Only used for opengroupv1 it seems.
* To be removed soon
*/
export async function handlePublicMessage(messageData: any) {
const { source } = messageData;
const { group, profile, profileKey } = messageData.message;
const isMe = UserUtils.isUsFromCache(source);
if (!isMe && profile) {
const conversation = await getConversationController().getOrCreateAndWait(
source,
ConversationTypeEnum.PRIVATE
);
await updateProfileOneAtATime(conversation, profile, profileKey);
}
const isPublicVisibleMessage = group && group.id && !!group.id.match(openGroupPrefixRegex);
if (!isPublicVisibleMessage) {
throw new Error('handlePublicMessage Should only be called with public message groups');
}
const ev = {
// Public chat messages from ourselves should be outgoing
type: isMe ? 'sent' : 'message',
data: messageData,
confirm: () => {
/* do nothing */
},
};
await handleMessageEvent(ev); // open groups v1
}
export async function handleOpenGroupV2Message(
message: OpenGroupMessageV2,
roomInfos: OpenGroupRequestCommonType

View file

@ -3,7 +3,7 @@ import { UserUtils } from '.';
import { getItemById } from '../../../ts/data/data';
import { KeyPair } from '../../../libtextsecure/libsignal-protocol';
import { PubKey } from '../types';
import { toHex } from './String';
import { fromHexToArray, toHex } from './String';
import { getConversationController } from '../conversations';
export type HexKeyPair = {
@ -97,14 +97,15 @@ export function getOurProfile(): OurLokiProfile | undefined {
// in their primary device's conversation
const ourNumber = window.storage.get('primaryDevicePubKey');
const ourConversation = getConversationController().get(ourNumber);
const profileKey = new Uint8Array(window.storage.get('profileKey'));
const ourProfileKeyHex = ourConversation.get('profileKey');
const profileKeyAsBytes = ourProfileKeyHex ? fromHexToArray(ourProfileKeyHex) : null;
const avatarPointer = ourConversation.get('avatarPointer');
const { displayName } = ourConversation.getLokiProfile();
return {
displayName,
avatarPointer,
profileKey: profileKey.length ? profileKey : null,
profileKey: profileKeyAsBytes?.length ? profileKeyAsBytes : null,
};
} catch (e) {
window?.log?.error(`Failed to get our profile: ${e}`);

View file

@ -14,7 +14,7 @@ import {
ConfigurationMessageContact,
} from '../messages/outgoing/controlMessage/ConfigurationMessage';
import { ConversationModel } from '../../models/conversation';
import { fromBase64ToArray } from './String';
import { fromBase64ToArray, fromHexToArray } from './String';
import { SignalService } from '../../protobuf';
import _ from 'lodash';
import {
@ -160,9 +160,14 @@ const getValidContacts = (convos: Array<ConversationModel>) => {
const contacts = contactsModels.map(c => {
try {
const profileKeyForContact = c.get('profileKey')
? fromBase64ToArray(c.get('profileKey') as string)
: undefined;
const profileKey = c.get('profileKey');
let profileKeyForContact;
if (typeof profileKey === 'string') {
profileKeyForContact = fromHexToArray(profileKey);
} else if (profileKey) {
console.warn('AUDRIC migration todo');
debugger;
}
return new ConfigurationMessageContact({
publicKey: c.id,
@ -189,8 +194,12 @@ export const getCurrentConfigurationMessage = async (convos: Array<ConversationM
if (!ourConvo) {
window?.log?.error('Could not find our convo while building a configuration message.');
}
const profileKeyFromStorage = window.storage.get('profileKey');
const profileKey = profileKeyFromStorage ? new Uint8Array(profileKeyFromStorage) : undefined;
const ourProfileKeyHex =
getConversationController()
.get(UserUtils.getOurPubKeyStrFromCache())
?.get('profileKey') || null;
const profileKey = ourProfileKeyHex ? fromHexToArray(ourProfileKeyHex) : undefined;
const profilePicture = ourConvo?.get('avatarPointer') || undefined;
const displayName = ourConvo?.getLokiProfile()?.displayName || undefined;