Page MenuHomePhabricator

No OneTemporary

diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js
index d6dbf7a9b..f662bbbe8 100644
--- a/lib/types/identity-service-types.js
+++ b/lib/types/identity-service-types.js
@@ -1,432 +1,423 @@
// @flow
import invariant from 'invariant';
import t, { type TInterface, type TList, type TDict, type TEnums } from 'tcomb';
import {
identityKeysBlobValidator,
type IdentityKeysBlob,
signedPrekeysValidator,
type SignedPrekeys,
type OneTimeKeysResultValues,
} from './crypto-types.js';
import { type Platform } from './device-types.js';
import {
type OlmSessionInitializationInfo,
olmSessionInitializationInfoValidator,
} from './olm-session-types.js';
import {
currentUserInfoValidator,
type CurrentUserInfo,
} from './user-types.js';
import { values } from '../utils/objects.js';
import { tUserID, tShape } from '../utils/validation-utils.js';
export type UserAuthMetadata = {
+userID: string,
+accessToken: string,
};
// This type should not be altered without also updating OutboundKeyInfoResponse
// in native/native_rust_library/src/identity/x3dh.rs
export type OutboundKeyInfoResponse = {
+payload: string,
+payloadSignature: string,
+contentPrekey: string,
+contentPrekeySignature: string,
+notifPrekey: string,
+notifPrekeySignature: string,
+oneTimeContentPrekey: ?string,
+oneTimeNotifPrekey: ?string,
};
// This type should not be altered without also updating InboundKeyInfoResponse
// in native/native_rust_library/src/identity/x3dh.rs
export type InboundKeyInfoResponse = {
+payload: string,
+payloadSignature: string,
+contentPrekey: string,
+contentPrekeySignature: string,
+notifPrekey: string,
+notifPrekeySignature: string,
+username?: ?string,
+walletAddress?: ?string,
};
export type DeviceOlmOutboundKeys = {
+identityKeysBlob: IdentityKeysBlob,
+contentInitializationInfo: OlmSessionInitializationInfo,
+notifInitializationInfo: OlmSessionInitializationInfo,
+payloadSignature: string,
};
export const deviceOlmOutboundKeysValidator: TInterface<DeviceOlmOutboundKeys> =
tShape<DeviceOlmOutboundKeys>({
identityKeysBlob: identityKeysBlobValidator,
contentInitializationInfo: olmSessionInitializationInfoValidator,
notifInitializationInfo: olmSessionInitializationInfoValidator,
payloadSignature: t.String,
});
export type UserDevicesOlmOutboundKeys = {
+deviceID: string,
+keys: ?DeviceOlmOutboundKeys,
};
export type DeviceOlmInboundKeys = {
+identityKeysBlob: IdentityKeysBlob,
+signedPrekeys: SignedPrekeys,
+payloadSignature: string,
};
export const deviceOlmInboundKeysValidator: TInterface<DeviceOlmInboundKeys> =
tShape<DeviceOlmInboundKeys>({
identityKeysBlob: identityKeysBlobValidator,
signedPrekeys: signedPrekeysValidator,
payloadSignature: t.String,
});
export type UserDevicesOlmInboundKeys = {
+keys: {
+[deviceID: string]: ?DeviceOlmInboundKeys,
},
+username?: ?string,
+walletAddress?: ?string,
};
// This type should not be altered without also updating FarcasterUser in
// keyserver/addons/rust-node-addon/src/identity_client/get_farcaster_users.rs
export type FarcasterUser = {
+userID: string,
+username: string,
+farcasterID: string,
};
export const farcasterUserValidator: TInterface<FarcasterUser> =
tShape<FarcasterUser>({
userID: tUserID,
username: t.String,
farcasterID: t.String,
});
export const farcasterUsersValidator: TList<Array<FarcasterUser>> = t.list(
farcasterUserValidator,
);
export const userDeviceOlmInboundKeysValidator: TInterface<UserDevicesOlmInboundKeys> =
tShape<UserDevicesOlmInboundKeys>({
keys: t.dict(t.String, t.maybe(deviceOlmInboundKeysValidator)),
username: t.maybe(t.String),
walletAddress: t.maybe(t.String),
});
export interface IdentityServiceClient {
// Only a primary device can initiate account deletion, and web cannot be a
// primary device
+deleteWalletUser?: () => Promise<void>;
// Only a primary device can initiate account deletion, and web cannot be a
// primary device
+deletePasswordUser?: (password: string) => Promise<void>;
+logOut: () => Promise<void>;
// This log out type is specific to primary device, and web cannot be a
// primary device
+logOutPrimaryDevice?: () => Promise<void>;
+logOutSecondaryDevice: () => Promise<void>;
+getKeyserverKeys: string => Promise<DeviceOlmOutboundKeys>;
// Users cannot register from web
+registerPasswordUser?: (
username: string,
password: string,
fid: ?string,
) => Promise<IdentityAuthResult>;
// Users cannot register from web
+registerReservedPasswordUser?: (
username: string,
password: string,
keyserverMessage: string,
keyserverSignature: string,
) => Promise<IdentityAuthResult>;
+logInPasswordUser: (
username: string,
password: string,
) => Promise<IdentityAuthResult>;
+getOutboundKeysForUser: (
userID: string,
) => Promise<UserDevicesOlmOutboundKeys[]>;
+getInboundKeysForUser: (
userID: string,
) => Promise<UserDevicesOlmInboundKeys>;
+uploadOneTimeKeys: (oneTimeKeys: OneTimeKeysResultValues) => Promise<void>;
+generateNonce: () => Promise<string>;
// Users cannot register from web
+registerWalletUser?: (
walletAddress: string,
siweMessage: string,
siweSignature: string,
fid: ?string,
) => Promise<IdentityAuthResult>;
+logInWalletUser: (
walletAddress: string,
siweMessage: string,
siweSignature: string,
) => Promise<IdentityAuthResult>;
// Users cannot restore backup from web
+restoreUser?: (
userID: string,
deviceList: SignedDeviceList,
siweMessage?: string,
siweSignature?: string,
) => Promise<IdentityAuthResult>;
// on native, publishing prekeys to Identity is called directly from C++,
// there is no need to expose it to JS
+publishWebPrekeys?: (prekeys: SignedPrekeys) => Promise<void>;
+getDeviceListHistoryForUser: (
userID: string,
sinceTimestamp?: number,
) => Promise<$ReadOnlyArray<SignedDeviceList>>;
+getDeviceListsForUsers: (
userIDs: $ReadOnlyArray<string>,
) => Promise<PeersDeviceLists>;
// updating device list is possible only on Native
// web cannot be a primary device, so there's no need to expose it to JS
+updateDeviceList?: (newDeviceList: SignedDeviceList) => Promise<void>;
+syncPlatformDetails: () => Promise<void>;
+uploadKeysForRegisteredDeviceAndLogIn: (
userID: string,
signedNonce: SignedNonce,
) => Promise<IdentityAuthResult>;
+getFarcasterUsers: (
farcasterIDs: $ReadOnlyArray<string>,
) => Promise<$ReadOnlyArray<FarcasterUser>>;
+linkFarcasterAccount: (farcasterID: string) => Promise<void>;
+unlinkFarcasterAccount: () => Promise<void>;
+findUserIdentities: (
userIDs: $ReadOnlyArray<string>,
) => Promise<UserIdentitiesResponse>;
+versionSupported: () => Promise<boolean>;
+changePassword: (oldPassword: string, newPassword: string) => Promise<void>;
}
export type IdentityServiceAuthLayer = {
+userID: string,
+deviceID: string,
+commServicesAccessToken: string,
};
export type IdentityAuthResult = {
+userID: string,
+accessToken: string,
+username: string,
+preRequestUserState?: ?CurrentUserInfo,
};
export const identityAuthResultValidator: TInterface<IdentityAuthResult> =
tShape<IdentityAuthResult>({
userID: tUserID,
accessToken: t.String,
username: t.String,
preRequestUserState: t.maybe(currentUserInfoValidator),
});
export type IdentityNewDeviceKeyUpload = {
+keyPayload: string,
+keyPayloadSignature: string,
+contentPrekey: string,
+contentPrekeySignature: string,
+notifPrekey: string,
+notifPrekeySignature: string,
+contentOneTimeKeys: $ReadOnlyArray<string>,
+notifOneTimeKeys: $ReadOnlyArray<string>,
};
-export type IdentityExistingDeviceKeyUpload = {
- +keyPayload: string,
- +keyPayloadSignature: string,
- +contentPrekey: string,
- +contentPrekeySignature: string,
- +notifPrekey: string,
- +notifPrekeySignature: string,
-};
-
// Device list types
export type RawDeviceList = {
+devices: $ReadOnlyArray<string>,
+timestamp: number,
};
export const rawDeviceListValidator: TInterface<RawDeviceList> =
tShape<RawDeviceList>({
devices: t.list(t.String),
timestamp: t.Number,
});
export type UsersRawDeviceLists = {
+[userID: string]: RawDeviceList,
};
// User Identity types
export type EthereumIdentity = {
walletAddress: string,
siweMessage: string,
siweSignature: string,
};
export type Identity = {
+username: string,
+ethIdentity: ?EthereumIdentity,
+farcasterID: ?string,
};
export type Identities = {
+[userID: string]: Identity,
};
export const ethereumIdentityValidator: TInterface<EthereumIdentity> =
tShape<EthereumIdentity>({
walletAddress: t.String,
siweMessage: t.String,
siweSignature: t.String,
});
export const identityValidator: TInterface<Identity> = tShape<Identity>({
username: t.String,
ethIdentity: t.maybe(ethereumIdentityValidator),
farcasterID: t.maybe(t.String),
});
export const identitiesValidator: TDict<Identities> = t.dict(
t.String,
identityValidator,
);
export type ReservedUserIdentifiers = {
+[userID: string]: string,
};
export const reservedIdentifiersValidator: TDict<ReservedUserIdentifiers> =
t.dict(t.String, t.String);
export type UserIdentitiesResponse = {
+identities: Identities,
+reservedUserIdentifiers: ReservedUserIdentifiers,
};
export const userIdentitiesResponseValidator: TInterface<UserIdentitiesResponse> =
tShape<UserIdentitiesResponse>({
identities: identitiesValidator,
reservedUserIdentifiers: reservedIdentifiersValidator,
});
export type SignedDeviceList = {
// JSON-stringified RawDeviceList
+rawDeviceList: string,
// Current primary device signature. Absent for Identity Service generated
// device lists.
+curPrimarySignature?: string,
// Previous primary device signature. Present only if primary device
// has changed since last update.
+lastPrimarySignature?: string,
};
export const signedDeviceListValidator: TInterface<SignedDeviceList> =
tShape<SignedDeviceList>({
rawDeviceList: t.String,
curPrimarySignature: t.maybe(t.String),
lastPrimarySignature: t.maybe(t.String),
});
export const signedDeviceListHistoryValidator: TList<Array<SignedDeviceList>> =
t.list(signedDeviceListValidator);
export type UsersSignedDeviceLists = {
+[userID: string]: SignedDeviceList,
};
export const usersSignedDeviceListsValidator: TDict<UsersSignedDeviceLists> =
t.dict(t.String, signedDeviceListValidator);
export type SignedNonce = {
+nonce: string,
+nonceSignature: string,
};
export const ONE_TIME_KEYS_NUMBER = 10;
export const identityDeviceTypes = Object.freeze({
KEYSERVER: 0,
WEB: 1,
IOS: 2,
ANDROID: 3,
WINDOWS: 4,
MAC_OS: 5,
});
export type IdentityDeviceType = $Values<typeof identityDeviceTypes>;
function isIdentityDeviceType(deviceType: number): boolean %checks {
return (
deviceType === 0 ||
deviceType === 1 ||
deviceType === 2 ||
deviceType === 3 ||
deviceType === 4 ||
deviceType === 5
);
}
export function assertIdentityDeviceType(
deviceType: number,
): IdentityDeviceType {
invariant(
isIdentityDeviceType(deviceType),
'number is not IdentityDeviceType enum',
);
return deviceType;
}
export const identityDeviceTypeToPlatform: {
+[identityDeviceType: IdentityDeviceType]: Platform,
} = Object.freeze({
[identityDeviceTypes.WEB]: 'web',
[identityDeviceTypes.ANDROID]: 'android',
[identityDeviceTypes.IOS]: 'ios',
[identityDeviceTypes.WINDOWS]: 'windows',
[identityDeviceTypes.MAC_OS]: 'macos',
});
export const platformToIdentityDeviceType: {
+[platform: Platform]: IdentityDeviceType,
} = Object.freeze({
web: identityDeviceTypes.WEB,
android: identityDeviceTypes.ANDROID,
ios: identityDeviceTypes.IOS,
windows: identityDeviceTypes.WINDOWS,
macos: identityDeviceTypes.MAC_OS,
});
export const identityDeviceTypeValidator: TEnums = t.enums.of(
values(identityDeviceTypes),
);
export type IdentityPlatformDetails = {
+deviceType: IdentityDeviceType,
+codeVersion: number,
+stateVersion?: number,
+majorDesktopVersion?: number,
};
export const identityPlatformDetailsValidator: TInterface<IdentityPlatformDetails> =
tShape<IdentityPlatformDetails>({
deviceType: identityDeviceTypeValidator,
codeVersion: t.Number,
stateVersion: t.maybe(t.Number),
majorDesktopVersion: t.maybe(t.Number),
});
export type UserDevicesPlatformDetails = {
+[deviceID: string]: IdentityPlatformDetails,
};
export const userDevicesPlatformDetailsValidator: TDict<UserDevicesPlatformDetails> =
t.dict(t.String, identityPlatformDetailsValidator);
export type UsersDevicesPlatformDetails = {
+[userID: string]: UserDevicesPlatformDetails,
};
export const usersDevicesPlatformDetailsValidator: TDict<UsersDevicesPlatformDetails> =
t.dict(t.String, userDevicesPlatformDetailsValidator);
export type PeersDeviceLists = {
+usersSignedDeviceLists: UsersSignedDeviceLists,
+usersDevicesPlatformDetails: UsersDevicesPlatformDetails,
};
export const peersDeviceListsValidator: TInterface<PeersDeviceLists> = tShape({
usersSignedDeviceLists: usersSignedDeviceListsValidator,
usersDevicesPlatformDetails: usersDevicesPlatformDetailsValidator,
});
diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js
index c3a86a365..c23efad92 100644
--- a/web/grpc/identity-service-client-wrapper.js
+++ b/web/grpc/identity-service-client-wrapper.js
@@ -1,881 +1,842 @@
// @flow
import { Login, Registration } from '@commapp/opaque-ke-wasm';
import identityServiceConfig from 'lib/facts/identity-service.js';
import type {
OneTimeKeysResultValues,
SignedPrekeys,
} from 'lib/types/crypto-types.js';
import type { PlatformDetails } from 'lib/types/device-types.js';
import {
type SignedDeviceList,
signedDeviceListHistoryValidator,
type SignedNonce,
type IdentityServiceAuthLayer,
type IdentityServiceClient,
type DeviceOlmOutboundKeys,
deviceOlmOutboundKeysValidator,
type UserDevicesOlmOutboundKeys,
type IdentityAuthResult,
type IdentityNewDeviceKeyUpload,
- type IdentityExistingDeviceKeyUpload,
identityAuthResultValidator,
type UserDevicesOlmInboundKeys,
type DeviceOlmInboundKeys,
deviceOlmInboundKeysValidator,
userDeviceOlmInboundKeysValidator,
type FarcasterUser,
farcasterUsersValidator,
type UsersSignedDeviceLists,
type Identities,
type UserIdentitiesResponse,
userIdentitiesResponseValidator,
type PeersDeviceLists,
peersDeviceListsValidator,
type IdentityPlatformDetails,
platformToIdentityDeviceType,
} from 'lib/types/identity-service-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { assertWithValidator } from 'lib/utils/validation-utils.js';
import { VersionInterceptor, AuthInterceptor } from './interceptor.js';
import * as IdentityAuthClient from '../protobufs/identity-auth-client.cjs';
import * as IdentityAuthStructs from '../protobufs/identity-auth-structs.cjs';
import * as identityUnauthStructs from '../protobufs/identity-unauth-structs.cjs';
import {
DeviceKeyUpload,
Empty,
IdentityKeyInfo,
OpaqueLoginFinishRequest,
OpaqueLoginStartRequest,
Prekey,
WalletAuthRequest,
SecondaryDeviceKeysUploadRequest,
GetFarcasterUsersRequest,
} from '../protobufs/identity-unauth-structs.cjs';
import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs';
import { initOpaque } from '../shared-worker/utils/opaque-utils.js';
class IdentityServiceClientWrapper implements IdentityServiceClient {
overridedOpaqueFilepath: string;
platformDetails: PlatformDetails;
authClient: ?IdentityAuthClient.IdentityClientServicePromiseClient;
unauthClient: IdentityUnauthClient.IdentityClientServicePromiseClient;
getNewDeviceKeyUpload: () => Promise<IdentityNewDeviceKeyUpload>;
- getExistingDeviceKeyUpload: () => Promise<IdentityExistingDeviceKeyUpload>;
constructor(
platformDetails: PlatformDetails,
overridedOpaqueFilepath: string,
authLayer: ?IdentityServiceAuthLayer,
getNewDeviceKeyUpload: () => Promise<IdentityNewDeviceKeyUpload>,
- getExistingDeviceKeyUpload: () => Promise<IdentityExistingDeviceKeyUpload>,
) {
this.overridedOpaqueFilepath = overridedOpaqueFilepath;
this.platformDetails = platformDetails;
if (authLayer) {
this.authClient = IdentityServiceClientWrapper.createAuthClient(
platformDetails,
authLayer,
);
}
this.unauthClient =
IdentityServiceClientWrapper.createUnauthClient(platformDetails);
this.getNewDeviceKeyUpload = getNewDeviceKeyUpload;
- this.getExistingDeviceKeyUpload = getExistingDeviceKeyUpload;
}
static determineSocketAddr(): string {
return process.env.IDENTITY_SOCKET_ADDR ?? identityServiceConfig.defaultURL;
}
static createAuthClient(
platformDetails: PlatformDetails,
authLayer: IdentityServiceAuthLayer,
): IdentityAuthClient.IdentityClientServicePromiseClient {
const { userID, deviceID, commServicesAccessToken } = authLayer;
const identitySocketAddr =
IdentityServiceClientWrapper.determineSocketAddr();
const versionInterceptor = new VersionInterceptor<Request, Response>(
platformDetails,
);
const authInterceptor = new AuthInterceptor<Request, Response>(
userID,
deviceID,
commServicesAccessToken,
);
const authClientOpts = {
unaryInterceptors: [versionInterceptor, authInterceptor],
};
return new IdentityAuthClient.IdentityClientServicePromiseClient(
identitySocketAddr,
null,
authClientOpts,
);
}
static createUnauthClient(
platformDetails: PlatformDetails,
): IdentityUnauthClient.IdentityClientServicePromiseClient {
const identitySocketAddr =
IdentityServiceClientWrapper.determineSocketAddr();
const versionInterceptor = new VersionInterceptor<Request, Response>(
platformDetails,
);
const unauthClientOpts = {
unaryInterceptors: [versionInterceptor],
};
return new IdentityUnauthClient.IdentityClientServicePromiseClient(
identitySocketAddr,
null,
unauthClientOpts,
);
}
logOut: () => Promise<void> = async () => {
if (!this.authClient) {
throw new Error('Identity service client is not initialized');
}
await this.authClient.logOutUser(new Empty());
};
logOutSecondaryDevice: () => Promise<void> = async () => {
if (!this.authClient) {
throw new Error('Identity service client is not initialized');
}
await this.authClient.logOutSecondaryDevice(new Empty());
};
getKeyserverKeys: (keyserverID: string) => Promise<DeviceOlmOutboundKeys> =
async (keyserverID: string) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const request = new IdentityAuthStructs.OutboundKeysForUserRequest();
request.setUserId(keyserverID);
const response = await client.getKeyserverKeys(request);
const keyserverInfo = response.getKeyserverInfo();
const identityInfo = keyserverInfo?.getIdentityInfo();
const contentPreKey = keyserverInfo?.getContentPrekey();
const notifPreKey = keyserverInfo?.getNotifPrekey();
const payload = identityInfo?.getPayload();
const keyserverKeys = {
identityKeysBlob: payload ? JSON.parse(payload) : null,
contentInitializationInfo: {
prekey: contentPreKey?.getPrekey(),
prekeySignature: contentPreKey?.getPrekeySignature(),
oneTimeKey: keyserverInfo?.getOneTimeContentPrekey(),
},
notifInitializationInfo: {
prekey: notifPreKey?.getPrekey(),
prekeySignature: notifPreKey?.getPrekeySignature(),
oneTimeKey: keyserverInfo?.getOneTimeNotifPrekey(),
},
payloadSignature: identityInfo?.getPayloadSignature(),
};
return assertWithValidator(keyserverKeys, deviceOlmOutboundKeysValidator);
};
getOutboundKeysForUser: (
userID: string,
) => Promise<UserDevicesOlmOutboundKeys[]> = async (userID: string) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const request = new IdentityAuthStructs.OutboundKeysForUserRequest();
request.setUserId(userID);
const response = await client.getOutboundKeysForUser(request);
const devicesMap = response.toObject()?.devicesMap;
if (!devicesMap || !Array.isArray(devicesMap)) {
throw new Error('Invalid devicesMap');
}
const devicesKeys: (?UserDevicesOlmOutboundKeys)[] = devicesMap.map(
([deviceID, outboundKeysInfo]) => {
const identityInfo = outboundKeysInfo?.identityInfo;
const payload = identityInfo?.payload;
const contentPreKey = outboundKeysInfo?.contentPrekey;
const notifPreKey = outboundKeysInfo?.notifPrekey;
if (typeof deviceID !== 'string') {
console.log(`Invalid deviceID in devicesMap: ${deviceID}`);
return null;
}
const deviceKeys = {
identityKeysBlob: payload ? JSON.parse(payload) : null,
contentInitializationInfo: {
prekey: contentPreKey?.prekey,
prekeySignature: contentPreKey?.prekeySignature,
oneTimeKey: outboundKeysInfo.oneTimeContentPrekey,
},
notifInitializationInfo: {
prekey: notifPreKey?.prekey,
prekeySignature: notifPreKey?.prekeySignature,
oneTimeKey: outboundKeysInfo.oneTimeNotifPrekey,
},
payloadSignature: identityInfo?.payloadSignature,
};
try {
const validatedKeys = assertWithValidator(
deviceKeys,
deviceOlmOutboundKeysValidator,
);
return {
deviceID,
keys: validatedKeys,
};
} catch (e) {
console.log(e);
return {
deviceID,
keys: null,
};
}
},
);
return devicesKeys.filter(Boolean);
};
getInboundKeysForUser: (
userID: string,
) => Promise<UserDevicesOlmInboundKeys> = async (userID: string) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const request = new IdentityAuthStructs.InboundKeysForUserRequest();
request.setUserId(userID);
const response = await client.getInboundKeysForUser(request);
const devicesMap = response.toObject()?.devicesMap;
if (!devicesMap || !Array.isArray(devicesMap)) {
throw new Error('Invalid devicesMap');
}
const devicesKeys: {
[deviceID: string]: ?DeviceOlmInboundKeys,
} = {};
devicesMap.forEach(([deviceID, inboundKeys]) => {
const identityInfo = inboundKeys?.identityInfo;
const payload = identityInfo?.payload;
const contentPreKey = inboundKeys?.contentPrekey;
const notifPreKey = inboundKeys?.notifPrekey;
if (typeof deviceID !== 'string') {
console.log(`Invalid deviceID in devicesMap: ${deviceID}`);
return;
}
const deviceKeys = {
identityKeysBlob: payload ? JSON.parse(payload) : null,
signedPrekeys: {
contentPrekey: contentPreKey?.prekey,
contentPrekeySignature: contentPreKey?.prekeySignature,
notifPrekey: notifPreKey?.prekey,
notifPrekeySignature: notifPreKey?.prekeySignature,
},
payloadSignature: identityInfo?.payloadSignature,
};
try {
devicesKeys[deviceID] = assertWithValidator(
deviceKeys,
deviceOlmInboundKeysValidator,
);
} catch (e) {
console.log(e);
devicesKeys[deviceID] = null;
}
});
const identityInfo = response?.getIdentity();
const inboundUserKeys = {
keys: devicesKeys,
username: identityInfo?.getUsername(),
walletAddress: identityInfo?.getEthIdentity()?.getWalletAddress(),
};
return assertWithValidator(
inboundUserKeys,
userDeviceOlmInboundKeysValidator,
);
};
uploadOneTimeKeys: (oneTimeKeys: OneTimeKeysResultValues) => Promise<void> =
async (oneTimeKeys: OneTimeKeysResultValues) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const contentOneTimeKeysArray = [...oneTimeKeys.contentOneTimeKeys];
const notifOneTimeKeysArray = [...oneTimeKeys.notificationsOneTimeKeys];
const request = new IdentityAuthStructs.UploadOneTimeKeysRequest();
request.setContentOneTimePrekeysList(contentOneTimeKeysArray);
request.setNotifOneTimePrekeysList(notifOneTimeKeysArray);
await client.uploadOneTimeKeys(request);
};
logInPasswordUser: (
username: string,
password: string,
) => Promise<IdentityAuthResult> = async (
username: string,
password: string,
) => {
const client = this.unauthClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const [identityDeviceKeyUpload] = await Promise.all([
- this.getExistingDeviceKeyUpload(),
+ this.getNewDeviceKeyUpload(),
initOpaque(this.overridedOpaqueFilepath),
]);
const opaqueLogin = new Login();
const startRequestBytes = opaqueLogin.start(password);
- const deviceKeyUpload = authExistingDeviceKeyUpload(
+ const deviceKeyUpload = authNewDeviceKeyUpload(
this.platformDetails,
identityDeviceKeyUpload,
);
const loginStartRequest = new OpaqueLoginStartRequest();
loginStartRequest.setUsername(username);
loginStartRequest.setOpaqueLoginRequest(startRequestBytes);
loginStartRequest.setDeviceKeyUpload(deviceKeyUpload);
let loginStartResponse;
try {
loginStartResponse =
await client.logInPasswordUserStart(loginStartRequest);
} catch (e) {
console.log(
'Error calling logInPasswordUserStart:',
getMessageForException(e) ?? 'unknown',
);
throw e;
}
const finishRequestBytes = opaqueLogin.finish(
loginStartResponse.getOpaqueLoginResponse_asU8(),
);
const loginFinishRequest = new OpaqueLoginFinishRequest();
loginFinishRequest.setSessionId(loginStartResponse.getSessionId());
loginFinishRequest.setOpaqueLoginUpload(finishRequestBytes);
let loginFinishResponse;
try {
loginFinishResponse =
await client.logInPasswordUserFinish(loginFinishRequest);
} catch (e) {
console.log(
'Error calling logInPasswordUserFinish:',
getMessageForException(e) ?? 'unknown',
);
throw e;
}
const userID = loginFinishResponse.getUserId();
const accessToken = loginFinishResponse.getAccessToken();
const usernameResponse = loginFinishResponse.getUsername();
const identityAuthResult = {
accessToken,
userID,
username: usernameResponse,
};
return assertWithValidator(identityAuthResult, identityAuthResultValidator);
};
logInWalletUser: (
walletAddress: string,
siweMessage: string,
siweSignature: string,
) => Promise<IdentityAuthResult> = async (
walletAddress: string,
siweMessage: string,
siweSignature: string,
) => {
- const identityDeviceKeyUpload = await this.getExistingDeviceKeyUpload();
- const deviceKeyUpload = authExistingDeviceKeyUpload(
+ const identityDeviceKeyUpload = await this.getNewDeviceKeyUpload();
+ const deviceKeyUpload = authNewDeviceKeyUpload(
this.platformDetails,
identityDeviceKeyUpload,
);
const loginRequest = new WalletAuthRequest();
loginRequest.setSiweMessage(siweMessage);
loginRequest.setSiweSignature(siweSignature);
loginRequest.setDeviceKeyUpload(deviceKeyUpload);
let loginResponse;
try {
loginResponse = await this.unauthClient.logInWalletUser(loginRequest);
} catch (e) {
console.log(
'Error calling logInWalletUser:',
getMessageForException(e) ?? 'unknown',
);
throw e;
}
const userID = loginResponse.getUserId();
const accessToken = loginResponse.getAccessToken();
const username = loginResponse.getUsername();
const identityAuthResult = { accessToken, userID, username };
return assertWithValidator(identityAuthResult, identityAuthResultValidator);
};
uploadKeysForRegisteredDeviceAndLogIn: (
ownerUserID: string,
nonceChallengeResponse: SignedNonce,
) => Promise<IdentityAuthResult> = async (
ownerUserID,
nonceChallengeResponse,
) => {
const identityDeviceKeyUpload = await this.getNewDeviceKeyUpload();
const deviceKeyUpload = authNewDeviceKeyUpload(
this.platformDetails,
identityDeviceKeyUpload,
);
const { nonce, nonceSignature } = nonceChallengeResponse;
const request = new SecondaryDeviceKeysUploadRequest();
request.setUserId(ownerUserID);
request.setNonce(nonce);
request.setNonceSignature(nonceSignature);
request.setDeviceKeyUpload(deviceKeyUpload);
let response;
try {
response =
await this.unauthClient.uploadKeysForRegisteredDeviceAndLogIn(request);
} catch (e) {
console.log(
'Error calling uploadKeysForRegisteredDeviceAndLogIn:',
getMessageForException(e) ?? 'unknown',
);
throw e;
}
const userID = response.getUserId();
const accessToken = response.getAccessToken();
const username = response.getUsername();
const identityAuthResult = { accessToken, userID, username };
return assertWithValidator(identityAuthResult, identityAuthResultValidator);
};
generateNonce: () => Promise<string> = async () => {
const result = await this.unauthClient.generateNonce(new Empty());
return result.getNonce();
};
publishWebPrekeys: (prekeys: SignedPrekeys) => Promise<void> = async (
prekeys: SignedPrekeys,
) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const contentPrekeyUpload = new Prekey();
contentPrekeyUpload.setPrekey(prekeys.contentPrekey);
contentPrekeyUpload.setPrekeySignature(prekeys.contentPrekeySignature);
const notifPrekeyUpload = new Prekey();
notifPrekeyUpload.setPrekey(prekeys.notifPrekey);
notifPrekeyUpload.setPrekeySignature(prekeys.notifPrekeySignature);
const request = new IdentityAuthStructs.RefreshUserPrekeysRequest();
request.setNewContentPrekey(contentPrekeyUpload);
request.setNewNotifPrekey(notifPrekeyUpload);
await client.refreshUserPrekeys(request);
};
getDeviceListHistoryForUser: (
userID: string,
sinceTimestamp?: number,
) => Promise<$ReadOnlyArray<SignedDeviceList>> = async (
userID,
sinceTimestamp,
) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const request = new IdentityAuthStructs.GetDeviceListRequest();
request.setUserId(userID);
if (sinceTimestamp) {
request.setSinceTimestamp(sinceTimestamp);
}
const response = await client.getDeviceListForUser(request);
const rawPayloads = response.getDeviceListUpdatesList();
const deviceListUpdates: SignedDeviceList[] = rawPayloads.map(payload =>
JSON.parse(payload),
);
return assertWithValidator(
deviceListUpdates,
signedDeviceListHistoryValidator,
);
};
getDeviceListsForUsers: (
userIDs: $ReadOnlyArray<string>,
) => Promise<PeersDeviceLists> = async userIDs => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const request = new IdentityAuthStructs.PeersDeviceListsRequest();
request.setUserIdsList([...userIDs]);
const response = await client.getDeviceListsForUsers(request);
const rawPayloads = response.toObject()?.usersDeviceListsMap;
const rawUsersDevicesPlatformDetails =
response.toObject()?.usersDevicesPlatformDetailsMap;
let usersDeviceLists: UsersSignedDeviceLists = {};
rawPayloads.forEach(([userID, rawPayload]) => {
usersDeviceLists = {
...usersDeviceLists,
[userID]: JSON.parse(rawPayload),
};
});
const usersDevicesPlatformDetails: {
[userID: string]: { +[deviceID: string]: IdentityPlatformDetails },
} = {};
for (const [
userID,
rawUserDevicesPlatformDetails,
] of rawUsersDevicesPlatformDetails) {
usersDevicesPlatformDetails[userID] = Object.fromEntries(
rawUserDevicesPlatformDetails.devicesPlatformDetailsMap,
);
}
const peersDeviceLists = {
usersSignedDeviceLists: usersDeviceLists,
usersDevicesPlatformDetails,
};
return assertWithValidator(peersDeviceLists, peersDeviceListsValidator);
};
syncPlatformDetails: () => Promise<void> = async () => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
await client.syncPlatformDetails(new identityUnauthStructs.Empty());
};
getFarcasterUsers: (
farcasterIDs: $ReadOnlyArray<string>,
) => Promise<$ReadOnlyArray<FarcasterUser>> = async farcasterIDs => {
const getFarcasterUsersRequest = new GetFarcasterUsersRequest();
getFarcasterUsersRequest.setFarcasterIdsList([...farcasterIDs]);
let getFarcasterUsersResponse;
try {
getFarcasterUsersResponse = await this.unauthClient.getFarcasterUsers(
getFarcasterUsersRequest,
);
} catch (e) {
console.log(
'Error calling getFarcasterUsers:',
getMessageForException(e) ?? 'unknown',
);
throw e;
}
const farcasterUsersList =
getFarcasterUsersResponse.getFarcasterUsersList();
const returnList = [];
for (const user of farcasterUsersList) {
returnList.push({
userID: user.getUserId(),
username: user.getUsername(),
farcasterID: user.getFarcasterId(),
});
}
return assertWithValidator(returnList, farcasterUsersValidator);
};
linkFarcasterAccount: (farcasterID: string) => Promise<void> =
async farcasterID => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const linkFarcasterAccountRequest =
new IdentityAuthStructs.LinkFarcasterAccountRequest();
linkFarcasterAccountRequest.setFarcasterId(farcasterID);
await client.linkFarcasterAccount(linkFarcasterAccountRequest);
};
unlinkFarcasterAccount: () => Promise<void> = async () => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
await client.unlinkFarcasterAccount(new Empty());
};
findUserIdentities: (
userIDs: $ReadOnlyArray<string>,
) => Promise<UserIdentitiesResponse> = async userIDs => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
const request = new IdentityAuthStructs.UserIdentitiesRequest();
request.setUserIdsList([...userIDs]);
const response = await client.findUserIdentities(request);
const responseObject = response.toObject();
const identityObjects = responseObject?.identitiesMap;
const reservedUserEntries = responseObject?.reservedUserIdentifiersMap;
let identities: Identities = {};
identityObjects.forEach(([userID, identityObject]) => {
identities = {
...identities,
[userID]: {
ethIdentity: identityObject.ethIdentity,
username: identityObject.username,
farcasterID: identityObject.farcasterId,
},
};
});
const userIdentitiesResponse: UserIdentitiesResponse = {
identities,
reservedUserIdentifiers: Object.fromEntries(reservedUserEntries),
};
return assertWithValidator(
userIdentitiesResponse,
userIdentitiesResponseValidator,
);
};
versionSupported: () => Promise<boolean> = async () => {
const client = this.unauthClient;
try {
await client.ping(new Empty());
return true;
} catch (e) {
if (getMessageForException(e) === 'unsupported_version') {
return false;
}
throw e;
}
};
changePassword: (oldPassword: string, newPassword: string) => Promise<void> =
async (oldPassword, newPassword) => {
const client = this.authClient;
if (!client) {
throw new Error('Identity service client is not initialized');
}
await initOpaque(this.overridedOpaqueFilepath);
const opaqueLogin = new Login();
const loginStartRequestBytes = opaqueLogin.start(oldPassword);
const opaqueRegistration = new Registration();
const registrationStartRequestBytes =
opaqueRegistration.start(newPassword);
const updatePasswordStartRequest =
new IdentityAuthStructs.UpdateUserPasswordStartRequest();
updatePasswordStartRequest.setOpaqueLoginRequest(loginStartRequestBytes);
updatePasswordStartRequest.setOpaqueRegistrationRequest(
registrationStartRequestBytes,
);
let updatePasswordStartResponse;
try {
updatePasswordStartResponse = await client.updateUserPasswordStart(
updatePasswordStartRequest,
);
} catch (e) {
console.log('Error calling updateUserPasswordStart:', e);
throw new Error(
`updateUserPasswordStart RPC failed: ${
getMessageForException(e) ?? 'unknown'
}`,
);
}
const loginFinishRequestBytes = opaqueLogin.finish(
updatePasswordStartResponse.getOpaqueLoginResponse_asU8(),
);
const registrationFinishRequestBytes = opaqueRegistration.finish(
newPassword,
updatePasswordStartResponse.getOpaqueRegistrationResponse_asU8(),
);
const updatePasswordFinishRequest =
new IdentityAuthStructs.UpdateUserPasswordFinishRequest();
updatePasswordFinishRequest.setSessionId(
updatePasswordStartResponse.getSessionId(),
);
updatePasswordFinishRequest.setOpaqueLoginUpload(loginFinishRequestBytes);
updatePasswordFinishRequest.setOpaqueRegistrationUpload(
registrationFinishRequestBytes,
);
try {
await client.updateUserPasswordFinish(updatePasswordFinishRequest);
} catch (e) {
console.log('Error calling updateUserPasswordFinish:', e);
throw new Error(
`updateUserPasswordFinish RPC failed: ${
getMessageForException(e) ?? 'unknown'
}`,
);
}
};
}
function authNewDeviceKeyUpload(
platformDetails: PlatformDetails,
uploadData: IdentityNewDeviceKeyUpload,
): DeviceKeyUpload {
const {
keyPayload,
keyPayloadSignature,
contentPrekey,
contentPrekeySignature,
notifPrekey,
notifPrekeySignature,
contentOneTimeKeys,
notifOneTimeKeys,
} = uploadData;
const identityKeyInfo = createIdentityKeyInfo(
keyPayload,
keyPayloadSignature,
);
const contentPrekeyUpload = createPrekey(
contentPrekey,
contentPrekeySignature,
);
const notifPrekeyUpload = createPrekey(notifPrekey, notifPrekeySignature);
const deviceKeyUpload = createDeviceKeyUpload(
platformDetails,
identityKeyInfo,
contentPrekeyUpload,
notifPrekeyUpload,
contentOneTimeKeys,
notifOneTimeKeys,
);
return deviceKeyUpload;
}
-function authExistingDeviceKeyUpload(
- platformDetails: PlatformDetails,
- uploadData: IdentityExistingDeviceKeyUpload,
-): DeviceKeyUpload {
- const {
- keyPayload,
- keyPayloadSignature,
- contentPrekey,
- contentPrekeySignature,
- notifPrekey,
- notifPrekeySignature,
- } = uploadData;
-
- const identityKeyInfo = createIdentityKeyInfo(
- keyPayload,
- keyPayloadSignature,
- );
-
- const contentPrekeyUpload = createPrekey(
- contentPrekey,
- contentPrekeySignature,
- );
-
- const notifPrekeyUpload = createPrekey(notifPrekey, notifPrekeySignature);
-
- const deviceKeyUpload = createDeviceKeyUpload(
- platformDetails,
- identityKeyInfo,
- contentPrekeyUpload,
- notifPrekeyUpload,
- );
-
- return deviceKeyUpload;
-}
-
function createIdentityKeyInfo(
keyPayload: string,
keyPayloadSignature: string,
): IdentityKeyInfo {
const identityKeyInfo = new IdentityKeyInfo();
identityKeyInfo.setPayload(keyPayload);
identityKeyInfo.setPayloadSignature(keyPayloadSignature);
return identityKeyInfo;
}
function createPrekey(prekey: string, prekeySignature: string): Prekey {
const prekeyUpload = new Prekey();
prekeyUpload.setPrekey(prekey);
prekeyUpload.setPrekeySignature(prekeySignature);
return prekeyUpload;
}
function createDeviceKeyUpload(
platformDetails: PlatformDetails,
identityKeyInfo: IdentityKeyInfo,
contentPrekeyUpload: Prekey,
notifPrekeyUpload: Prekey,
contentOneTimeKeys: $ReadOnlyArray<string> = [],
notifOneTimeKeys: $ReadOnlyArray<string> = [],
): DeviceKeyUpload {
const deviceKeyUpload = new DeviceKeyUpload();
deviceKeyUpload.setDeviceKeyInfo(identityKeyInfo);
deviceKeyUpload.setContentUpload(contentPrekeyUpload);
deviceKeyUpload.setNotifUpload(notifPrekeyUpload);
deviceKeyUpload.setOneTimeContentPrekeysList([...contentOneTimeKeys]);
deviceKeyUpload.setOneTimeNotifPrekeysList([...notifOneTimeKeys]);
deviceKeyUpload.setDeviceType(
platformToIdentityDeviceType[platformDetails.platform],
);
return deviceKeyUpload;
}
export { IdentityServiceClientWrapper };
diff --git a/web/shared-worker/worker/identity-client.js b/web/shared-worker/worker/identity-client.js
index 7bf53f7c7..3b2e2560c 100644
--- a/web/shared-worker/worker/identity-client.js
+++ b/web/shared-worker/worker/identity-client.js
@@ -1,71 +1,67 @@
// @flow
import type { PlatformDetails } from 'lib/types/device-types.js';
-import {
- getNewDeviceKeyUpload,
- getExistingDeviceKeyUpload,
-} from './worker-crypto.js';
+import { getNewDeviceKeyUpload } from './worker-crypto.js';
import { IdentityServiceClientWrapper } from '../../grpc/identity-service-client-wrapper.js';
import {
type WorkerResponseMessage,
type WorkerRequestMessage,
workerRequestMessageTypes,
workerResponseMessageTypes,
} from '../../types/worker-types.js';
import type { EmscriptenModule } from '../types/module.js';
import type { SQLiteQueryExecutor } from '../types/sqlite-query-executor.js';
import { initOpaque } from '../utils/opaque-utils.js';
let identityClient: ?IdentityServiceClientWrapper = null;
async function processAppIdentityClientRequest(
sqliteQueryExecutor: SQLiteQueryExecutor,
dbModule: EmscriptenModule,
platformDetails: PlatformDetails,
message: WorkerRequestMessage,
): Promise<?WorkerResponseMessage> {
if (
message.type === workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT
) {
void initOpaque(message.opaqueWasmPath);
identityClient = new IdentityServiceClientWrapper(
platformDetails,
message.opaqueWasmPath,
message.authLayer,
async () => getNewDeviceKeyUpload(),
- async () => getExistingDeviceKeyUpload(),
);
return undefined;
}
if (!identityClient) {
throw new Error('Identity client not created');
}
if (message.type === workerRequestMessageTypes.CALL_IDENTITY_CLIENT_METHOD) {
// Flow doesn't allow us to access methods like this (it needs an index
// signature declaration in the object type)
const method: (...$ReadOnlyArray<mixed>) => mixed = (identityClient: any)[
message.method
];
if (typeof method !== 'function') {
throw new Error(
`Couldn't find identity client method with name '${message.method}'`,
);
}
const result = await method(...message.args);
return {
type: workerResponseMessageTypes.CALL_IDENTITY_CLIENT_METHOD,
result,
};
}
return undefined;
}
function getIdentityClient(): ?IdentityServiceClientWrapper {
return identityClient;
}
export { processAppIdentityClientRequest, getIdentityClient };
diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js
index 7775deb13..d025897c8 100644
--- a/web/shared-worker/worker/worker-crypto.js
+++ b/web/shared-worker/worker/worker-crypto.js
@@ -1,1030 +1,998 @@
// @flow
import olm, { type Utility } from '@commapp/olm';
import localforage from 'localforage';
import uuid from 'uuid';
import { initialEncryptedMessageContent } from 'lib/shared/crypto-utils.js';
import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
import {
type OLMIdentityKeys,
type PickledOLMAccount,
type IdentityKeysBlob,
type SignedIdentityKeysBlob,
type OlmAPI,
type OneTimeKeysResultValues,
type ClientPublicKeys,
type NotificationsOlmDataType,
type EncryptedData,
type OutboundSessionCreationResult,
} from 'lib/types/crypto-types.js';
import type { PlatformDetails } from 'lib/types/device-types.js';
-import type {
- IdentityNewDeviceKeyUpload,
- IdentityExistingDeviceKeyUpload,
-} from 'lib/types/identity-service-types.js';
+import type { IdentityNewDeviceKeyUpload } from 'lib/types/identity-service-types.js';
import type { OlmSessionInitializationInfo } from 'lib/types/olm-session-types.js';
import type { InboundP2PMessage } from 'lib/types/sqlite-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { entries } from 'lib/utils/objects.js';
import {
retrieveAccountKeysSet,
getAccountOneTimeKeys,
getAccountPrekeysSet,
shouldForgetPrekey,
shouldRotatePrekey,
- retrieveIdentityKeysAndPrekeys,
olmSessionErrors,
} from 'lib/utils/olm-utils.js';
import { getIdentityClient } from './identity-client.js';
import { getProcessingStoreOpsExceptionMessage } from './process-operations.js';
import {
getDBModule,
getSQLiteQueryExecutor,
getPlatformDetails,
} from './worker-database.js';
import {
getOlmDataKeyForCookie,
getOlmEncryptionKeyDBLabelForCookie,
getOlmDataKeyForDeviceID,
getOlmEncryptionKeyDBLabelForDeviceID,
encryptNotification,
type NotificationAccountWithPicklingKey,
getNotifsCryptoAccount,
persistNotifsAccountWithOlmData,
} from '../../push-notif/notif-crypto-utils.js';
import {
type WorkerRequestMessage,
type WorkerResponseMessage,
workerRequestMessageTypes,
workerResponseMessageTypes,
type LegacyCryptoStore,
} from '../../types/worker-types.js';
import type { OlmPersistSession } from '../types/sqlite-query-executor.js';
type OlmSession = { +session: olm.Session, +version: number };
type OlmSessions = {
[deviceID: string]: OlmSession,
};
type WorkerCryptoStore = {
+contentAccountPickleKey: string,
+contentAccount: olm.Account,
+contentSessions: OlmSessions,
};
let cryptoStore: ?WorkerCryptoStore = null;
let olmUtility: ?Utility = null;
function clearCryptoStore() {
cryptoStore = null;
}
async function persistCryptoStore(
notifsCryptoAccount?: NotificationAccountWithPicklingKey,
withoutTransaction: boolean = false,
) {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
throw new Error(
"Couldn't persist crypto store because database is not initialized",
);
}
if (!cryptoStore) {
throw new Error("Couldn't persist crypto store because it doesn't exist");
}
const { contentAccountPickleKey, contentAccount, contentSessions } =
cryptoStore;
const pickledContentAccount: PickledOLMAccount = {
picklingKey: contentAccountPickleKey,
pickledAccount: contentAccount.pickle(contentAccountPickleKey),
};
const pickledContentSessions: OlmPersistSession[] = entries(
contentSessions,
).map(([targetDeviceID, sessionData]) => ({
targetDeviceID,
sessionData: sessionData.session.pickle(contentAccountPickleKey),
version: sessionData.version,
}));
try {
if (!withoutTransaction) {
sqliteQueryExecutor.beginTransaction();
}
sqliteQueryExecutor.storeOlmPersistAccount(
sqliteQueryExecutor.getContentAccountID(),
JSON.stringify(pickledContentAccount),
);
for (const pickledSession of pickledContentSessions) {
sqliteQueryExecutor.storeOlmPersistSession(pickledSession);
}
if (notifsCryptoAccount) {
const {
notificationAccount,
picklingKey,
synchronizationValue,
accountEncryptionKey,
} = notifsCryptoAccount;
const pickledAccount = notificationAccount.pickle(picklingKey);
const accountWithPicklingKey: PickledOLMAccount = {
pickledAccount,
picklingKey,
};
await persistNotifsAccountWithOlmData({
accountEncryptionKey,
accountWithPicklingKey,
synchronizationValue,
forceWrite: true,
});
}
if (!withoutTransaction) {
sqliteQueryExecutor.commitTransaction();
}
} catch (err) {
if (!withoutTransaction) {
sqliteQueryExecutor.rollbackTransaction();
}
throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule));
}
}
async function createAndPersistNotificationsOutboundSession(
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
dataPersistenceKey: string,
dataEncryptionKeyDBLabel: string,
): Promise<EncryptedData> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const notificationAccountWithPicklingKey = await getNotifsCryptoAccount();
const {
notificationAccount,
picklingKey,
synchronizationValue,
accountEncryptionKey,
} = notificationAccountWithPicklingKey;
const notificationsPrekey = notificationsInitializationInfo.prekey;
const session = new olm.Session();
if (notificationsInitializationInfo.oneTimeKey) {
session.create_outbound(
notificationAccount,
notificationsIdentityKeys.curve25519,
notificationsIdentityKeys.ed25519,
notificationsPrekey,
notificationsInitializationInfo.prekeySignature,
notificationsInitializationInfo.oneTimeKey,
);
} else {
session.create_outbound_without_otk(
notificationAccount,
notificationsIdentityKeys.curve25519,
notificationsIdentityKeys.ed25519,
notificationsPrekey,
notificationsInitializationInfo.prekeySignature,
);
}
const { body: message, type: messageType } = session.encrypt(
JSON.stringify(initialEncryptedMessageContent),
);
const mainSession = session.pickle(
notificationAccountWithPicklingKey.picklingKey,
);
const notificationsOlmData: NotificationsOlmDataType = {
mainSession,
pendingSessionUpdate: mainSession,
updateCreationTimestamp: Date.now(),
picklingKey,
};
const pickledAccount = notificationAccount.pickle(picklingKey);
const accountWithPicklingKey: PickledOLMAccount = {
pickledAccount,
picklingKey,
};
await persistNotifsAccountWithOlmData({
accountEncryptionKey,
accountWithPicklingKey,
olmDataKey: dataPersistenceKey,
olmData: notificationsOlmData,
olmEncryptionKeyDBLabel: dataEncryptionKeyDBLabel,
synchronizationValue,
forceWrite: true,
});
return { message, messageType };
}
async function getOrCreateOlmAccount(accountIDInDB: number): Promise<{
+picklingKey: string,
+account: olm.Account,
+synchronizationValue?: ?string,
}> {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
throw new Error('Database not initialized');
}
const account = new olm.Account();
let picklingKey;
let accountDBString;
try {
accountDBString =
sqliteQueryExecutor.getOlmPersistAccountDataWeb(accountIDInDB);
} catch (err) {
throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule));
}
const maybeNotifsCryptoAccount: ?NotificationAccountWithPicklingKey =
await (async () => {
if (accountIDInDB !== sqliteQueryExecutor.getNotifsAccountID()) {
return undefined;
}
try {
return await getNotifsCryptoAccount();
} catch (e) {
return undefined;
}
})();
if (maybeNotifsCryptoAccount) {
const {
notificationAccount,
picklingKey: notificationAccountPicklingKey,
synchronizationValue,
} = maybeNotifsCryptoAccount;
return {
account: notificationAccount,
picklingKey: notificationAccountPicklingKey,
synchronizationValue,
};
}
if (accountDBString.isNull) {
picklingKey = uuid.v4();
account.create();
} else {
const dbAccount: PickledOLMAccount = JSON.parse(accountDBString.value);
picklingKey = dbAccount.picklingKey;
account.unpickle(picklingKey, dbAccount.pickledAccount);
}
if (accountIDInDB === sqliteQueryExecutor.getNotifsAccountID()) {
return { picklingKey, account, synchronizationValue: uuid.v4() };
}
return { picklingKey, account };
}
function getOlmSessions(picklingKey: string): OlmSessions {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
throw new Error(
"Couldn't get olm sessions because database is not initialized",
);
}
let dbSessionsData;
try {
dbSessionsData = sqliteQueryExecutor.getOlmPersistSessionsData();
} catch (err) {
throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule));
}
const sessionsData: OlmSessions = {};
for (const persistedSession: OlmPersistSession of dbSessionsData) {
const { sessionData, version } = persistedSession;
const session = new olm.Session();
session.unpickle(picklingKey, sessionData);
sessionsData[persistedSession.targetDeviceID] = {
session,
version,
};
}
return sessionsData;
}
function unpickleInitialCryptoStoreAccount(
account: PickledOLMAccount,
): olm.Account {
const { picklingKey, pickledAccount } = account;
const olmAccount = new olm.Account();
olmAccount.unpickle(picklingKey, pickledAccount);
return olmAccount;
}
async function initializeCryptoAccount(
olmWasmPath: string,
initialCryptoStore: ?LegacyCryptoStore,
) {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
if (!sqliteQueryExecutor) {
throw new Error('Database not initialized');
}
await olm.init({ locateFile: () => olmWasmPath });
olmUtility = new olm.Utility();
if (initialCryptoStore) {
cryptoStore = {
contentAccountPickleKey: initialCryptoStore.primaryAccount.picklingKey,
contentAccount: unpickleInitialCryptoStoreAccount(
initialCryptoStore.primaryAccount,
),
contentSessions: {},
};
const notifsCryptoAccount = {
picklingKey: initialCryptoStore.notificationAccount.picklingKey,
notificationAccount: unpickleInitialCryptoStoreAccount(
initialCryptoStore.notificationAccount,
),
synchronizationValue: uuid.v4(),
};
await persistCryptoStore(notifsCryptoAccount);
return;
}
await olmAPI.initializeCryptoAccount();
}
async function processAppOlmApiRequest(
message: WorkerRequestMessage,
): Promise<?WorkerResponseMessage> {
if (message.type === workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT) {
await initializeCryptoAccount(
message.olmWasmPath,
message.initialCryptoStore,
);
} else if (message.type === workerRequestMessageTypes.CALL_OLM_API_METHOD) {
const method: (...$ReadOnlyArray<mixed>) => mixed = (olmAPI[
message.method
]: any);
// Flow doesn't allow us to bind the (stringified) method name with
// the argument types so we need to pass the args as mixed.
const result = await method(...message.args);
return {
type: workerResponseMessageTypes.CALL_OLM_API_METHOD,
result,
};
}
return undefined;
}
async function getSignedIdentityKeysBlob(): Promise<SignedIdentityKeysBlob> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
const { notificationAccount } = await getNotifsCryptoAccount();
const identityKeysBlob: IdentityKeysBlob = {
notificationIdentityPublicKeys: JSON.parse(
notificationAccount.identity_keys(),
),
primaryIdentityPublicKeys: JSON.parse(contentAccount.identity_keys()),
};
const payloadToBeSigned: string = JSON.stringify(identityKeysBlob);
const signedIdentityKeysBlob: SignedIdentityKeysBlob = {
payload: payloadToBeSigned,
signature: contentAccount.sign(payloadToBeSigned),
};
return signedIdentityKeysBlob;
}
async function getNewDeviceKeyUpload(): Promise<IdentityNewDeviceKeyUpload> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
const [notifsCryptoAccount, signedIdentityKeysBlob] = await Promise.all([
getNotifsCryptoAccount(),
getSignedIdentityKeysBlob(),
]);
const primaryAccountKeysSet = retrieveAccountKeysSet(contentAccount);
const notificationAccountKeysSet = retrieveAccountKeysSet(
notifsCryptoAccount.notificationAccount,
);
contentAccount.mark_keys_as_published();
notifsCryptoAccount.notificationAccount.mark_keys_as_published();
await persistCryptoStore(notifsCryptoAccount);
return {
keyPayload: signedIdentityKeysBlob.payload,
keyPayloadSignature: signedIdentityKeysBlob.signature,
contentPrekey: primaryAccountKeysSet.prekey,
contentPrekeySignature: primaryAccountKeysSet.prekeySignature,
notifPrekey: notificationAccountKeysSet.prekey,
notifPrekeySignature: notificationAccountKeysSet.prekeySignature,
contentOneTimeKeys: primaryAccountKeysSet.oneTimeKeys,
notifOneTimeKeys: notificationAccountKeysSet.oneTimeKeys,
};
}
-async function getExistingDeviceKeyUpload(): Promise<IdentityExistingDeviceKeyUpload> {
- if (!cryptoStore) {
- throw new Error('Crypto account not initialized');
- }
- const { contentAccount } = cryptoStore;
- const [notifsCryptoAccount, signedIdentityKeysBlob] = await Promise.all([
- getNotifsCryptoAccount(),
- getSignedIdentityKeysBlob(),
- ]);
-
- const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } =
- retrieveIdentityKeysAndPrekeys(contentAccount);
- const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } =
- retrieveIdentityKeysAndPrekeys(notifsCryptoAccount.notificationAccount);
-
- await persistCryptoStore(notifsCryptoAccount);
-
- return {
- keyPayload: signedIdentityKeysBlob.payload,
- keyPayloadSignature: signedIdentityKeysBlob.signature,
- contentPrekey,
- contentPrekeySignature,
- notifPrekey,
- notifPrekeySignature,
- };
-}
-
function getNotifsPersistenceKeys(
cookie: ?string,
keyserverID: string,
platformDetails: PlatformDetails,
) {
if (hasMinCodeVersion(platformDetails, { majorDesktop: 12 })) {
return {
notifsOlmDataEncryptionKeyDBLabel: getOlmEncryptionKeyDBLabelForCookie(
cookie,
keyserverID,
),
notifsOlmDataContentKey: getOlmDataKeyForCookie(cookie, keyserverID),
};
} else {
return {
notifsOlmDataEncryptionKeyDBLabel:
getOlmEncryptionKeyDBLabelForCookie(cookie),
notifsOlmDataContentKey: getOlmDataKeyForCookie(cookie),
};
}
}
async function reassignLocalForageItem(source: string, destination: string) {
const value = await localforage.getItem<mixed>(source);
if (!value) {
return;
}
const valueAtDestination = await localforage.getItem<mixed>(destination);
if (!valueAtDestination) {
await localforage.setItem(destination, value);
}
await localforage.removeItem(source);
}
const olmAPI: OlmAPI = {
async initializeCryptoAccount(): Promise<void> {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
if (!sqliteQueryExecutor) {
throw new Error('Database not initialized');
}
const [contentAccountResult, notificationAccountResult] = await Promise.all(
[
getOrCreateOlmAccount(sqliteQueryExecutor.getContentAccountID()),
getOrCreateOlmAccount(sqliteQueryExecutor.getNotifsAccountID()),
],
);
const contentSessions = getOlmSessions(contentAccountResult.picklingKey);
cryptoStore = {
contentAccountPickleKey: contentAccountResult.picklingKey,
contentAccount: contentAccountResult.account,
contentSessions,
};
const notifsCryptoAccount = {
picklingKey: notificationAccountResult.picklingKey,
notificationAccount: notificationAccountResult.account,
synchronizationValue: notificationAccountResult.synchronizationValue,
};
await persistCryptoStore(notifsCryptoAccount);
},
async getUserPublicKey(): Promise<ClientPublicKeys> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
const [{ notificationAccount }, { payload, signature }] = await Promise.all(
[getNotifsCryptoAccount(), getSignedIdentityKeysBlob()],
);
return {
primaryIdentityPublicKeys: JSON.parse(contentAccount.identity_keys()),
notificationIdentityPublicKeys: JSON.parse(
notificationAccount.identity_keys(),
),
blobPayload: payload,
signature,
};
},
async encrypt(content: string, deviceID: string): Promise<EncryptedData> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const olmSession = cryptoStore.contentSessions[deviceID];
if (!olmSession) {
throw new Error(olmSessionErrors.sessionDoesNotExist);
}
const encryptedContent = olmSession.session.encrypt(content);
await persistCryptoStore();
return {
message: encryptedContent.body,
messageType: encryptedContent.type,
sessionVersion: olmSession.version,
};
},
async encryptAndPersist(
content: string,
deviceID: string,
messageID: string,
): Promise<EncryptedData> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const olmSession = cryptoStore.contentSessions[deviceID];
if (!olmSession) {
throw new Error(olmSessionErrors.sessionDoesNotExist);
}
const encryptedContent = olmSession.session.encrypt(content);
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
throw new Error(
"Couldn't persist crypto store because database is not initialized",
);
}
const result: EncryptedData = {
message: encryptedContent.body,
messageType: encryptedContent.type,
sessionVersion: olmSession.version,
};
sqliteQueryExecutor.beginTransaction();
try {
sqliteQueryExecutor.setCiphertextForOutboundP2PMessage(
messageID,
deviceID,
JSON.stringify(result),
);
await persistCryptoStore(undefined, true);
sqliteQueryExecutor.commitTransaction();
} catch (e) {
sqliteQueryExecutor.rollbackTransaction();
throw e;
}
return result;
},
async encryptNotification(
payload: string,
deviceID: string,
): Promise<EncryptedData> {
const { body: message, type: messageType } = await encryptNotification(
payload,
deviceID,
);
return { message, messageType };
},
async decrypt(
encryptedData: EncryptedData,
deviceID: string,
): Promise<string> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const olmSession = cryptoStore.contentSessions[deviceID];
if (!olmSession) {
throw new Error(olmSessionErrors.sessionDoesNotExist);
}
if (
encryptedData.sessionVersion &&
encryptedData.sessionVersion < olmSession.version
) {
throw new Error(olmSessionErrors.invalidSessionVersion);
}
const result = olmSession.session.decrypt(
encryptedData.messageType,
encryptedData.message,
);
await persistCryptoStore();
return result;
},
async decryptAndPersist(
encryptedData: EncryptedData,
deviceID: string,
userID: string,
messageID: string,
): Promise<string> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const olmSession = cryptoStore.contentSessions[deviceID];
if (!olmSession) {
throw new Error(olmSessionErrors.sessionDoesNotExist);
}
if (
encryptedData.sessionVersion &&
encryptedData.sessionVersion < olmSession.version
) {
throw new Error(olmSessionErrors.invalidSessionVersion);
}
const result = olmSession.session.decrypt(
encryptedData.messageType,
encryptedData.message,
);
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
throw new Error(
"Couldn't persist crypto store because database is not initialized",
);
}
const receivedMessage: InboundP2PMessage = {
messageID,
senderDeviceID: deviceID,
senderUserID: userID,
plaintext: result,
status: 'decrypted',
};
sqliteQueryExecutor.beginTransaction();
try {
sqliteQueryExecutor.addInboundP2PMessage(receivedMessage);
await persistCryptoStore(undefined, true);
sqliteQueryExecutor.commitTransaction();
} catch (e) {
sqliteQueryExecutor.rollbackTransaction();
throw e;
}
return result;
},
async contentInboundSessionCreator(
contentIdentityKeys: OLMIdentityKeys,
initialEncryptedData: EncryptedData,
sessionVersion: number,
overwrite: boolean,
): Promise<string> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount, contentSessions } = cryptoStore;
const existingSession = contentSessions[contentIdentityKeys.ed25519];
if (existingSession) {
if (!overwrite && existingSession.version > sessionVersion) {
throw new Error(olmSessionErrors.alreadyCreated);
} else if (!overwrite && existingSession.version === sessionVersion) {
throw new Error(olmSessionErrors.raceCondition);
}
}
const session = new olm.Session();
session.create_inbound_from(
contentAccount,
contentIdentityKeys.curve25519,
initialEncryptedData.message,
);
contentAccount.remove_one_time_keys(session);
const initialEncryptedMessage = session.decrypt(
initialEncryptedData.messageType,
initialEncryptedData.message,
);
contentSessions[contentIdentityKeys.ed25519] = {
session,
version: sessionVersion,
};
await persistCryptoStore();
return initialEncryptedMessage;
},
async contentOutboundSessionCreator(
contentIdentityKeys: OLMIdentityKeys,
contentInitializationInfo: OlmSessionInitializationInfo,
): Promise<OutboundSessionCreationResult> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount, contentSessions } = cryptoStore;
const existingSession = contentSessions[contentIdentityKeys.ed25519];
const session = new olm.Session();
if (contentInitializationInfo.oneTimeKey) {
session.create_outbound(
contentAccount,
contentIdentityKeys.curve25519,
contentIdentityKeys.ed25519,
contentInitializationInfo.prekey,
contentInitializationInfo.prekeySignature,
contentInitializationInfo.oneTimeKey,
);
} else {
session.create_outbound_without_otk(
contentAccount,
contentIdentityKeys.curve25519,
contentIdentityKeys.ed25519,
contentInitializationInfo.prekey,
contentInitializationInfo.prekeySignature,
);
}
const initialEncryptedData = session.encrypt(
JSON.stringify(initialEncryptedMessageContent),
);
const newSessionVersion = existingSession ? existingSession.version + 1 : 1;
contentSessions[contentIdentityKeys.ed25519] = {
session,
version: newSessionVersion,
};
await persistCryptoStore();
const encryptedData: EncryptedData = {
message: initialEncryptedData.body,
messageType: initialEncryptedData.type,
sessionVersion: newSessionVersion,
};
return { encryptedData, sessionVersion: newSessionVersion };
},
async isContentSessionInitialized(deviceID: string) {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
return !!cryptoStore.contentSessions[deviceID];
},
async notificationsOutboundSessionCreator(
deviceID: string,
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
): Promise<EncryptedData> {
const dataPersistenceKey = getOlmDataKeyForDeviceID(deviceID);
const dataEncryptionKeyDBLabel =
getOlmEncryptionKeyDBLabelForDeviceID(deviceID);
return createAndPersistNotificationsOutboundSession(
notificationsIdentityKeys,
notificationsInitializationInfo,
dataPersistenceKey,
dataEncryptionKeyDBLabel,
);
},
async isDeviceNotificationsSessionInitialized(deviceID: string) {
const dataPersistenceKey = getOlmDataKeyForDeviceID(deviceID);
const dataEncryptionKeyDBLabel =
getOlmEncryptionKeyDBLabelForDeviceID(deviceID);
const allKeys = await localforage.keys();
const allKeysSet = new Set(allKeys);
return (
allKeysSet.has(dataPersistenceKey) &&
allKeysSet.has(dataEncryptionKeyDBLabel)
);
},
async isNotificationsSessionInitializedWithDevices(
deviceIDs: $ReadOnlyArray<string>,
) {
const allKeys = await localforage.keys();
const allKeysSet = new Set(allKeys);
const deviceInfoPairs = deviceIDs.map(deviceID => {
const dataPersistenceKey = getOlmDataKeyForDeviceID(deviceID);
const dataEncryptionKeyDBLabel =
getOlmEncryptionKeyDBLabelForDeviceID(deviceID);
return [
deviceID,
allKeysSet.has(dataPersistenceKey) &&
allKeysSet.has(dataEncryptionKeyDBLabel),
];
});
return Object.fromEntries(deviceInfoPairs);
},
async keyserverNotificationsSessionCreator(
cookie: ?string,
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
keyserverID: string,
): Promise<string> {
const platformDetails = getPlatformDetails();
if (!platformDetails) {
throw new Error('Worker not initialized');
}
const { notifsOlmDataContentKey, notifsOlmDataEncryptionKeyDBLabel } =
getNotifsPersistenceKeys(cookie, keyserverID, platformDetails);
const { message } = await createAndPersistNotificationsOutboundSession(
notificationsIdentityKeys,
notificationsInitializationInfo,
notifsOlmDataContentKey,
notifsOlmDataEncryptionKeyDBLabel,
);
return message;
},
async reassignNotificationsSession(
prevCookie: ?string,
newCookie: ?string,
keyserverID: string,
): Promise<void> {
const platformDetails = getPlatformDetails();
if (!platformDetails) {
throw new Error('Worker not initialized');
}
const prevPersistenceKeys = getNotifsPersistenceKeys(
prevCookie,
keyserverID,
platformDetails,
);
const newPersistenceKeys = getNotifsPersistenceKeys(
newCookie,
keyserverID,
platformDetails,
);
await Promise.all([
reassignLocalForageItem(
prevPersistenceKeys.notifsOlmDataContentKey,
newPersistenceKeys.notifsOlmDataContentKey,
),
reassignLocalForageItem(
prevPersistenceKeys.notifsOlmDataEncryptionKeyDBLabel,
newPersistenceKeys.notifsOlmDataEncryptionKeyDBLabel,
),
]);
},
async getOneTimeKeys(numberOfKeys: number): Promise<OneTimeKeysResultValues> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
const notifsCryptoAccount = await getNotifsCryptoAccount();
const contentOneTimeKeys = getAccountOneTimeKeys(
contentAccount,
numberOfKeys,
);
contentAccount.mark_keys_as_published();
const notificationsOneTimeKeys = getAccountOneTimeKeys(
notifsCryptoAccount.notificationAccount,
numberOfKeys,
);
notifsCryptoAccount.notificationAccount.mark_keys_as_published();
await persistCryptoStore(notifsCryptoAccount);
return { contentOneTimeKeys, notificationsOneTimeKeys };
},
async validateAndUploadPrekeys(authMetadata): Promise<void> {
const { userID, deviceID, accessToken } = authMetadata;
if (!userID || !deviceID || !accessToken) {
return;
}
const identityClient = getIdentityClient();
if (!identityClient) {
throw new Error('Identity client not initialized');
}
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
const notifsCryptoAccount = await getNotifsCryptoAccount();
// Content and notification accounts' keys are always rotated at the same
// time so we only need to check one of them.
if (shouldRotatePrekey(contentAccount)) {
contentAccount.generate_prekey();
notifsCryptoAccount.notificationAccount.generate_prekey();
}
if (shouldForgetPrekey(contentAccount)) {
contentAccount.forget_old_prekey();
notifsCryptoAccount.notificationAccount.forget_old_prekey();
}
await persistCryptoStore(notifsCryptoAccount);
if (!contentAccount.unpublished_prekey()) {
return;
}
const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } =
getAccountPrekeysSet(notifsCryptoAccount.notificationAccount);
const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } =
getAccountPrekeysSet(contentAccount);
if (!notifPrekeySignature || !contentPrekeySignature) {
throw new Error('Prekey signature is missing');
}
await identityClient.publishWebPrekeys({
contentPrekey,
contentPrekeySignature,
notifPrekey,
notifPrekeySignature,
});
contentAccount.mark_prekey_as_published();
notifsCryptoAccount.notificationAccount.mark_prekey_as_published();
await persistCryptoStore(notifsCryptoAccount);
},
async signMessage(message: string): Promise<string> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
return contentAccount.sign(message);
},
async verifyMessage(
message: string,
signature: string,
signingPublicKey: string,
): Promise<boolean> {
if (!olmUtility) {
throw new Error('Crypto account not initialized');
}
try {
olmUtility.ed25519_verify(signingPublicKey, message, signature);
return true;
} catch (err) {
const isSignatureInvalid =
getMessageForException(err)?.includes('BAD_MESSAGE_MAC');
if (isSignatureInvalid) {
return false;
}
throw err;
}
},
async markPrekeysAsPublished(): Promise<void> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
const { contentAccount } = cryptoStore;
const notifsCryptoAccount = await getNotifsCryptoAccount();
contentAccount.mark_prekey_as_published();
notifsCryptoAccount.notificationAccount.mark_prekey_as_published();
await persistCryptoStore(notifsCryptoAccount);
},
};
export {
clearCryptoStore,
processAppOlmApiRequest,
getSignedIdentityKeysBlob,
getNewDeviceKeyUpload,
- getExistingDeviceKeyUpload,
};

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 9:09 AM (16 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2690692
Default Alt Text
(76 KB)

Event Timeline