11import { type AuthProvider } from './auth/auth.js' ;
2- import {
3- ConfigurationError ,
4- InvalidFileError ,
5- NetworkError ,
6- PermissionDeniedError ,
7- ServiceError ,
8- UnauthenticatedError ,
9- } from './errors.js' ;
10- import { pemToCryptoPublicKey , validateSecureUrl } from './utils.js' ;
2+ import { ServiceError } from './errors.js' ;
3+ import { RewrapResponse } from './platform/kas/kas_pb.js' ;
4+ import { getPlatformUrlFromKasEndpoint , validateSecureUrl } from './utils.js' ;
5+
6+ import { fetchKeyAccessServers as fetchKeyAccessServersRpc } from './access/access-rpc.js' ;
7+ import { fetchKeyAccessServers as fetchKeyAccessServersLegacy } from './access/access-fetch.js' ;
8+ import { fetchWrappedKey as fetchWrappedKeysRpc } from './access/access-rpc.js' ;
9+ import { fetchWrappedKey as fetchWrappedKeysLegacy } from './access/access-fetch.js' ;
10+ import { fetchKasPubKey as fetchKasPubKeyRpc } from './access/access-rpc.js' ;
11+ import { fetchKasPubKey as fetchKasPubKeyLegacy } from './access/access-fetch.js' ;
1112
1213export type RewrapRequest = {
1314 signedRequestToken : string ;
1415} ;
1516
16- export type RewrapResponse = {
17- metadata : Record < string , unknown > ;
18- entityWrappedKey : string ;
19- sessionPublicKey : string ;
20- schemaVersion : string ;
21- } ;
22-
2317/**
2418 * Get a rewrapped access key to the document, if possible
2519 * @param url Key access server rewrap endpoint
@@ -29,59 +23,20 @@ export type RewrapResponse = {
2923 */
3024export async function fetchWrappedKey (
3125 url : string ,
32- requestBody : RewrapRequest ,
33- authProvider : AuthProvider ,
34- clientVersion : string
26+ signedRequestToken : string ,
27+ authProvider : AuthProvider
3528) : Promise < RewrapResponse > {
36- const req = await authProvider . withCreds ( {
37- url,
38- method : 'POST' ,
39- headers : {
40- 'Content-Type' : 'application/json' ,
41- } ,
42- body : JSON . stringify ( requestBody ) ,
43- } ) ;
44-
45- let response : Response ;
46-
47- try {
48- response = await fetch ( req . url , {
49- method : req . method ,
50- mode : 'cors' , // no-cors, *cors, same-origin
51- cache : 'no-cache' , // *default, no-cache, reload, force-cache, only-if-cached
52- credentials : 'same-origin' , // include, *same-origin, omit
53- headers : req . headers ,
54- redirect : 'follow' , // manual, *follow, error
55- referrerPolicy : 'no-referrer' , // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
56- body : req . body as BodyInit ,
57- } ) ;
58- } catch ( e ) {
59- throw new NetworkError ( `unable to fetch wrapped key from [${ url } ]` , e ) ;
60- }
61-
62- if ( ! response . ok ) {
63- switch ( response . status ) {
64- case 400 :
65- throw new InvalidFileError (
66- `400 for [${ req . url } ]: rewrap bad request [${ await response . text ( ) } ]`
67- ) ;
68- case 401 :
69- throw new UnauthenticatedError ( `401 for [${ req . url } ]; rewrap auth failure` ) ;
70- case 403 :
71- throw new PermissionDeniedError ( `403 for [${ req . url } ]; rewrap permission denied` ) ;
72- default :
73- if ( response . status >= 500 ) {
74- throw new ServiceError (
75- `${ response . status } for [${ req . url } ]: rewrap failure due to service error [${ await response . text ( ) } ]`
76- ) ;
77- }
78- throw new NetworkError (
79- `${ req . method } ${ req . url } => ${ response . status } ${ response . statusText } `
80- ) ;
81- }
82- }
83-
84- return response . json ( ) ;
29+ const platformUrl = getPlatformUrlFromKasEndpoint ( url ) ;
30+
31+ return await tryPromisesUntilFirstSuccess (
32+ ( ) => fetchWrappedKeysRpc ( platformUrl , signedRequestToken , authProvider ) ,
33+ ( ) =>
34+ fetchWrappedKeysLegacy (
35+ url ,
36+ { signedRequestToken } ,
37+ authProvider
38+ ) as unknown as Promise < RewrapResponse >
39+ ) ;
8540}
8641
8742export type KasPublicKeyAlgorithm = 'ec:secp256r1' | 'rsa:2048' ;
@@ -145,7 +100,7 @@ export type KasPublicKeyInfo = {
145100 key : Promise < CryptoKey > ;
146101} ;
147102
148- async function noteInvalidPublicKey ( url : URL , r : Promise < CryptoKey > ) : Promise < CryptoKey > {
103+ export async function noteInvalidPublicKey ( url : URL , r : Promise < CryptoKey > ) : Promise < CryptoKey > {
149104 try {
150105 return await r ;
151106 } catch ( e ) {
@@ -160,49 +115,10 @@ export async function fetchKeyAccessServers(
160115 platformUrl : string ,
161116 authProvider : AuthProvider
162117) : Promise < OriginAllowList > {
163- let nextOffset = 0 ;
164- const allServers = [ ] ;
165- do {
166- const req = await authProvider . withCreds ( {
167- url : `${ platformUrl } /key-access-servers?pagination.offset=${ nextOffset } ` ,
168- method : 'GET' ,
169- headers : {
170- 'Content-Type' : 'application/json' ,
171- } ,
172- } ) ;
173- let response : Response ;
174- try {
175- response = await fetch ( req . url , {
176- method : req . method ,
177- headers : req . headers ,
178- body : req . body as BodyInit ,
179- mode : 'cors' ,
180- cache : 'no-cache' ,
181- credentials : 'same-origin' ,
182- redirect : 'follow' ,
183- referrerPolicy : 'no-referrer' ,
184- } ) ;
185- } catch ( e ) {
186- throw new NetworkError ( `unable to fetch kas list from [${ req . url } ]` , e ) ;
187- }
188- // if we get an error from the kas registry, throw an error
189- if ( ! response . ok ) {
190- throw new ServiceError (
191- `unable to fetch kas list from [${ req . url } ], status: ${ response . status } `
192- ) ;
193- }
194- const { keyAccessServers = [ ] , pagination = { } } = await response . json ( ) ;
195- allServers . push ( ...keyAccessServers ) ;
196- nextOffset = pagination . nextOffset || 0 ;
197- } while ( nextOffset > 0 ) ;
198-
199- const serverUrls = allServers . map ( ( server ) => server . uri ) ;
200- // add base platform kas
201- if ( ! serverUrls . includes ( `${ platformUrl } /kas` ) ) {
202- serverUrls . push ( `${ platformUrl } /kas` ) ;
203- }
204-
205- return new OriginAllowList ( serverUrls , false ) ;
118+ return await tryPromisesUntilFirstSuccess (
119+ ( ) => fetchKeyAccessServersRpc ( platformUrl , authProvider ) ,
120+ ( ) => fetchKeyAccessServersLegacy ( platformUrl , authProvider )
121+ ) ;
206122}
207123
208124/**
@@ -217,64 +133,10 @@ export async function fetchKasPubKey(
217133 kasEndpoint : string ,
218134 algorithm ?: KasPublicKeyAlgorithm
219135) : Promise < KasPublicKeyInfo > {
220- if ( ! kasEndpoint ) {
221- throw new ConfigurationError ( 'KAS definition not found' ) ;
222- }
223- // Logs insecure KAS. Secure is enforced in constructor
224- validateSecureUrl ( kasEndpoint ) ;
225-
226- // Parse kasEndpoint to URL, then append to its path and update its query parameters
227- let pkUrlV2 : URL ;
228- try {
229- pkUrlV2 = new URL ( kasEndpoint ) ;
230- } catch ( e ) {
231- throw new ConfigurationError ( `KAS definition invalid: [${ kasEndpoint } ]` , e ) ;
232- }
233- if ( ! pkUrlV2 . pathname . endsWith ( 'kas_public_key' ) ) {
234- if ( ! pkUrlV2 . pathname . endsWith ( '/' ) ) {
235- pkUrlV2 . pathname += '/' ;
236- }
237- pkUrlV2 . pathname += 'v2/kas_public_key' ;
238- }
239- pkUrlV2 . searchParams . set ( 'algorithm' , algorithm || 'rsa:2048' ) ;
240- if ( ! pkUrlV2 . searchParams . get ( 'v' ) ) {
241- pkUrlV2 . searchParams . set ( 'v' , '2' ) ;
242- }
243-
244- let kasPubKeyResponseV2 : Response ;
245- try {
246- kasPubKeyResponseV2 = await fetch ( pkUrlV2 ) ;
247- } catch ( e ) {
248- throw new NetworkError ( `unable to fetch public key from [${ pkUrlV2 } ]` , e ) ;
249- }
250- if ( ! kasPubKeyResponseV2 . ok ) {
251- switch ( kasPubKeyResponseV2 . status ) {
252- case 404 :
253- throw new ConfigurationError ( `404 for [${ pkUrlV2 } ]` ) ;
254- case 401 :
255- throw new UnauthenticatedError ( `401 for [${ pkUrlV2 } ]` ) ;
256- case 403 :
257- throw new PermissionDeniedError ( `403 for [${ pkUrlV2 } ]` ) ;
258- default :
259- throw new NetworkError (
260- `${ pkUrlV2 } => ${ kasPubKeyResponseV2 . status } ${ kasPubKeyResponseV2 . statusText } `
261- ) ;
262- }
263- }
264- const jsonContent = await kasPubKeyResponseV2 . json ( ) ;
265- const { publicKey, kid } : KasPublicKeyInfo = jsonContent ;
266- if ( ! publicKey ) {
267- throw new NetworkError (
268- `invalid response from public key endpoint [${ JSON . stringify ( jsonContent ) } ]`
269- ) ;
270- }
271- return {
272- key : noteInvalidPublicKey ( pkUrlV2 , pemToCryptoPublicKey ( publicKey ) ) ,
273- publicKey,
274- url : kasEndpoint ,
275- algorithm : algorithm || 'rsa:2048' ,
276- ...( kid && { kid } ) ,
277- } ;
136+ return await tryPromisesUntilFirstSuccess (
137+ ( ) => fetchKasPubKeyRpc ( kasEndpoint , algorithm ) ,
138+ ( ) => fetchKasPubKeyLegacy ( kasEndpoint , algorithm )
139+ ) ;
278140}
279141
280142const origin = ( u : string ) : string => {
@@ -301,3 +163,25 @@ export class OriginAllowList {
301163 return this . origins . includes ( origin ( url ) ) ;
302164 }
303165}
166+
167+ /**
168+ * Tries two promise-returning functions in order and returns the first successful result.
169+ * If both fail, throws the error from the second.
170+ * @param first First function returning a promise to try.
171+ * @param second Second function returning a promise to try if the first fails.
172+ */
173+ async function tryPromisesUntilFirstSuccess < T > (
174+ first : ( ) => Promise < T > ,
175+ second : ( ) => Promise < T >
176+ ) : Promise < T > {
177+ try {
178+ return await first ( ) ;
179+ } catch ( e1 ) {
180+ console . info ( 'v2 request error' , e1 ) ;
181+ try {
182+ return await second ( ) ;
183+ } catch ( err ) {
184+ throw err ;
185+ }
186+ }
187+ }
0 commit comments