@@ -4,6 +4,7 @@ import {platform} from './platform'
44import {
55 formatTimeDiff ,
66 generateTransmissionKey ,
7+ generateHpkeTransmissionKey ,
78 getKeeperUrl ,
89 isTwoFactorResultCode , log ,
910 normal64Bytes ,
@@ -26,7 +27,7 @@ import WssClientResponse = Push.WssClientResponse;
2627import WssConnectionRequest = Push . WssConnectionRequest ;
2728import SsoCloudResponse = SsoCloud . SsoCloudResponse ;
2829import { KeeperHttpResponse , RestCommand } from './commands'
29- import { AllowedNumbers , isAllowedNumber } from './transmissionKeys'
30+ import { AllowedEcKeyIds , AllowedMlKemKeyIds , isAllowedEcKeyId , isAllowedMlKemKeyId } from './transmissionKeys'
3031
3132export class KeeperEndpoint {
3233 private _transmissionKey ?: TransmissionKey
@@ -37,21 +38,29 @@ export class KeeperEndpoint {
3738 private onsitePrivateKey : Uint8Array | null = null
3839 private onsitePublicKey : Uint8Array | null = null
3940
40- constructor ( private options : ClientConfigurationInternal ) {
41+ private useHpkeForTransmissionKey : boolean = false
42+
43+ constructor ( private options : ClientConfigurationInternal ) {
4144 if ( options . deviceToken ) {
4245 this . deviceToken = options . deviceToken
43- }
46+ }
4447 if ( options . locale ) {
4548 this . locale = options . locale
4649 }
50+ if ( options . useHpkeForTransmissionKey ) {
51+ this . useHpkeForTransmissionKey = true
52+ }
4753 }
4854
4955 async getTransmissionKey ( ) :Promise < TransmissionKey > {
50- const deviceConfigTransmissionKeyId = this . options . deviceConfig . transmissionKeyId || 7
51- if ( ! this . _transmissionKey && isAllowedNumber ( deviceConfigTransmissionKeyId ) ) {
52- this . _transmissionKey = await generateTransmissionKey ( deviceConfigTransmissionKeyId )
56+ const DEFAULT_PROD_EC_KEY_ID = 10
57+ const DEFAULT_PROD_ML_KEM_KEY_ID = 100
58+ const deviceConfigTransmissionKeyId = this . options . deviceConfig . transmissionKeyId || DEFAULT_PROD_EC_KEY_ID
59+ const deviceConfigMlKemKeyId = this . options . deviceConfig . mlKemPublicKeyId || DEFAULT_PROD_ML_KEM_KEY_ID
60+ if ( ! this . _transmissionKey && isAllowedEcKeyId ( deviceConfigTransmissionKeyId ) && isAllowedMlKemKeyId ( deviceConfigMlKemKeyId ) ) {
61+ this . _transmissionKey = await generateTransmissionKey ( deviceConfigTransmissionKeyId , deviceConfigMlKemKeyId )
5362 } else if ( ! this . _transmissionKey ) {
54- this . _transmissionKey = await generateTransmissionKey ( 7 )
63+ this . _transmissionKey = await generateTransmissionKey ( DEFAULT_PROD_EC_KEY_ID , DEFAULT_PROD_ML_KEM_KEY_ID )
5564 }
5665
5766 return this . _transmissionKey
@@ -159,7 +168,15 @@ export class KeeperEndpoint {
159168 while ( true ) {
160169 const payload = 'toBytes' in message ? message . toBytes ( ) : new Uint8Array ( )
161170 const apiVersion = message . apiVersion || 0
162- const request = await this . prepareRequest ( payload , sessionToken , apiVersion )
171+
172+ const request = await prepareApiRequest ( {
173+ payload,
174+ transmissionKey : this . _transmissionKey ,
175+ sessionToken,
176+ locale : this . locale ,
177+ apiVersion,
178+ useHpkeForTransmissionKey : this . useHpkeForTransmissionKey
179+ } )
163180 log ( `Calling REST URL: ${ this . getUrl ( message . path ) } ` , 'noCR' ) ;
164181 const startTime = Date . now ( )
165182 const response = await platform . post ( this . getUrl ( message . path ) , request )
@@ -183,9 +200,14 @@ export class KeeperEndpoint {
183200 const errorObj : KeeperError = JSON . parse ( errorMessage )
184201 switch ( errorObj . error ) {
185202 case 'key' :
186- if ( isAllowedNumber ( errorObj . key_id ! ) ) {
187- await this . updateTransmissionKey ( errorObj . key_id ! )
188- } else {
203+ if ( errorObj . qrc_ec_key_id && isAllowedEcKeyId ( errorObj . qrc_ec_key_id ) && isAllowedMlKemKeyId ( errorObj . key_id ! ) ) {
204+ // Rotate EC key and ML-KEM key
205+ await this . updateTransmissionKey ( errorObj . qrc_ec_key_id ! , errorObj . key_id ! )
206+ } else if ( errorObj . key_id && isAllowedEcKeyId ( errorObj . key_id ) && this . _transmissionKey ) {
207+ // Rotate EC key
208+ await this . updateTransmissionKey ( errorObj . key_id , this . _transmissionKey . mlKemKeyId as AllowedMlKemKeyIds )
209+ }
210+ else {
189211 throw new Error ( 'Incorrect Transmission Key ID being used.' )
190212 }
191213 continue
@@ -245,18 +267,26 @@ export class KeeperEndpoint {
245267 return platform . get ( this . getUrl ( path ) , { } )
246268 }
247269
248- public async updateTransmissionKey ( keyNumber : AllowedNumbers ) {
249- this . _transmissionKey = await generateTransmissionKey ( keyNumber )
270+ public async updateTransmissionKey ( ecKeyId : AllowedEcKeyIds , mlKemKeyId : AllowedMlKemKeyIds ) {
271+ this . _transmissionKey = await generateTransmissionKey ( ecKeyId , mlKemKeyId )
250272
251- this . options . deviceConfig . transmissionKeyId = keyNumber
273+ this . options . deviceConfig . transmissionKeyId = ecKeyId
274+ this . options . deviceConfig . mlKemPublicKeyId = mlKemKeyId
252275 if ( this . options . onDeviceConfig ) {
253276 await this . options . onDeviceConfig ( this . options . deviceConfig , this . options . host ) ;
254277 }
255278 }
256279
257280 public async prepareRequest ( payload : Uint8Array | unknown , sessionToken ?: string , apiVersion ?: number ) : Promise < Uint8Array > {
258281 this . _transmissionKey = await this . getTransmissionKey ( )
259- return prepareApiRequest ( payload , this . _transmissionKey , sessionToken , this . locale , apiVersion )
282+ return prepareApiRequest ( {
283+ payload,
284+ transmissionKey : this . _transmissionKey ,
285+ sessionToken,
286+ locale : this . locale ,
287+ apiVersion,
288+ useHpkeForTransmissionKey : this . useHpkeForTransmissionKey
289+ } )
260290 }
261291
262292 async decryptPushMessage ( pushMessageData : Uint8Array ) : Promise < WssClientResponse > {
@@ -281,7 +311,12 @@ export class KeeperEndpoint {
281311 "idpSessionId" : idpSessionId ,
282312 "username" : username
283313 }
284- const request = await prepareApiRequest ( SsoCloud . SsoCloudRequest . encode ( payload ) . finish ( ) , this . _transmissionKey , undefined , this . locale )
314+ const request = await prepareApiRequest ( {
315+ payload : SsoCloud . SsoCloudRequest . encode ( payload ) . finish ( ) ,
316+ transmissionKey : this . _transmissionKey ,
317+ locale : this . locale ,
318+ useHpkeForTransmissionKey : this . useHpkeForTransmissionKey
319+ } )
285320 return webSafe64FromBytes ( request )
286321 }
287322
@@ -325,11 +360,32 @@ export async function getPushConnectionRequest(messageSessionUid: Uint8Array, tr
325360 deviceTimeStamp : new Date ( ) . getTime ( )
326361 } )
327362 const connectionRequestBytes = WssConnectionRequest . encode ( connectionRequest ) . finish ( )
328- const apiRequest = await prepareApiRequest ( connectionRequestBytes , transmissionKey , undefined , locale )
363+ const apiRequest = await prepareApiRequest ( {
364+ payload : connectionRequestBytes ,
365+ transmissionKey,
366+ locale,
367+ useHpkeForTransmissionKey : false // HPKE not currently supported for push
368+ } )
329369 return webSafe64FromBytes ( apiRequest )
330370}
331371
332- export async function prepareApiRequest ( payload : Uint8Array | unknown , transmissionKey : TransmissionKey , sessionToken ?: string , locale ?: string , apiVersion ?: number ) : Promise < Uint8Array > {
372+ type PrepareApiRequestParams = {
373+ payload : Uint8Array | unknown ,
374+ transmissionKey : TransmissionKey ,
375+ sessionToken ?: string ,
376+ locale ?: string ,
377+ apiVersion ?: number
378+ useHpkeForTransmissionKey ?: boolean
379+ }
380+
381+ export async function prepareApiRequest ( {
382+ payload,
383+ transmissionKey,
384+ sessionToken,
385+ locale,
386+ apiVersion,
387+ useHpkeForTransmissionKey
388+ } : PrepareApiRequestParams ) : Promise < Uint8Array > {
333389 const requestPayload = ApiRequestPayload . create ( )
334390 if ( payload ) {
335391 requestPayload . payload = payload instanceof Uint8Array
@@ -340,14 +396,31 @@ export async function prepareApiRequest(payload: Uint8Array | unknown, transmiss
340396 requestPayload . encryptedSessionToken = normal64Bytes ( sessionToken ) ;
341397 }
342398 requestPayload . apiVersion = apiVersion || 0
343- let requestPayloadBytes = ApiRequestPayload . encode ( requestPayload ) . finish ( )
344- let encryptedRequestPayload = await platform . aesGcmEncrypt ( requestPayloadBytes , transmissionKey . key )
345- let apiRequest = ApiRequest . create ( {
346- encryptedTransmissionKey : transmissionKey . encryptedKey ,
347- encryptedPayload : encryptedRequestPayload ,
348- publicKeyId : transmissionKey . publicKeyId ,
349- locale : locale || 'en_US'
350- } )
399+ const requestPayloadBytes = ApiRequestPayload . encode ( requestPayload ) . finish ( )
400+ const encryptedRequestPayload = await platform . aesGcmEncrypt ( requestPayloadBytes , transmissionKey . key )
401+ let apiRequest : Authentication . IApiRequest
402+
403+ if ( useHpkeForTransmissionKey ) {
404+ const hpkeTransmissionKey = await generateHpkeTransmissionKey (
405+ transmissionKey ,
406+ true // use optional data
407+ )
408+ apiRequest = ApiRequest . create ( {
409+ qrcMessageKey : hpkeTransmissionKey . qrcMessageKey ,
410+ encryptedPayload : encryptedRequestPayload ,
411+ publicKeyId : hpkeTransmissionKey . mlKemKeyId ,
412+ encryptedTransmissionKey : hpkeTransmissionKey . optionalData || null ,
413+ locale : locale || 'en_US'
414+ } ) ;
415+ } else {
416+ apiRequest = ApiRequest . create ( {
417+ encryptedTransmissionKey : transmissionKey . ecEncryptedKey ,
418+ encryptedPayload : encryptedRequestPayload ,
419+ publicKeyId : transmissionKey . ecKeyId ,
420+ locale : locale || 'en_US'
421+ } )
422+ }
423+
351424 return ApiRequest . encode ( apiRequest ) . finish ( )
352425}
353426
0 commit comments