From 463c79126679fb8f51dd55355753171aafc72215 Mon Sep 17 00:00:00 2001 From: Tyler Carson Date: Mon, 29 Apr 2024 15:33:50 -0700 Subject: [PATCH] Add apiVersion parameter to prepareApiRequest, create proto messages (#50) - update validate_master_password to use apiVersion=1 to fix mitm attack vuln --- keeperapi/package-lock.json | 4 ++-- keeperapi/package.json | 2 +- keeperapi/src/endpoint.ts | 10 ++++++---- keeperapi/src/restMessages.ts | 27 +++++++++++++++++---------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/keeperapi/package-lock.json b/keeperapi/package-lock.json index b6af805..0eb8472 100644 --- a/keeperapi/package-lock.json +++ b/keeperapi/package-lock.json @@ -1,12 +1,12 @@ { "name": "@keeper-security/keeperapi", - "version": "16.0.58", + "version": "16.0.59", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@keeper-security/keeperapi", - "version": "16.0.58", + "version": "16.0.59", "license": "ISC", "dependencies": { "asmcrypto.js": "^2.3.2", diff --git a/keeperapi/package.json b/keeperapi/package.json index 1cfb785..ef5728c 100644 --- a/keeperapi/package.json +++ b/keeperapi/package.json @@ -1,7 +1,7 @@ { "name": "@keeper-security/keeperapi", "description": "Keeper API Javascript SDK", - "version": "16.0.58", + "version": "16.0.59", "browser": "dist/index.es.js", "main": "dist/index.cjs.js", "types": "dist/node/index.d.ts", diff --git a/keeperapi/src/endpoint.ts b/keeperapi/src/endpoint.ts index a1d9892..e128edd 100644 --- a/keeperapi/src/endpoint.ts +++ b/keeperapi/src/endpoint.ts @@ -158,7 +158,8 @@ export class KeeperEndpoint { this._transmissionKey = await this.getTransmissionKey() while (true) { const payload = 'toBytes' in message ? message.toBytes() : new Uint8Array() - const request = await this.prepareRequest(payload, sessionToken) + const apiVersion = message.apiVersion || 0 + const request = await this.prepareRequest(payload, sessionToken, apiVersion) log(`Calling REST URL: ${this.getUrl(message.path)}`, 'noCR'); const startTime = Date.now() const response = await platform.post(this.getUrl(message.path), request) @@ -253,9 +254,9 @@ export class KeeperEndpoint { } } - public async prepareRequest(payload: Uint8Array | unknown, sessionToken?: string): Promise { + public async prepareRequest(payload: Uint8Array | unknown, sessionToken?: string, apiVersion?: number): Promise { this._transmissionKey = await this.getTransmissionKey() - return prepareApiRequest(payload, this._transmissionKey, sessionToken, this.locale) + return prepareApiRequest(payload, this._transmissionKey, sessionToken, this.locale, apiVersion) } async decryptPushMessage(pushMessageData: Uint8Array): Promise { @@ -329,7 +330,7 @@ export async function getPushConnectionRequest(messageSessionUid: Uint8Array, tr return webSafe64FromBytes(apiRequest) } -export async function prepareApiRequest(payload: Uint8Array | unknown, transmissionKey: TransmissionKey, sessionToken?: string, locale?: string): Promise { +export async function prepareApiRequest(payload: Uint8Array | unknown, transmissionKey: TransmissionKey, sessionToken?: string, locale?: string, apiVersion?: number): Promise { const requestPayload = ApiRequestPayload.create() if (payload) { requestPayload.payload = payload instanceof Uint8Array @@ -339,6 +340,7 @@ export async function prepareApiRequest(payload: Uint8Array | unknown, transmiss if (sessionToken) { requestPayload.encryptedSessionToken = normal64Bytes(sessionToken); } + requestPayload.apiVersion = apiVersion || 0 let requestPayloadBytes = ApiRequestPayload.encode(requestPayload).finish() let encryptedRequestPayload = await platform.aesGcmEncrypt(requestPayloadBytes, transmissionKey.key) let apiRequest = ApiRequest.create({ diff --git a/keeperapi/src/restMessages.ts b/keeperapi/src/restMessages.ts index 2cd62e9..bc2010b 100644 --- a/keeperapi/src/restMessages.ts +++ b/keeperapi/src/restMessages.ts @@ -19,17 +19,20 @@ export type NN = Required<{ [prop in keyof T]: NonNullable }> export type RestInMessage = { path: string toBytes(): Uint8Array + apiVersion?: number } export type RestOutMessage = { path: string fromBytes(data: Uint8Array): TOut + apiVersion?: number } export type RestMessage = RestInMessage & RestOutMessage export type RestActionMessage = { path: string + apiVersion?: number } type encoderClass = { @@ -40,32 +43,36 @@ type decoderClass = { decode: (reader: Uint8Array, length?: number) => T } -const createInMessage = (data: TIn, path: string, encoder: encoderClass): RestInMessage => ({ +const createInMessage = (data: TIn, path: string, encoder: encoderClass, apiVersion?: number): RestInMessage => ({ path: path, toBytes(): Uint8Array { return encoder.encode(data).finish() - } + }, + apiVersion }) -const createOutMessage = (path: string, decoder: decoderClass): RestOutMessage => ({ +const createOutMessage = (path: string, decoder: decoderClass, apiVersion?: number): RestOutMessage => ({ path: path, fromBytes(data: Uint8Array): TOut { return decoder.decode(data) - } + }, + apiVersion }) -const createMessage = (data: TIn, path: string, encoder: encoderClass, decoder: decoderClass): RestMessage> => ({ +const createMessage = (data: TIn, path: string, encoder: encoderClass, decoder: decoderClass, apiVersion?: number): RestMessage> => ({ path: path, toBytes(): Uint8Array { return encoder.encode(data).finish() }, fromBytes(data: Uint8Array): NN { return >decoder.decode(data) - } + }, + apiVersion }) -const createActionMessage = (path: string): RestActionMessage => ({ - path: path +const createActionMessage = (path: string, apiVersion?: number): RestActionMessage => ({ + path: path, + apiVersion }) // new login @@ -142,8 +149,8 @@ export const requestDeviceAdminApprovalMessage = (data: Authentication.IDeviceVe export const requestSaltAndIterations = (): RestOutMessage => createOutMessage('authentication/get_salt_and_iterations', Authentication.Salt) -export const validateMasterPasswordMessage = (data: Authentication.IMasterPasswordReentryRequest): RestInMessage => - createInMessage(data, 'authentication/validate_master_password', Authentication.MasterPasswordReentryRequest) +export const validateMasterPasswordMessage = (data: Authentication.IMasterPasswordReentryRequest): RestMessage => + createMessage(data, 'authentication/validate_master_password', Authentication.MasterPasswordReentryRequest, Authentication.MasterPasswordReentryResponse, 1) export const startLoginMessageFromSessionToken = (data: Authentication.IStartLoginRequest): RestMessage> => createMessage(data, 'authentication/login_from_existing_session_token', Authentication.StartLoginRequest, Authentication.LoginResponse)