Handle PNI keys from ProvisionMessage

This commit is contained in:
Fedor Indutny 2022-03-01 15:01:21 -08:00 committed by GitHub
parent 19441cd3f3
commit 2b0c98f943
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 35 deletions

View File

@ -14,9 +14,13 @@ message ProvisionEnvelope {
} }
message ProvisionMessage { message ProvisionMessage {
optional bytes identityKeyPrivate = 2; optional bytes aciIdentityKeyPublic = 1;
optional bytes aciIdentityKeyPrivate = 2;
optional bytes pniIdentityKeyPublic = 11;
optional bytes pniIdentityKeyPrivate = 12;
optional string aci = 8;
optional string pni = 10;
optional string number = 3; optional string number = 3;
optional string uuid = 8;
optional string provisioningCode = 4; optional string provisioningCode = 4;
optional string userAgent = 5; optional string userAgent = 5;
optional bytes profileKey = 6; optional bytes profileKey = 6;

View File

@ -21,6 +21,7 @@ import { InstallError } from '../../components/installScreen/InstallScreenErrorS
import { MAX_DEVICE_NAME_LENGTH } from '../../components/installScreen/InstallScreenChoosingDeviceNameStep'; import { MAX_DEVICE_NAME_LENGTH } from '../../components/installScreen/InstallScreenChoosingDeviceNameStep';
import { HTTPError } from '../../textsecure/Errors'; import { HTTPError } from '../../textsecure/Errors';
import { isRecord } from '../../util/isRecord'; import { isRecord } from '../../util/isRecord';
import * as Errors from '../../types/errors';
import { normalizeDeviceName } from '../../util/normalizeDeviceName'; import { normalizeDeviceName } from '../../util/normalizeDeviceName';
type PropsType = ComponentProps<typeof InstallScreen>; type PropsType = ComponentProps<typeof InstallScreen>;
@ -183,7 +184,7 @@ export function SmartInstallScreen(): ReactElement {
} catch (error) { } catch (error) {
log.error( log.error(
'confirmNumber: error clearing database', 'confirmNumber: error clearing database',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -205,13 +206,17 @@ export function SmartInstallScreen(): ReactElement {
); );
window.removeSetupMenuItems(); window.removeSetupMenuItems();
} catch (err: unknown) { } catch (error) {
log.error(
'account.registerSecondDevice: got an error',
Errors.toLogFormat(error)
);
if (hasCleanedUp) { if (hasCleanedUp) {
return; return;
} }
setState({ setState({
step: InstallScreenStep.Error, step: InstallScreenStep.Error,
error: getInstallError(err), error: getInstallError(error),
}); });
} }
})(); })();

View File

