Merge pull request #1794 from Bilb/fix-profile-key-config-message

Fix profile key config message
pull/1795/head
Audric Ackermann 4 years ago committed by GitHub
commit 2a371d3c57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,7 +9,6 @@ module.exports = {
concatenateBytes,
constantTimeEqual,
decryptSymmetric,
deriveAccessKey,
encryptAesCtr,
encryptSymmetric,
getRandomBytes,
@ -27,13 +26,6 @@ function bytesFromString(string) {
// High-level Operations
async function deriveAccessKey(profileKey) {
const iv = getZeroes(12);
const plaintext = getZeroes(16);
const accessKey = await _encrypt_aes_gcm(profileKey, iv, plaintext);
return _getFirstBytes(accessKey, 16);
}
const IV_LENGTH = 16;
const MAC_LENGTH = 16;
const NONCE_LENGTH = 16;
@ -141,17 +133,6 @@ async function encryptAesCtr(key, plaintext, counter) {
return ciphertext;
}
async function _encrypt_aes_gcm(key, iv, plaintext) {
const algorithm = {
name: 'AES-GCM',
iv,
};
const extractable = false;
const cryptoKey = await crypto.subtle.importKey('raw', key, algorithm, extractable, ['encrypt']);
return crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
}
// Utility
function getRandomBytes(n) {

@ -1,6 +1,6 @@
/* global crypto */
const { isFunction, isNumber } = require('lodash');
const { isFunction } = require('lodash');
const { createLastMessageUpdate } = require('../../../ts/types/Conversation');
const { arrayBufferToBase64 } = require('../crypto');
@ -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,
};

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.6.7",
"version": "1.6.8",
"license": "GPL-3.0",
"author": {
"name": "Loki Project",

@ -210,7 +210,6 @@ const AvatarItem = (props: {
};
const ConversationListItem = (props: Props) => {
// console.warn('ConversationListItem', props.id.substr(-1), ': ', props);
const {
activeAt,
unreadCount,

@ -62,6 +62,7 @@ export const Image = (props: Props) => {
const role = canClick ? 'button' : undefined;
const { loading, urlToLoad } = useEncryptedFileFetch(url, attachment.contentType);
// data will be url if loading is finished and '' if not
const srcData = !loading ? urlToLoad : '';

@ -19,12 +19,13 @@ export const useEncryptedFileFetch = (url: string, contentType: string) => {
}
useEffect(() => {
setLoading(true);
mountedRef.current = true;
void fetchUrl();
return () => {
mountedRef.current = false;
};
}, [url]);
return { urlToLoad, loading };
};

@ -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

@ -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,8 +91,10 @@ export interface ConversationAttributes {
nickname?: string;
profile?: any;
profileAvatar?: any;
/**
* Consider this being a hex string if it set
*/
profileKey?: string;
accessKey?: any;
triggerNotificationsFor: ConversationNotificationSettingType;
isTrustedForAttachmentDownload: boolean;
isPinned: boolean;
@ -129,8 +131,10 @@ export interface ConversationAttributesOptionals {
nickname?: string;
profile?: any;
profileAvatar?: any;
/**
* Consider this being a hex string if it set
*/
profileKey?: string;
accessKey?: any;
triggerNotificationsFor?: ConversationNotificationSettingType;
isTrustedForAttachmentDownload?: boolean;
isPinned: boolean;
@ -1196,36 +1200,28 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
await this.commit();
}
}
public async setProfileKey(profileKey: string) {
// profileKey is a string so we can compare it directly
if (this.get('profileKey') !== profileKey) {
this.set({
profileKey,
accessKey: null,
});
await this.deriveAccessKeyIfNeeded();
await this.commit();
}
}
/**
* 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;
public async deriveAccessKeyIfNeeded() {
const profileKey = this.get('profileKey');
if (!profileKey) {
return;
}
if (this.get('accessKey')) {
return;
}
try {
const profileKeyBuffer = fromBase64ToArrayBuffer(profileKey);
const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey(profileKeyBuffer);
const accessKey = fromArrayBufferToBase64(accessKeyBuffer);
this.set({ accessKey });
} catch (e) {
window?.log?.warn(`Failed to derive access key for ${this.id}`);
const profileKeyHex = toHex(profileKey);
// profileKey is a string so we can compare it directly
if (this.get('profileKey') !== profileKeyHex) {
this.set({
profileKey: profileKeyHex,
});
if (autoCommit) {
await this.commit();
}
}
}

@ -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,

@ -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);

@ -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

@ -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

@ -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}`);

@ -14,7 +14,7 @@ import {
ConfigurationMessageContact,
} from '../messages/outgoing/controlMessage/ConfigurationMessage';
import { ConversationModel } from '../../models/conversation';
import { fromBase64ToArray } from './String';
import { fromBase64ToArray, fromHexToArray, toHex } from './String';
import { SignalService } from '../../protobuf';
import _ from 'lodash';
import {
@ -160,9 +160,24 @@ 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') {
// this will throw if the profileKey is not in hex.
try {
profileKeyForContact = fromHexToArray(profileKey);
} catch (e) {
profileKeyForContact = fromBase64ToArray(profileKey);
// if the line above does not fail, update the stored profileKey for this convo
void c.setProfileKey(profileKeyForContact);
}
} else if (profileKey) {
window.log.warn(
'Got a profileKey for a contact in another format than string. Contact: ',
c.id
);
return null;
}
return new ConfigurationMessageContact({
publicKey: c.id,
@ -189,8 +204,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;

Loading…
Cancel
Save