Skip to content

Commit

Permalink
Add apiVersion parameter to prepareApiRequest, create proto messages (#…
Browse files Browse the repository at this point in the history
…50)

- update validate_master_password to use apiVersion=1 to fix mitm attack vuln
  • Loading branch information
tylerccarson authored Apr 29, 2024
1 parent 6765b5c commit 463c791
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 17 deletions.
4 changes: 2 additions & 2 deletions keeperapi/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion keeperapi/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
10 changes: 6 additions & 4 deletions keeperapi/src/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -253,9 +254,9 @@ export class KeeperEndpoint {
}
}

public async prepareRequest(payload: Uint8Array | unknown, sessionToken?: string): Promise<Uint8Array> {
public async prepareRequest(payload: Uint8Array | unknown, sessionToken?: string, apiVersion?: number): Promise<Uint8Array> {
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<WssClientResponse> {
Expand Down Expand Up @@ -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<Uint8Array> {
export async function prepareApiRequest(payload: Uint8Array | unknown, transmissionKey: TransmissionKey, sessionToken?: string, locale?: string, apiVersion?: number): Promise<Uint8Array> {
const requestPayload = ApiRequestPayload.create()
if (payload) {
requestPayload.payload = payload instanceof Uint8Array
Expand All @@ -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({
Expand Down
27 changes: 17 additions & 10 deletions keeperapi/src/restMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ export type NN<T> = Required<{ [prop in keyof T]: NonNullable<T[prop]> }>
export type RestInMessage<TIn> = {
path: string
toBytes(): Uint8Array
apiVersion?: number
}

export type RestOutMessage<TOut> = {
path: string
fromBytes(data: Uint8Array): TOut
apiVersion?: number
}

export type RestMessage<TIn, TOut> = RestInMessage<TIn> & RestOutMessage<TOut>

export type RestActionMessage = {
path: string
apiVersion?: number
}

type encoderClass<T> = {
Expand All @@ -40,32 +43,36 @@ type decoderClass<T> = {
decode: (reader: Uint8Array, length?: number) => T
}

const createInMessage = <TIn>(data: TIn, path: string, encoder: encoderClass<TIn>): RestInMessage<TIn> => ({
const createInMessage = <TIn>(data: TIn, path: string, encoder: encoderClass<TIn>, apiVersion?: number): RestInMessage<TIn> => ({
path: path,
toBytes(): Uint8Array {
return encoder.encode(data).finish()
}
},
apiVersion
})

const createOutMessage = <TOut>(path: string, decoder: decoderClass<TOut>): RestOutMessage<TOut> => ({
const createOutMessage = <TOut>(path: string, decoder: decoderClass<TOut>, apiVersion?: number): RestOutMessage<TOut> => ({
path: path,
fromBytes(data: Uint8Array): TOut {
return decoder.decode(data)
}
},
apiVersion
})

const createMessage = <TIn, TOut>(data: TIn, path: string, encoder: encoderClass<TIn>, decoder: decoderClass<TOut>): RestMessage<TIn, NN<TOut>> => ({
const createMessage = <TIn, TOut>(data: TIn, path: string, encoder: encoderClass<TIn>, decoder: decoderClass<TOut>, apiVersion?: number): RestMessage<TIn, NN<TOut>> => ({
path: path,
toBytes(): Uint8Array {
return encoder.encode(data).finish()
},
fromBytes(data: Uint8Array): NN<TOut> {
return <NN<TOut>>decoder.decode(data)
}
},
apiVersion
})

const createActionMessage = (path: string): RestActionMessage => ({
path: path
const createActionMessage = (path: string, apiVersion?: number): RestActionMessage => ({
path: path,
apiVersion
})

// new login
Expand Down Expand Up @@ -142,8 +149,8 @@ export const requestDeviceAdminApprovalMessage = (data: Authentication.IDeviceVe
export const requestSaltAndIterations = (): RestOutMessage<Authentication.Salt> =>
createOutMessage('authentication/get_salt_and_iterations', Authentication.Salt)

export const validateMasterPasswordMessage = (data: Authentication.IMasterPasswordReentryRequest): RestInMessage<Authentication.IMasterPasswordReentryRequest> =>
createInMessage(data, 'authentication/validate_master_password', Authentication.MasterPasswordReentryRequest)
export const validateMasterPasswordMessage = (data: Authentication.IMasterPasswordReentryRequest): RestMessage<Authentication.IMasterPasswordReentryRequest, Authentication.IMasterPasswordReentryResponse> =>
createMessage(data, 'authentication/validate_master_password', Authentication.MasterPasswordReentryRequest, Authentication.MasterPasswordReentryResponse, 1)

export const startLoginMessageFromSessionToken = (data: Authentication.IStartLoginRequest): RestMessage<Authentication.IStartLoginRequest, NN<Authentication.ILoginResponse>> =>
createMessage(data, 'authentication/login_from_existing_session_token', Authentication.StartLoginRequest, Authentication.LoginResponse)
Expand Down

0 comments on commit 463c791

Please sign in to comment.