@@ -18,6 +18,7 @@ import {
18
18
} from '@opentdf/client' ;
19
19
import { CLIError , Level , log } from './logger.js' ;
20
20
import { webcrypto } from 'crypto' ;
21
+ import { attributeFQNsAsValues } from '@opentdf/client/nano' ;
21
22
22
23
type AuthToProcess = {
23
24
auth ?: string ;
@@ -91,6 +92,13 @@ async function processAuth({
91
92
} ;
92
93
}
93
94
95
+ const rstrip = ( str : string , suffix = ' ' ) : string => {
96
+ while ( str && suffix && str . endsWith ( suffix ) ) {
97
+ str = str . slice ( 0 , - suffix . length ) ;
98
+ }
99
+ return str ;
100
+ } ;
101
+
94
102
type AnyNanoClient = NanoTDFClient | NanoTDFDatasetClient ;
95
103
96
104
function addParams ( client : AnyNanoClient , argv : Partial < mainArgs > ) {
@@ -120,6 +128,9 @@ async function tdf3EncryptParamsFor(argv: Partial<mainArgs>): Promise<EncryptPar
120
128
if ( argv . mimeType ?. length ) {
121
129
c . setMimeType ( argv . mimeType ) ;
122
130
}
131
+ if ( argv . autoconfigure ) {
132
+ c . withAutoconfigure ( ) ;
133
+ }
123
134
// use offline mode, we do not have upsert for v2
124
135
c . setOffline ( ) ;
125
136
// FIXME TODO must call file.close() after we are done
@@ -171,53 +182,66 @@ export const handleArgs = (args: string[]) => {
171
182
// AUTH OPTIONS
172
183
. option ( 'kasEndpoint' , {
173
184
demandOption : true ,
174
- group : 'KAS Configuration ' ,
185
+ group : 'Server Endpoints: ' ,
175
186
type : 'string' ,
176
187
description : 'URL to non-default KAS instance (https://mykas.net)' ,
177
188
} )
178
189
. option ( 'oidcEndpoint' , {
179
190
demandOption : true ,
180
- group : 'OIDC IdP Endpoint :' ,
191
+ group : 'Server Endpoints :' ,
181
192
type : 'string' ,
182
193
description : 'URL to non-default OIDC IdP (https://myidp.net)' ,
183
194
} )
195
+ . option ( 'policyEndpoint' , {
196
+ group : 'Server Endpoints:' ,
197
+ type : 'string' ,
198
+ description : 'Attribute and key grant service endpoint' ,
199
+ } )
184
200
. option ( 'allowList' , {
185
- group : 'KAS Configuration ' ,
201
+ group : 'Security: ' ,
186
202
desc : 'allowed KAS origins, comma separated; defaults to [kasEndpoint]' ,
187
203
type : 'string' ,
188
204
validate : ( attributes : string ) => attributes . split ( ',' ) ,
189
205
} )
190
- . boolean ( 'ignoreAllowList' )
206
+ . option ( 'ignoreAllowList' , {
207
+ group : 'Security:' ,
208
+ desc : 'disable KAS allowlist feature for decrypt' ,
209
+ type : 'boolean' ,
210
+ } )
191
211
. option ( 'auth' , {
192
- group : 'Authentication :' ,
212
+ group : 'OAuth and OIDC :' ,
193
213
type : 'string' ,
194
- description : 'Authentication string (<clientId>:<clientSecret>)' ,
214
+ description : 'Combined OAuth Client Credentials (<clientId>:<clientSecret>)' ,
215
+ } )
216
+ . option ( 'dpop' , {
217
+ group : 'Security:' ,
218
+ desc : 'Use DPoP for token binding' ,
219
+ type : 'boolean' ,
195
220
} )
196
- . boolean ( 'dpop' )
197
221
. implies ( 'auth' , '--no-clientId' )
198
222
. implies ( 'auth' , '--no-clientSecret' )
199
223
200
224
. option ( 'clientId' , {
201
- group : 'OIDC client credentials ' ,
225
+ group : 'OAuth and OIDC: ' ,
202
226
alias : 'cid' ,
203
227
type : 'string' ,
204
- description : 'IdP-issued Client ID' ,
228
+ description : 'OAuth Client Credentials: IdP-issued Client ID' ,
205
229
} )
206
230
. implies ( 'clientId' , 'clientSecret' )
207
231
208
232
. option ( 'clientSecret' , {
209
- group : 'OIDC client credentials ' ,
233
+ group : 'OAuth and OIDC: ' ,
210
234
alias : 'cs' ,
211
235
type : 'string' ,
212
- description : 'IdP-issued Client Secret' ,
236
+ description : 'OAuth Client Credentials: IdP-issued Client Secret' ,
213
237
} )
214
238
. implies ( 'clientSecret' , 'clientId' )
215
239
216
240
. option ( 'exchangeToken' , {
217
- group : 'Token from trusted external IdP to exchange for Virtru auth ' ,
241
+ group : 'OAuth and OIDC: ' ,
218
242
alias : 'et' ,
219
243
type : 'string' ,
220
- description : 'Token issued by trusted external IdP' ,
244
+ description : 'OAuth Token Exchange: Token issued by trusted external IdP' ,
221
245
} )
222
246
. implies ( 'exchangeToken' , 'clientId' )
223
247
@@ -229,39 +253,45 @@ export const handleArgs = (args: string[]) => {
229
253
// Policy, encryption, and container options
230
254
. options ( {
231
255
attributes : {
232
- group : 'Encrypt Options' ,
256
+ group : 'Encrypt Options: ' ,
233
257
desc : 'Data attributes for the policy' ,
234
258
type : 'string' ,
235
259
default : '' ,
236
260
validate : ( attributes : string ) => attributes . split ( ',' ) ,
237
261
} ,
262
+ autoconfigure : {
263
+ group : 'Encrypt Options:' ,
264
+ desc : 'Enable automatic configuration from attributes using policy service' ,
265
+ type : 'boolean' ,
266
+ default : false ,
267
+ } ,
238
268
containerType : {
239
- group : 'Encrypt Options' ,
269
+ group : 'Encrypt Options: ' ,
240
270
alias : 't' ,
241
271
choices : containerTypes ,
242
272
description : 'Container format' ,
243
273
default : 'nano' ,
244
274
} ,
245
275
policyBinding : {
246
- group : 'Encrypt Options' ,
276
+ group : 'Encrypt Options: ' ,
247
277
choices : bindingTypes ,
248
278
description : 'Policy Binding Type (nano only)' ,
249
279
default : 'gmac' ,
250
280
} ,
251
281
mimeType : {
252
- group : 'Encrypt Options' ,
282
+ group : 'Encrypt Options: ' ,
253
283
desc : 'Mime type for the plain text file (only supported for ztdf)' ,
254
284
type : 'string' ,
255
285
default : '' ,
256
286
} ,
257
287
userId : {
258
- group : 'Encrypt Options' ,
288
+ group : 'Encrypt Options: ' ,
259
289
type : 'string' ,
260
290
description : 'Owner email address' ,
261
291
} ,
262
292
usersWithAccess : {
263
293
alias : 'users-with-access' ,
264
- group : 'Encrypt Options' ,
294
+ group : 'Encrypt Options: ' ,
265
295
desc : 'Add users to the policy' ,
266
296
type : 'string' ,
267
297
default : '' ,
@@ -272,12 +302,14 @@ export const handleArgs = (args: string[]) => {
272
302
// COMMANDS
273
303
. options ( {
274
304
logLevel : {
305
+ group : 'Verbosity:' ,
275
306
alias : 'log-level' ,
276
307
type : 'string' ,
277
308
default : 'info' ,
278
309
desc : 'Set logging level' ,
279
310
} ,
280
311
silent : {
312
+ group : 'Verbosity:' ,
281
313
type : 'boolean' ,
282
314
default : false ,
283
315
desc : 'Disable logging' ,
@@ -287,6 +319,39 @@ export const handleArgs = (args: string[]) => {
287
319
type : 'string' ,
288
320
description : 'output file' ,
289
321
} )
322
+
323
+ . command (
324
+ 'attrs' ,
325
+ 'Look up defintions of attributes' ,
326
+ ( yargs ) => {
327
+ yargs . strict ( ) ;
328
+ } ,
329
+ async ( argv ) => {
330
+ log ( 'DEBUG' , 'attribute value lookup' ) ;
331
+ const authProvider = await processAuth ( argv ) ;
332
+ const signingKey = await crypto . subtle . generateKey (
333
+ {
334
+ name : 'RSASSA-PKCS1-v1_5' ,
335
+ hash : 'SHA-256' ,
336
+ modulusLength : 2048 ,
337
+ publicExponent : new Uint8Array ( [ 0x01 , 0x00 , 0x01 ] ) ,
338
+ } ,
339
+ true ,
340
+ [ 'sign' , 'verify' ]
341
+ ) ;
342
+ authProvider . updateClientPublicKey ( signingKey ) ;
343
+ log ( 'DEBUG' , `Initialized auth provider ${ JSON . stringify ( authProvider ) } ` ) ;
344
+
345
+ const policyUrl : string = guessPolicyUrl ( argv ) ;
346
+ const defs = await attributeFQNsAsValues (
347
+ policyUrl ,
348
+ authProvider ,
349
+ ...( argv . attributes as string ) . split ( ',' )
350
+ ) ;
351
+ console . log ( JSON . stringify ( defs , null , 2 ) ) ;
352
+ }
353
+ )
354
+
290
355
. command (
291
356
'decrypt [file]' ,
292
357
'Decrypt TDF to string' ,
@@ -397,11 +462,13 @@ export const handleArgs = (args: string[]) => {
397
462
398
463
if ( 'tdf3' === argv . containerType || 'ztdf' === argv . containerType ) {
399
464
log ( 'DEBUG' , `TDF3 Client` ) ;
465
+ const policyEndpoint : string = guessPolicyUrl ( argv ) ;
400
466
const client = new TDF3Client ( {
401
467
allowedKases,
402
468
ignoreAllowList,
403
469
authProvider,
404
470
kasEndpoint,
471
+ policyEndpoint,
405
472
dpopEnabled : argv . dpop ,
406
473
} ) ;
407
474
log ( 'SILLY' , `Initialized client ${ JSON . stringify ( client ) } ` ) ;
@@ -480,3 +547,20 @@ handleArgs(hideBin(process.argv))
480
547
. catch ( ( err ) => {
481
548
console . error ( err ) ;
482
549
} ) ;
550
+
551
+ function guessPolicyUrl ( {
552
+ kasEndpoint,
553
+ policyEndpoint,
554
+ } : {
555
+ kasEndpoint : string ;
556
+ policyEndpoint ?: string ;
557
+ } ) {
558
+ let policyUrl : string ;
559
+ if ( policyEndpoint ) {
560
+ policyUrl = rstrip ( policyEndpoint , '/' ) ;
561
+ } else {
562
+ const uNoSlash = rstrip ( kasEndpoint , '/' ) ;
563
+ policyUrl = uNoSlash . endsWith ( '/kas' ) ? uNoSlash . slice ( 0 , - 4 ) : uNoSlash ;
564
+ }
565
+ return policyUrl ;
566
+ }
0 commit comments