@ -76,7 +76,7 @@ export type GeneratedKeysType = {
type CreateAccountOptionsType = Readonly<{ type CreateAccountOptionsType = Readonly<{
number: string; number: string;
verificationCode: string; verificationCode: string;
identityKeyPair: KeyPairType; aciKeyPair: KeyPairType;
pniKeyPair?: KeyPairType; pniKeyPair?: KeyPairType;
profileKey?: Uint8Array; profileKey?: Uint8Array;
deviceName?: string; deviceName?: string;
@ -167,7 +167,7 @@ export default class AccountManager extends EventTarget {
async registerSingleDevice(number: string, verificationCode: string) { async registerSingleDevice(number: string, verificationCode: string) {
return this.queueTask(async () => { return this.queueTask(async () => {
const identityKeyPair = generateKeyPair(); const aciKeyPair = generateKeyPair();
const pniKeyPair = generateKeyPair(); const pniKeyPair = generateKeyPair();
const profileKey = getRandomBytes(PROFILE_KEY_LENGTH); const profileKey = getRandomBytes(PROFILE_KEY_LENGTH);
const accessKey = deriveAccessKey(profileKey); const accessKey = deriveAccessKey(profileKey);
@ -177,7 +177,7 @@ export default class AccountManager extends EventTarget {
await this.createAccount({ await this.createAccount({
number, number,
verificationCode, verificationCode,
identityKeyPair, aciKeyPair,
pniKeyPair, pniKeyPair,
profileKey, profileKey,
accessKey, accessKey,
@ -277,7 +277,7 @@ export default class AccountManager extends EventTarget {
if ( if (
!provisionMessage.number || !provisionMessage.number ||
!provisionMessage.provisioningCode || !provisionMessage.provisioningCode ||
!provisionMessage.identityKeyPair !provisionMessage.aciKeyPair
) { ) {
throw new Error( throw new Error(
'AccountManager.registerSecondDevice: Provision message was missing key data' 'AccountManager.registerSecondDevice: Provision message was missing key data'
@ -290,20 +290,31 @@ export default class AccountManager extends EventTarget {
await this.createAccount({ await this.createAccount({
number: provisionMessage.number, number: provisionMessage.number,
verificationCode: provisionMessage.provisioningCode, verificationCode: provisionMessage.provisioningCode,
identityKeyPair: provisionMessage.identityKeyPair, aciKeyPair: provisionMessage.aciKeyPair,
pniKeyPair: provisionMessage.pniKeyPair,
profileKey: provisionMessage.profileKey, profileKey: provisionMessage.profileKey,
deviceName, deviceName,
userAgent: provisionMessage.userAgent, userAgent: provisionMessage.userAgent,
readReceipts: provisionMessage.readReceipts, readReceipts: provisionMessage.readReceipts,
}); });
await clearSessionsAndPreKeys(); await clearSessionsAndPreKeys();
// TODO: DESKTOP-2794
const keyKinds = [UUIDKind.ACI];
if (provisionMessage.pniKeyPair) {
keyKinds.push(UUIDKind.PNI);
}
await Promise.all(
keyKinds.map(async kind => {
const keys = await this.generateKeys( const keys = await this.generateKeys(
SIGNED_KEY_GEN_BATCH_SIZE, SIGNED_KEY_GEN_BATCH_SIZE,
UUIDKind.ACI kind
);
await this.server.registerKeys(keys, kind);
await this.confirmKeys(keys, kind);
})
); );
await this.server.registerKeys(keys, UUIDKind.ACI);
await this.confirmKeys(keys, UUIDKind.ACI);
} finally { } finally {
this.server.finishRegistration(registrationBaton); this.server.finishRegistration(registrationBaton);
} }
@ -493,7 +504,7 @@ export default class AccountManager extends EventTarget {
async createAccount({ async createAccount({
number, number,
verificationCode, verificationCode,
identityKeyPair, aciKeyPair,
pniKeyPair, pniKeyPair,
profileKey, profileKey,
deviceName, deviceName,
@ -511,7 +522,7 @@ export default class AccountManager extends EventTarget {
let encryptedDeviceName; let encryptedDeviceName;
if (deviceName) { if (deviceName) {
encryptedDeviceName = this.encryptDeviceName(deviceName, identityKeyPair); encryptedDeviceName = this.encryptDeviceName(deviceName, aciKeyPair);
await this.deviceNameIsEncrypted(); await this.deviceNameIsEncrypted();
} }
@ -623,7 +634,7 @@ export default class AccountManager extends EventTarget {
await Promise.all([ await Promise.all([
storage.protocol.saveIdentityWithAttributes(new UUID(ourUuid), { storage.protocol.saveIdentityWithAttributes(new UUID(ourUuid), {
...identityAttrs, ...identityAttrs,
publicKey: identityKeyPair.pubKey, publicKey: aciKeyPair.pubKey,
}), }),
pniKeyPair pniKeyPair
? storage.protocol.saveIdentityWithAttributes(new UUID(ourPni), { ? storage.protocol.saveIdentityWithAttributes(new UUID(ourPni), {
@ -636,8 +647,8 @@ export default class AccountManager extends EventTarget {
const identityKeyMap = { const identityKeyMap = {
...(storage.get('identityKeyMap') || {}), ...(storage.get('identityKeyMap') || {}),
[ourUuid]: { [ourUuid]: {
pubKey: Bytes.toBase64(identityKeyPair.pubKey), pubKey: Bytes.toBase64(aciKeyPair.pubKey),
privKey: Bytes.toBase64(identityKeyPair.privKey), privKey: Bytes.toBase64(aciKeyPair.privKey),
}, },
...(pniKeyPair ...(pniKeyPair
? { ? {

View File

@ -17,9 +17,11 @@ import { strictAssert } from '../util/assert';
import { normalizeUuid } from '../util/normalizeUuid'; import { normalizeUuid } from '../util/normalizeUuid';
type ProvisionDecryptResult = { type ProvisionDecryptResult = {
identityKeyPair: KeyPairType; aciKeyPair: KeyPairType;
pniKeyPair?: KeyPairType;
number?: string; number?: string;
uuid?: string; aci?: string;
pni?: string;
provisioningCode?: string; provisioningCode?: string;
userAgent?: string; userAgent?: string;
readReceipts?: boolean; readReceipts?: boolean;
@ -61,18 +63,24 @@ class ProvisioningCipherInner {
const plaintext = decryptAes256CbcPkcsPadding(keys[0], ciphertext, iv); const plaintext = decryptAes256CbcPkcsPadding(keys[0], ciphertext, iv);
const provisionMessage = Proto.ProvisionMessage.decode(plaintext); const provisionMessage = Proto.ProvisionMessage.decode(plaintext);
const privKey = provisionMessage.identityKeyPrivate; const aciPrivKey = provisionMessage.aciIdentityKeyPrivate;
strictAssert(privKey, 'Missing identityKeyPrivate in ProvisionMessage'); const pniPrivKey = provisionMessage.pniIdentityKeyPrivate;
strictAssert(aciPrivKey, 'Missing aciKeyPrivate in ProvisionMessage');
const keyPair = createKeyPair(privKey); const aciKeyPair = createKeyPair(aciPrivKey);
const pniKeyPair = pniPrivKey?.length
? createKeyPair(pniPrivKey)
: undefined;
const { uuid } = provisionMessage; const { aci, pni } = provisionMessage;
strictAssert(uuid, 'Missing uuid in provisioning message'); strictAssert(aci, 'Missing aci in provisioning message');
const ret: ProvisionDecryptResult = { const ret: ProvisionDecryptResult = {
identityKeyPair: keyPair, aciKeyPair,
pniKeyPair,
number: provisionMessage.number, number: provisionMessage.number,
uuid: normalizeUuid(uuid, 'ProvisionMessage.uuid'), aci: normalizeUuid(aci, 'ProvisionMessage.aci'),
pni: pni ? normalizeUuid(pni, 'ProvisionMessage.pni') : undefined,
provisioningCode: provisionMessage.provisioningCode, provisioningCode: provisionMessage.provisioningCode,
userAgent: provisionMessage.userAgent, userAgent: provisionMessage.userAgent,
readReceipts: provisionMessage.readReceipts, readReceipts: provisionMessage.readReceipts,