Improve profile key validation

This commit is contained in:
trevor-signal
2025-10-16 12:07:11 -04:00
committed by GitHub
parent 69aa0b3e30
commit df27e4c4e8
6 changed files with 40 additions and 21 deletions

View File

@@ -204,14 +204,6 @@ export function deriveStorageItemKey({
);
}
export function deriveAccessKey(profileKey: Uint8Array): Uint8Array {
const iv = getZeroes(12);
const plaintext = getZeroes(16);
const accessKey = encryptAesGcm(profileKey, iv, plaintext);
return getFirstBytes(accessKey, 16);
}
export function getAccessKeyVerifier(accessKey: Uint8Array): Uint8Array {
const plaintext = getZeroes(32);

View File

@@ -107,7 +107,6 @@ import {
constantTimeEqual,
decryptProfile,
decryptProfileName,
deriveAccessKey,
hashProfileKey,
} from '../Crypto.js';
import { decryptAttachmentV2 } from '../AttachmentCrypto.js';
@@ -194,7 +193,10 @@ import {
import { imageToBlurHash } from '../util/imageToBlurHash.js';
import { ReceiptType } from '../types/Receipt.js';
import { getQuoteAttachment } from '../util/makeQuote.js';
import { deriveProfileKeyVersion } from '../util/zkgroup.js';
import {
deriveAccessKeyFromProfileKey,
deriveProfileKeyVersion,
} from '../util/zkgroup.js';
import { incrementMessageCounter } from '../util/incrementMessageCounter.js';
import { generateMessageId } from '../util/generateMessageId.js';
import { getMessageAuthorText } from '../util/getMessageAuthorText.js';
@@ -5035,6 +5037,19 @@ export class ConversationModel {
return false;
}
let derivedAccessKey: Uint8Array;
try {
derivedAccessKey = deriveAccessKeyFromProfileKey(
Bytes.fromBase64(profileKey)
);
} catch (error) {
log.warn(
'setProfileKey: refusing to set invalid profile key',
Errors.toLogFormat(error)
);
return false;
}
const oldProfileKey = this.get('profileKey');
// profileKey is a string so we can compare it directly
@@ -5078,7 +5093,10 @@ export class ConversationModel {
}
// Don't trigger immediate profile fetches when syncing to remote storage
this.set({ profileKey }, { noTrigger: viaStorageServiceSync });
this.set(
{ profileKey, accessKey: Bytes.toBase64(derivedAccessKey) },
{ noTrigger: viaStorageServiceSync }
);
// If our profile key was cleared above, we don't tell our linked devices about it.
// We want linked devices to tell us what it should be, instead of telling them to
@@ -5132,10 +5150,11 @@ export class ConversationModel {
return;
}
const profileKeyBuffer = Bytes.fromBase64(profileKey);
const accessKeyBuffer = deriveAccessKey(profileKeyBuffer);
const accessKey = Bytes.toBase64(accessKeyBuffer);
this.set({ accessKey });
this.set({
accessKey: Bytes.toBase64(
deriveAccessKeyFromProfileKey(Bytes.fromBase64(profileKey))
),
});
}
deriveProfileKeyVersion(): string | undefined {

View File

@@ -74,6 +74,7 @@ import {
deriveGroupID,
deriveGroupSecretParams,
deriveGroupPublicParams,
deriveAccessKeyFromProfileKey,
} from '../../util/zkgroup.js';
import { incrementMessageCounter } from '../../util/incrementMessageCounter.js';
import { generateMessageId } from '../../util/generateMessageId.js';
@@ -86,7 +87,7 @@ import { ReadStatus } from '../../messages/MessageReadStatus.js';
import { SendStatus } from '../../messages/MessageSendState.js';
import type { SendStateByConversationId } from '../../messages/MessageSendState.js';
import { SeenStatus } from '../../MessageSeenStatus.js';
import { constantTimeEqual, deriveAccessKey } from '../../Crypto.js';
import { constantTimeEqual } from '../../Crypto.js';
import { signalProtocolStore } from '../../SignalProtocolStore.js';
import * as Bytes from '../../Bytes.js';
import { BACKUP_VERSION, WALLPAPER_TO_BUBBLE_COLOR } from './constants.js';
@@ -989,7 +990,7 @@ export class BackupImportStream extends Writable {
? Bytes.toBase64(contact.profileKey)
: undefined,
accessKey: contact.profileKey
? Bytes.toBase64(deriveAccessKey(contact.profileKey))
? Bytes.toBase64(deriveAccessKeyFromProfileKey(contact.profileKey))
: undefined,
sealedSender: SEALED_SENDER.UNKNOWN,
profileSharing: contact.profileSharing === true,

View File

@@ -25,7 +25,6 @@ import {
deriveSecrets,
encryptDeviceName,
decryptDeviceName,
deriveAccessKey,
getAccessKeyVerifier,
verifyAccessKey,
deriveMasterKeyFromGroupV1,
@@ -59,6 +58,7 @@ import {
} from '../util/AttachmentCrypto.js';
import { getPath } from '../windows/main/attachments.js';
import { MediaTier } from '../types/AttachmentDownload.js';
import { deriveAccessKeyFromProfileKey } from '../util/zkgroup.js';
const { emptyDir } = fsExtra;
@@ -238,7 +238,7 @@ describe('Crypto', () => {
describe('accessKey/profileKey', () => {
it('verification roundtrips', () => {
const profileKey = getRandomBytes(32);
const accessKey = deriveAccessKey(profileKey);
const accessKey = deriveAccessKeyFromProfileKey(profileKey);
const verifier = getAccessKeyVerifier(accessKey);

View File

@@ -38,7 +38,6 @@ import { isMockEnvironment } from '../environment.js';
import { senderCertificateService } from '../services/senderCertificate.js';
import {
decryptDeviceName,
deriveAccessKey,
deriveStorageServiceKey,
deriveMasterKey,
encryptDeviceName,
@@ -79,6 +78,7 @@ import { getMessageQueueTime } from '../util/getMessageQueueTime.js';
import { canAttemptRemoteBackupDownload } from '../util/isBackupEnabled.js';
import { signalProtocolStore } from '../SignalProtocolStore.js';
import { itemStorage } from './Storage.js';
import { deriveAccessKeyFromProfileKey } from '../util/zkgroup.js';
const { isNumber, omit, orderBy } = lodash;
@@ -345,7 +345,7 @@ export default class AccountManager extends EventTarget {
const aciKeyPair = generateKeyPair();
const pniKeyPair = generateKeyPair();
const profileKey = getRandomBytes(PROFILE_KEY_LENGTH);
const accessKey = deriveAccessKey(profileKey);
const accessKey = deriveAccessKeyFromProfileKey(profileKey);
const masterKey = getRandomBytes(MASTER_KEY_LENGTH);
const accountEntropyPool = AccountEntropyPool.generate();
const mediaRootBackupKey = BackupKey.generateRandom().serialize();

View File

@@ -125,6 +125,13 @@ export function deriveProfileKeyVersion(
return profileKeyVersion.toString();
}
export function deriveAccessKeyFromProfileKey(
profileKeyBytes: Uint8Array
): Uint8Array {
const profileKey = new ProfileKey(profileKeyBytes);
return profileKey.deriveAccessKey();
}
export function deriveGroupPublicParams(
groupSecretParamsBuffer: Uint8Array
): Uint8Array {