Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3509890
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
76 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment