@coffre/core 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{access-DvwZzqRR.js → access-B1wpMzEB.js} +35 -6
- package/dist/{access-BpcBLDW9.d.ts → access-CONhKG2P.d.ts} +38 -3
- package/dist/access.d.ts +2 -2
- package/dist/access.js +2 -2
- package/dist/envelope.d.ts +2 -2
- package/dist/envelope.js +2 -2
- package/dist/identity.d.ts +50 -6
- package/dist/identity.js +170 -19
- package/dist/kek.d.ts +1 -1
- package/dist/kek.js +3 -2
- package/dist/recipe.d.ts +126 -0
- package/dist/recipe.js +281 -0
- package/dist/{registry-C0NGpxcc.d.ts → registry-Bd7hPoY4.d.ts} +14 -2
- package/dist/schemas.d.ts +13 -3
- package/dist/schemas.js +14 -2
- package/dist/{types-Mi7-Tzj9.js → types-E1Sz50n6.js} +14 -3
- package/dist/vault.d.ts +31 -4
- package/dist/vault.js +10 -1
- package/dist/workloads.d.ts +33 -7
- package/dist/workloads.js +60 -9
- package/package.json +7 -2
- package/src/access.ts +50 -6
- package/src/context.ts +24 -2
- package/src/identity/tokens.ts +9 -4
- package/src/identity/workload-token.ts +157 -12
- package/src/identity/workloads.ts +74 -10
- package/src/kek/aws-kms.ts +7 -1
- package/src/recipe.ts +309 -0
- package/src/schemas.ts +15 -0
- package/src/vault.ts +31 -4
|
@@ -370,6 +370,24 @@ function grantKind(grant) {
|
|
|
370
370
|
/** The most places a scope asked for names: far more than any credential needs, and a bound on what a request makes the app check. */
|
|
371
371
|
const MAX_SCOPE_PLACES = 1e3;
|
|
372
372
|
/**
|
|
373
|
+
* Whether a personal token's scope may name `role`: one that manages no
|
|
374
|
+
* access. A token acting as its person reaches their secrets and places,
|
|
375
|
+
* never who holds them, so it cannot hand anyone, itself included, more
|
|
376
|
+
* than it was given. A project's Owner who wants a token for all of it
|
|
377
|
+
* names `maintainer`.
|
|
378
|
+
*/
|
|
379
|
+
function delegable(role) {
|
|
380
|
+
return !roleGrants(role, "grant.manage");
|
|
381
|
+
}
|
|
382
|
+
/** The roles a personal token's scope may name, in catalogue order. */
|
|
383
|
+
const DELEGABLE_ROLES = ROLE_NAMES.filter(delegable);
|
|
384
|
+
/**
|
|
385
|
+
* The longest a personal token lasts: a week. It is for an afternoon's
|
|
386
|
+
* script or agent, and whoever needs it longer makes another, as a person,
|
|
387
|
+
* rather than keep one around.
|
|
388
|
+
*/
|
|
389
|
+
const MAX_PERSONAL_TOKEN_MINUTES = 10080;
|
|
390
|
+
/**
|
|
373
391
|
* A credential's scope, by path as the API names it, in words, each role
|
|
374
392
|
* with its places: `developer on market/dev, api/dev; viewer on market/prod`.
|
|
375
393
|
*/
|
|
@@ -426,6 +444,16 @@ function allows(holder, permission, place) {
|
|
|
426
444
|
if (instanceRoleGrants(holder.role, permission) && inScope(holder.scope, place)) return true;
|
|
427
445
|
return holdsAt(holder.grants, permission, place);
|
|
428
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Whether the credential a request came in on, a scoped token or binding,
|
|
449
|
+
* leaves `permission` at `place` to its holder: always, for a session. A
|
|
450
|
+
* personal config's owner holds its every permission by reading its parent
|
|
451
|
+
* (docs/design/forking.md), and a token of theirs is still held to the
|
|
452
|
+
* role its scope names there: a viewer's token reads it, and writes nothing.
|
|
453
|
+
*/
|
|
454
|
+
function withinCredential(holder, permission, place) {
|
|
455
|
+
return holder.credentialScope == null || holdsAt(holder.credentialScope, permission, place);
|
|
456
|
+
}
|
|
429
457
|
/** Whether a grant among `grants` covers `place` with a role that holds `permission`. */
|
|
430
458
|
function holdsAt(grants, permission, place) {
|
|
431
459
|
return grants.some((grant) => covers(grant, place) && roleGrants(grant.role, permission));
|
|
@@ -447,7 +475,7 @@ function mayManageAccess(actor, place) {
|
|
|
447
475
|
* does none of these.
|
|
448
476
|
*/
|
|
449
477
|
function runsInstance(holder) {
|
|
450
|
-
return administers(holder.role) && unscoped(holder.scope);
|
|
478
|
+
return holder.credentialScope == null && administers(holder.role) && unscoped(holder.scope);
|
|
451
479
|
}
|
|
452
480
|
/**
|
|
453
481
|
* Whether `holder` may give a service account `role` at `place`: where
|
|
@@ -455,11 +483,12 @@ function runsInstance(holder) {
|
|
|
455
483
|
* everything `role` does. A Developer on `market/dev` gives viewer or
|
|
456
484
|
* developer there, never maintainer; a grant on `market` takes holding it
|
|
457
485
|
* on all of `market`. `setting` is null for a service account, which sets
|
|
458
|
-
* up none
|
|
486
|
+
* up none; a request on a credential with a scope sets none up either,
|
|
487
|
+
* since what it set up would outlive it.
|
|
459
488
|
*/
|
|
460
489
|
function givesService(holder, setting, role, place) {
|
|
461
490
|
if (mayManageAccess(holder, place)) return true;
|
|
462
|
-
return setting !== null && inScope(setting, place) && ROLES[role].permissions.every((permission) => allows(holder, permission, place));
|
|
491
|
+
return setting !== null && holder.credentialScope == null && inScope(setting, place) && ROLES[role].permissions.every((permission) => allows(holder, permission, place));
|
|
463
492
|
}
|
|
464
493
|
/** Whether `holder` gives a service account anything anywhere among `places`: whether they set any up. */
|
|
465
494
|
function setsUpServices(holder, setting, places) {
|
|
@@ -511,7 +540,7 @@ function issuesCredential(holder, setting, actor, account, scope) {
|
|
|
511
540
|
* Anyone else reads the places they hold `audit.read` in.
|
|
512
541
|
*/
|
|
513
542
|
function readsWholeLog(holder) {
|
|
514
|
-
return instanceRoleGrants(holder.role, "audit.read") && unscoped(holder.scope);
|
|
543
|
+
return holder.credentialScope == null && instanceRoleGrants(holder.role, "audit.read") && unscoped(holder.scope);
|
|
515
544
|
}
|
|
516
545
|
/**
|
|
517
546
|
* Whether the holder may make a project: an Admin or Owner whose scope
|
|
@@ -521,7 +550,7 @@ function readsWholeLog(holder) {
|
|
|
521
550
|
*/
|
|
522
551
|
function makesProjects(holder) {
|
|
523
552
|
const { projects, environments } = holder.scope;
|
|
524
|
-
return administers(holder.role) && environments === "all" && (projects === "all" || "except" in projects);
|
|
553
|
+
return holder.credentialScope == null && administers(holder.role) && environments === "all" && (projects === "all" || "except" in projects);
|
|
525
554
|
}
|
|
526
555
|
//#endregion
|
|
527
|
-
export {
|
|
556
|
+
export { mayManageAccess as A, convertEveryProjectGrants as B, isCredentialScope as C, issuesCredential as D, isScope as E, runsInstance as F, scopeInWords as I, setsUpServices as L, ownsInstance as M, readsWholeLog as N, makesProjects as O, roleGrants as P, unscoped as R, instanceRoleGrants as S, isRole as T, credentialScopeInWords as _, MAX_PERSONAL_TOKEN_MINUTES as a, grantKind as b, PERMISSIONS as c, ROLE_NAMES as d, administers as f, covers as g, assignableToEnvironment as h, INSTANCE_ROLE_NAMES as i, normalScope as j, managesService as k, PROJECT_ONLY_PERMISSIONS as l, allows as m, EVERYWHERE as n, MAX_SCOPE_PLACES as o, admits as p, INSTANCE_ROLES as r, NOTHING as s, DELEGABLE_ROLES as t, ROLES as u, delegable as v, isInstanceRole as w, inScope as x, givesService as y, withinCredential as z };
|
|
@@ -293,12 +293,32 @@ declare function grantKind(grant: GrantPlace): 'project' | 'environment' | null;
|
|
|
293
293
|
* account developer on market/dev, then viewer on market/prod too
|
|
294
294
|
* token developer on market/dev, issued before market/prod
|
|
295
295
|
* request reads and writes market/dev, never market/prod
|
|
296
|
+
*
|
|
297
|
+
* A person's personal token carries one too, each grant one they held when
|
|
298
|
+
* they made it (`delegable` roles only), and is weighed the same way
|
|
299
|
+
* against what they hold now, by their instance role or their grants.
|
|
296
300
|
*/
|
|
297
301
|
type CredentialScope = readonly (GrantPlace & {
|
|
298
302
|
role: Role;
|
|
299
303
|
})[];
|
|
300
304
|
/** The most places a scope asked for names: far more than any credential needs, and a bound on what a request makes the app check. */
|
|
301
305
|
declare const MAX_SCOPE_PLACES = 1000;
|
|
306
|
+
/**
|
|
307
|
+
* Whether a personal token's scope may name `role`: one that manages no
|
|
308
|
+
* access. A token acting as its person reaches their secrets and places,
|
|
309
|
+
* never who holds them, so it cannot hand anyone, itself included, more
|
|
310
|
+
* than it was given. A project's Owner who wants a token for all of it
|
|
311
|
+
* names `maintainer`.
|
|
312
|
+
*/
|
|
313
|
+
declare function delegable(role: Role): boolean;
|
|
314
|
+
/** The roles a personal token's scope may name, in catalogue order. */
|
|
315
|
+
declare const DELEGABLE_ROLES: readonly Role[];
|
|
316
|
+
/**
|
|
317
|
+
* The longest a personal token lasts: a week. It is for an afternoon's
|
|
318
|
+
* script or agent, and whoever needs it longer makes another, as a person,
|
|
319
|
+
* rather than keep one around.
|
|
320
|
+
*/
|
|
321
|
+
declare const MAX_PERSONAL_TOKEN_MINUTES: number;
|
|
302
322
|
/**
|
|
303
323
|
* A credential's scope, by path as the API names it, in words, each role
|
|
304
324
|
* with its places: `developer on market/dev, api/dev; viewer on market/prod`.
|
|
@@ -320,7 +340,13 @@ type Holdings = {
|
|
|
320
340
|
grants: readonly (GrantPlace & {
|
|
321
341
|
role: Role;
|
|
322
342
|
})[];
|
|
323
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* The scope of the credential a request came in on, past which nothing
|
|
345
|
+
* reaches; absent or null for a session's. A scope names places, so a
|
|
346
|
+
* request made with one holds nothing of the instance's own: it does not
|
|
347
|
+
* run the instance, read its whole log or make projects, whatever its
|
|
348
|
+
* holder's instance role.
|
|
349
|
+
*/
|
|
324
350
|
credentialScope?: CredentialScope | null;
|
|
325
351
|
};
|
|
326
352
|
/** Someone who holds nothing: a stranger, a removed member, a tampered row. */
|
|
@@ -342,6 +368,14 @@ declare function covers(grant: GrantPlace, place: Place): boolean;
|
|
|
342
368
|
* `billing`.
|
|
343
369
|
*/
|
|
344
370
|
declare function allows(holder: Holdings, permission: Permission, place: Place): boolean;
|
|
371
|
+
/**
|
|
372
|
+
* Whether the credential a request came in on, a scoped token or binding,
|
|
373
|
+
* leaves `permission` at `place` to its holder: always, for a session. A
|
|
374
|
+
* personal config's owner holds its every permission by reading its parent
|
|
375
|
+
* (docs/design/forking.md), and a token of theirs is still held to the
|
|
376
|
+
* role its scope names there: a viewer's token reads it, and writes nothing.
|
|
377
|
+
*/
|
|
378
|
+
declare function withinCredential(holder: Holdings, permission: Permission, place: Place): boolean;
|
|
345
379
|
/**
|
|
346
380
|
* Whether the actor may give or take grants at this place: with
|
|
347
381
|
* `grant.manage` there, from a project grant or an instance role whose
|
|
@@ -373,7 +407,8 @@ type ServiceSetting = Scope;
|
|
|
373
407
|
* everything `role` does. A Developer on `market/dev` gives viewer or
|
|
374
408
|
* developer there, never maintainer; a grant on `market` takes holding it
|
|
375
409
|
* on all of `market`. `setting` is null for a service account, which sets
|
|
376
|
-
* up none
|
|
410
|
+
* up none; a request on a credential with a scope sets none up either,
|
|
411
|
+
* since what it set up would outlive it.
|
|
377
412
|
*/
|
|
378
413
|
declare function givesService(holder: Holdings, setting: ServiceSetting | null, role: Role, place: Place): boolean;
|
|
379
414
|
/** Whether `holder` gives a service account anything anywhere among `places`: whether they set any up. */
|
|
@@ -425,4 +460,4 @@ declare function readsWholeLog(holder: Holdings): boolean;
|
|
|
425
460
|
*/
|
|
426
461
|
declare function makesProjects(holder: Holdings): boolean;
|
|
427
462
|
//#endregion
|
|
428
|
-
export {
|
|
463
|
+
export { ConvertedGrant as $, givesService as A, managesService as B, administers as C, covers as D, assignableToEnvironment as E, isInstanceRole as F, roleGrants as G, normalScope as H, isRole as I, setsUpServices as J, runsInstance as K, isScope as L, inScope as M, instanceRoleGrants as N, credentialScopeInWords as O, isCredentialScope as P, ConversionInput as Q, issuesCredential as R, ServiceSetting as S, allows as T, ownsInstance as U, mayManageAccess as V, readsWholeLog as W, withinCredential as X, unscoped as Y, Conversion as Z, ROLES as _, GrantPlace as a, Scope as b, INSTANCE_ROLE_NAMES as c, MAX_SCOPE_PLACES as d, EveryProjectGrant as et, NOTHING as f, Place as g, Permission as h, Filter as i, grantKind as j, delegable as k, InstanceRole as l, PROJECT_ONLY_PERMISSIONS as m, DELEGABLE_ROLES as n, convertEveryProjectGrants as nt, Holdings as o, PERMISSIONS as p, scopeInWords as q, EVERYWHERE as r, INSTANCE_ROLES as s, CredentialScope as t, Narrowed as tt, MAX_PERSONAL_TOKEN_MINUTES as u, ROLE_NAMES as v, admits as w, ServiceAccount as x, Role as y, makesProjects as z };
|
package/dist/access.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { type Conversion, type ConversionInput, type ConvertedGrant, CredentialScope, EVERYWHERE, type EveryProjectGrant, Filter, GrantPlace, Holdings, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, InstanceRole, MAX_SCOPE_PLACES, NOTHING, type Narrowed, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, Permission, Place, ROLES, ROLE_NAMES, Role, Scope, ServiceAccount, ServiceSetting, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped };
|
|
1
|
+
import { $ as ConvertedGrant, A as givesService, B as managesService, C as administers, D as covers, E as assignableToEnvironment, F as isInstanceRole, G as roleGrants, H as normalScope, I as isRole, J as setsUpServices, K as runsInstance, L as isScope, M as inScope, N as instanceRoleGrants, O as credentialScopeInWords, P as isCredentialScope, Q as ConversionInput, R as issuesCredential, S as ServiceSetting, T as allows, U as ownsInstance, V as mayManageAccess, W as readsWholeLog, X as withinCredential, Y as unscoped, Z as Conversion, _ as ROLES, a as GrantPlace, b as Scope, c as INSTANCE_ROLE_NAMES, d as MAX_SCOPE_PLACES, et as EveryProjectGrant, f as NOTHING, g as Place, h as Permission, i as Filter, j as grantKind, k as delegable, l as InstanceRole, m as PROJECT_ONLY_PERMISSIONS, n as DELEGABLE_ROLES, nt as convertEveryProjectGrants, o as Holdings, p as PERMISSIONS, q as scopeInWords, r as EVERYWHERE, s as INSTANCE_ROLES, t as CredentialScope, tt as Narrowed, u as MAX_PERSONAL_TOKEN_MINUTES, v as ROLE_NAMES, w as admits, x as ServiceAccount, y as Role, z as makesProjects } from "./access-CONhKG2P.js";
|
|
2
|
+
export { type Conversion, type ConversionInput, type ConvertedGrant, CredentialScope, DELEGABLE_ROLES, EVERYWHERE, type EveryProjectGrant, Filter, GrantPlace, Holdings, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, InstanceRole, MAX_PERSONAL_TOKEN_MINUTES, MAX_SCOPE_PLACES, NOTHING, type Narrowed, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, Permission, Place, ROLES, ROLE_NAMES, Role, Scope, ServiceAccount, ServiceSetting, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, delegable, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped, withinCredential };
|
package/dist/access.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { EVERYWHERE, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, MAX_SCOPE_PLACES, NOTHING, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, ROLES, ROLE_NAMES, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped };
|
|
1
|
+
import { A as mayManageAccess, B as convertEveryProjectGrants, C as isCredentialScope, D as issuesCredential, E as isScope, F as runsInstance, I as scopeInWords, L as setsUpServices, M as ownsInstance, N as readsWholeLog, O as makesProjects, P as roleGrants, R as unscoped, S as instanceRoleGrants, T as isRole, _ as credentialScopeInWords, a as MAX_PERSONAL_TOKEN_MINUTES, b as grantKind, c as PERMISSIONS, d as ROLE_NAMES, f as administers, g as covers, h as assignableToEnvironment, i as INSTANCE_ROLE_NAMES, j as normalScope, k as managesService, l as PROJECT_ONLY_PERMISSIONS, m as allows, n as EVERYWHERE, o as MAX_SCOPE_PLACES, p as admits, r as INSTANCE_ROLES, s as NOTHING, t as DELEGABLE_ROLES, u as ROLES, v as delegable, w as isInstanceRole, x as inScope, y as givesService, z as withinCredential } from "./access-B1wpMzEB.js";
|
|
2
|
+
export { DELEGABLE_ROLES, EVERYWHERE, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, MAX_PERSONAL_TOKEN_MINUTES, MAX_SCOPE_PLACES, NOTHING, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, ROLES, ROLE_NAMES, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, delegable, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped, withinCredential };
|
package/dist/envelope.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as checkContext, f as encodeAad, l as AAD_VERSION, t as KekRegistry, u as SecretContext } from "./registry-
|
|
1
|
+
import { d as checkContext, f as encodeAad, l as AAD_VERSION, p as ownerKey, t as KekRegistry, u as SecretContext } from "./registry-Bd7hPoY4.js";
|
|
2
2
|
//#region src/envelope.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Current envelope format version. Stored on every row so the format can change
|
|
@@ -47,4 +47,4 @@ export declare function seal(plaintext: Buffer, ctx: SecretContext, keks: KekReg
|
|
|
47
47
|
/** Unwrap the envelope's DEK under the registry and decrypt: `decrypt` and the unwrap in one. */
|
|
48
48
|
export declare function open(envelope: Envelope, ctx: SecretContext, keks: KekRegistry): Promise<Buffer>;
|
|
49
49
|
//#endregion
|
|
50
|
-
export { AAD_VERSION, SecretContext, checkContext, encodeAad };
|
|
50
|
+
export { AAD_VERSION, SecretContext, checkContext, encodeAad, ownerKey };
|
package/dist/envelope.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AAD_VERSION, o as checkContext, s as encodeAad } from "./types-
|
|
1
|
+
import { a as AAD_VERSION, c as ownerKey, o as checkContext, s as encodeAad } from "./types-E1Sz50n6.js";
|
|
2
2
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
3
3
|
//#region src/envelope.ts
|
|
4
4
|
const IV_BYTES = 12;
|
|
@@ -80,4 +80,4 @@ async function open(envelope, ctx, keks) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
//#endregion
|
|
83
|
-
export { AAD_VERSION, ENVELOPE_VERSION, checkContext, decrypt, encodeAad, encrypt, freshDek, open, seal };
|
|
83
|
+
export { AAD_VERSION, ENVELOPE_VERSION, checkContext, decrypt, encodeAad, encrypt, freshDek, open, ownerKey, seal };
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
|
|
1
|
+
import { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
|
|
2
2
|
import { JSONWebKeySet, JWTPayload } from "jose";
|
|
3
3
|
//#region src/identity/github-app.d.ts
|
|
4
4
|
/**
|
|
@@ -604,8 +604,11 @@ export declare function unseal<T>(key: Uint8Array, sealed: string | undefined |
|
|
|
604
604
|
//#endregion
|
|
605
605
|
//#region src/identity/tokens.d.ts
|
|
606
606
|
/**
|
|
607
|
-
* Bearer credentials coffre issues itself: browser sessions, CLI sessions
|
|
608
|
-
* service tokens
|
|
607
|
+
* Bearer credentials coffre issues itself: browser sessions, CLI sessions,
|
|
608
|
+
* service tokens, personal tokens, which act as the person who made
|
|
609
|
+
* them within the scope they named, and handles, which reach nothing but
|
|
610
|
+
* the credential proxy (`@coffre/server/plugins/credential-proxy`): the
|
|
611
|
+
* stand-in a person's app holds in place of a secret's value.
|
|
609
612
|
*
|
|
610
613
|
* A token is a kind prefix and 32 random bytes, e.g.
|
|
611
614
|
* `coffre_cli_Qm9Y...`. The prefix tells a person (or a secret scanner) what a
|
|
@@ -614,7 +617,7 @@ export declare function unseal<T>(key: Uint8Array, sealed: string | undefined |
|
|
|
614
617
|
* right tool here, unlike for passwords: 256 bits of randomness leave nothing
|
|
615
618
|
* to brute-force.
|
|
616
619
|
*/
|
|
617
|
-
export type CredentialKind = 'browser' | 'cli' | 'service';
|
|
620
|
+
export type CredentialKind = 'browser' | 'cli' | 'service' | 'personal' | 'handle';
|
|
618
621
|
export declare function generateToken(kind: CredentialKind): string;
|
|
619
622
|
/** Whether a string has the shape of a coffre token, before any lookup. */
|
|
620
623
|
export declare function isCoffreToken(value: string): boolean;
|
|
@@ -683,6 +686,8 @@ export type WorkloadRefusal =
|
|
|
683
686
|
'expired' |
|
|
684
687
|
/** Its `iat` is more than an hour ago, or not yet. */
|
|
685
688
|
'too_old' |
|
|
689
|
+
/** It lives longer than an hour, `exp` − `iat`, where its binding carries its keys. */
|
|
690
|
+
'too_long' |
|
|
686
691
|
/** Its `nbf` is still to come. */
|
|
687
692
|
'not_yet_valid' |
|
|
688
693
|
/** Its audience is not this instance alone. */
|
|
@@ -715,12 +720,14 @@ export declare function decodeWorkloadToken(token: string): DecodedToken;
|
|
|
715
720
|
* algorithms fixed and nothing taken from the header but `alg` and `kid`;
|
|
716
721
|
* the issuer exactly; `exp`, `nbf` and `iat` within the tolerance, `iat` at
|
|
717
722
|
* most an hour old; and the audience, this instance's public URL alone.
|
|
718
|
-
*
|
|
723
|
+
* With `maxLifetime`, in seconds, a token whose `exp` is further than that
|
|
724
|
+
* from its `iat` is refused too. Returns the verified claims.
|
|
719
725
|
*/
|
|
720
726
|
export declare function verifyWorkloadToken(token: string, keys: JSONWebKeySet, expected: {
|
|
721
727
|
issuer: string;
|
|
722
728
|
audience: string;
|
|
723
729
|
now: Date;
|
|
730
|
+
maxLifetime?: number;
|
|
724
731
|
}): Promise<JWTPayload & DecodedToken['claims']>;
|
|
725
732
|
/**
|
|
726
733
|
* Why a token issued at `iat` may not be exchanged at `now`, both in
|
|
@@ -729,5 +736,42 @@ export declare function verifyWorkloadToken(token: string, keys: JSONWebKeySet,
|
|
|
729
736
|
* both ask it.
|
|
730
737
|
*/
|
|
731
738
|
export declare function issuedRefusal(iat: number, now: number): WorkloadTokenRefused | null;
|
|
739
|
+
/** At most this many keys in a set, fetched or uploaded. */
|
|
740
|
+
export declare const MAX_KEYS = 32;
|
|
741
|
+
/** One key of an uploaded set, as an owner sees it: its `kid`, its algorithm, and its RFC 7638 thumbprint. */
|
|
742
|
+
export type KeySummary = {
|
|
743
|
+
kid: string | null;
|
|
744
|
+
alg: (typeof WORKLOAD_ALGORITHMS)[number];
|
|
745
|
+
thumbprint: string;
|
|
746
|
+
};
|
|
747
|
+
/**
|
|
748
|
+
* A key set uploaded with a binding, as the binding keeps it: its public
|
|
749
|
+
* keys alone, as canonical JSON, and that JSON's SHA-256, in hex, which
|
|
750
|
+
* stands in the binding where a URL would and so is under its MAC.
|
|
751
|
+
*/
|
|
752
|
+
export type UploadedKeys = {
|
|
753
|
+
jwks: string;
|
|
754
|
+
digest: string;
|
|
755
|
+
keys: KeySummary[];
|
|
756
|
+
};
|
|
757
|
+
/** A key set that cannot be uploaded, said so an owner can fix it. */
|
|
758
|
+
export declare class KeysInvalid extends Error {
|
|
759
|
+
constructor(message: string);
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* An issuer's public keys, as uploaded for a binding: a JWKS, `{ "keys": [
|
|
763
|
+
* … ] }`, of RSA keys of 2048 bits or more, for RS256, and P-256 keys, for
|
|
764
|
+
* ES256, each kept with only the members that verify (`kty`, `kid`, `use`,
|
|
765
|
+
* `alg` and the key), in the order of their thumbprints, so that one set
|
|
766
|
+
* however listed has one digest. An RSA key's parameters are checked
|
|
767
|
+
* before jose sees them, since it takes some that verify anything: an
|
|
768
|
+
* exponent of 1 makes every signature its message. Keys for encryption are
|
|
769
|
+
* left out, as from a fetched set; a private or a symmetric key refuses the
|
|
770
|
+
* whole set, since coffre keeps no secret of an issuer's. Throws
|
|
771
|
+
* `KeysInvalid`.
|
|
772
|
+
*/
|
|
773
|
+
export declare function uploadedKeys(value: unknown): Promise<UploadedKeys>;
|
|
774
|
+
/** The keys of a set `uploadedKeys` kept, as an owner sees them. */
|
|
775
|
+
export declare function keySummaries(jwks: string): KeySummary[];
|
|
732
776
|
//#endregion
|
|
733
|
-
export { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, type JSONWebKeySet, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef };
|
|
777
|
+
export { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, type JSONWebKeySet, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef };
|
package/dist/identity.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BindingInvalid, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, REQUIRED_CLAIMS, WORKLOAD_PROFILES, canonicalClaims, checkBinding, checkFetchUrl, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
|
|
1
|
+
import { BindingInvalid, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, REQUIRED_CLAIMS, WORKLOAD_PROFILES, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
|
|
2
2
|
import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes } from "node:crypto";
|
|
3
3
|
import * as oauth from "oauth4webapi";
|
|
4
|
-
import { createLocalJWKSet, createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
4
|
+
import { createLocalJWKSet, createRemoteJWKSet, errors, importJWK, jwtVerify } from "jose";
|
|
5
5
|
//#region src/identity/github-app.ts
|
|
6
6
|
/** GitHub could not be asked, or refused the app: the message says which, for a person, and carries no credential. */
|
|
7
7
|
var GitHubAppUnavailable = class extends Error {
|
|
@@ -996,9 +996,11 @@ function unseal(key, sealed, now = Date.now()) {
|
|
|
996
996
|
const PREFIX = {
|
|
997
997
|
browser: "coffre_web_",
|
|
998
998
|
cli: "coffre_cli_",
|
|
999
|
-
service: "coffre_svc_"
|
|
999
|
+
service: "coffre_svc_",
|
|
1000
|
+
personal: "coffre_pat_",
|
|
1001
|
+
handle: "coffre_use_"
|
|
1000
1002
|
};
|
|
1001
|
-
const TOKEN = /^coffre_(web|cli|svc)_[A-Za-z0-9_-]{43}$/;
|
|
1003
|
+
const TOKEN = /^coffre_(web|cli|svc|pat|use)_[A-Za-z0-9_-]{43}$/;
|
|
1002
1004
|
function generateToken(kind) {
|
|
1003
1005
|
return PREFIX[kind] + randomBytes(32).toString("base64url");
|
|
1004
1006
|
}
|
|
@@ -1154,28 +1156,32 @@ function decodeWorkloadToken(token) {
|
|
|
1154
1156
|
* algorithms fixed and nothing taken from the header but `alg` and `kid`;
|
|
1155
1157
|
* the issuer exactly; `exp`, `nbf` and `iat` within the tolerance, `iat` at
|
|
1156
1158
|
* most an hour old; and the audience, this instance's public URL alone.
|
|
1157
|
-
*
|
|
1159
|
+
* With `maxLifetime`, in seconds, a token whose `exp` is further than that
|
|
1160
|
+
* from its `iat` is refused too. Returns the verified claims.
|
|
1158
1161
|
*/
|
|
1159
1162
|
async function verifyWorkloadToken(token, keys, expected) {
|
|
1163
|
+
const options = {
|
|
1164
|
+
algorithms: [...WORKLOAD_ALGORITHMS],
|
|
1165
|
+
issuer: expected.issuer,
|
|
1166
|
+
clockTolerance: 30,
|
|
1167
|
+
requiredClaims: [
|
|
1168
|
+
"iss",
|
|
1169
|
+
"sub",
|
|
1170
|
+
"exp",
|
|
1171
|
+
"iat"
|
|
1172
|
+
],
|
|
1173
|
+
currentDate: expected.now
|
|
1174
|
+
};
|
|
1160
1175
|
let payload;
|
|
1161
1176
|
try {
|
|
1162
|
-
({payload} = await jwtVerify(token, createLocalJWKSet(keys),
|
|
1163
|
-
algorithms: [...WORKLOAD_ALGORITHMS],
|
|
1164
|
-
issuer: expected.issuer,
|
|
1165
|
-
clockTolerance: 30,
|
|
1166
|
-
requiredClaims: [
|
|
1167
|
-
"iss",
|
|
1168
|
-
"sub",
|
|
1169
|
-
"exp",
|
|
1170
|
-
"iat"
|
|
1171
|
-
],
|
|
1172
|
-
currentDate: expected.now
|
|
1173
|
-
}));
|
|
1177
|
+
({payload} = await jwtVerify(token, createLocalJWKSet(keys), options));
|
|
1174
1178
|
} catch (error) {
|
|
1175
|
-
throw refusalOf(error);
|
|
1179
|
+
if (!(error instanceof errors.JWKSMultipleMatchingKeys)) throw refusalOf(error);
|
|
1180
|
+
payload = await anyKey(token, error, options);
|
|
1176
1181
|
}
|
|
1177
1182
|
const issued = issuedRefusal(payload.iat, expected.now.getTime() / 1e3);
|
|
1178
1183
|
if (issued !== null) throw issued;
|
|
1184
|
+
if (expected.maxLifetime !== void 0 && payload.exp - payload.iat > expected.maxLifetime) throw new WorkloadTokenRefused("too_long", `the token lives longer than ${expected.maxLifetime / 60} minutes, from its iat to its exp: ask for a shorter one`);
|
|
1179
1185
|
const { aud } = payload;
|
|
1180
1186
|
if (!(aud === expected.audience || Array.isArray(aud) && aud.length === 1 && aud[0] === expected.audience)) throw new WorkloadTokenRefused("audience", `the token is for ${JSON.stringify(aud ?? null)}, not ${expected.audience} alone`);
|
|
1181
1187
|
return payload;
|
|
@@ -1191,6 +1197,19 @@ function issuedRefusal(iat, now) {
|
|
|
1191
1197
|
if (iat - now > 30) return new WorkloadTokenRefused("too_old", "the token says it was issued in the future");
|
|
1192
1198
|
return null;
|
|
1193
1199
|
}
|
|
1200
|
+
/**
|
|
1201
|
+
* A token that names no key, or one several keys share, verified by
|
|
1202
|
+
* whichever of them signed it: a set may hold several, as while its issuer
|
|
1203
|
+
* rotates. At most `MAX_KEYS` tries, each a signature check alone.
|
|
1204
|
+
*/
|
|
1205
|
+
async function anyKey(token, matching, options) {
|
|
1206
|
+
for await (const key of matching) try {
|
|
1207
|
+
return (await jwtVerify(token, key, options)).payload;
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
if (!(error instanceof errors.JWSSignatureVerificationFailed)) throw refusalOf(error);
|
|
1210
|
+
}
|
|
1211
|
+
throw new WorkloadTokenRefused("signature", "the token's signature does not verify");
|
|
1212
|
+
}
|
|
1194
1213
|
function refusalOf(error) {
|
|
1195
1214
|
if (error instanceof errors.JWKSNoMatchingKey) return new WorkloadTokenRefused("unknown_key", "the token's key is not among the issuer's");
|
|
1196
1215
|
if (error instanceof errors.JWTExpired) return error.claim === "iat" ? new WorkloadTokenRefused("too_old", `the token was issued more than ${MAX_TOKEN_AGE_SECONDS / 60} minutes ago`) : new WorkloadTokenRefused("expired", "the token has expired");
|
|
@@ -1205,5 +1224,137 @@ function refusalOf(error) {
|
|
|
1205
1224
|
if (error instanceof errors.JOSEError) return new WorkloadTokenRefused("signature", `the token does not verify: ${error.code}`);
|
|
1206
1225
|
return new WorkloadTokenRefused("signature", "the token does not verify");
|
|
1207
1226
|
}
|
|
1227
|
+
/** At most this many keys in a set, fetched or uploaded. */
|
|
1228
|
+
const MAX_KEYS = 32;
|
|
1229
|
+
/** A key set that cannot be uploaded, said so an owner can fix it. */
|
|
1230
|
+
var KeysInvalid = class extends Error {
|
|
1231
|
+
constructor(message) {
|
|
1232
|
+
super(message);
|
|
1233
|
+
this.name = "KeysInvalid";
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
/** Members that only a private or a symmetric key has. */
|
|
1237
|
+
const PRIVATE_MEMBERS = [
|
|
1238
|
+
"d",
|
|
1239
|
+
"p",
|
|
1240
|
+
"q",
|
|
1241
|
+
"dp",
|
|
1242
|
+
"dq",
|
|
1243
|
+
"qi",
|
|
1244
|
+
"oth",
|
|
1245
|
+
"k"
|
|
1246
|
+
];
|
|
1247
|
+
/**
|
|
1248
|
+
* An issuer's public keys, as uploaded for a binding: a JWKS, `{ "keys": [
|
|
1249
|
+
* … ] }`, of RSA keys of 2048 bits or more, for RS256, and P-256 keys, for
|
|
1250
|
+
* ES256, each kept with only the members that verify (`kty`, `kid`, `use`,
|
|
1251
|
+
* `alg` and the key), in the order of their thumbprints, so that one set
|
|
1252
|
+
* however listed has one digest. An RSA key's parameters are checked
|
|
1253
|
+
* before jose sees them, since it takes some that verify anything: an
|
|
1254
|
+
* exponent of 1 makes every signature its message. Keys for encryption are
|
|
1255
|
+
* left out, as from a fetched set; a private or a symmetric key refuses the
|
|
1256
|
+
* whole set, since coffre keeps no secret of an issuer's. Throws
|
|
1257
|
+
* `KeysInvalid`.
|
|
1258
|
+
*/
|
|
1259
|
+
async function uploadedKeys(value) {
|
|
1260
|
+
const listed = value?.keys;
|
|
1261
|
+
if (typeof value !== "object" || value === null || !Array.isArray(listed)) throw new KeysInvalid("the keys are a JWKS: { \"keys\": [ … ] }");
|
|
1262
|
+
if (listed.length > 32) throw new KeysInvalid(`a key set holds at most 32 keys`);
|
|
1263
|
+
const keys = [];
|
|
1264
|
+
for (const [i, given] of listed.entries()) {
|
|
1265
|
+
if (typeof given !== "object" || given === null || Array.isArray(given)) throw new KeysInvalid(`key ${i + 1} is not a JWK`);
|
|
1266
|
+
const jwk = given;
|
|
1267
|
+
const named = typeof jwk.kid === "string" ? `key ${jwk.kid}` : `key ${i + 1}`;
|
|
1268
|
+
if (PRIVATE_MEMBERS.some((member) => member in jwk)) throw new KeysInvalid(`${named} is a private or a secret key: upload the issuer's public keys only`);
|
|
1269
|
+
if (jwk.use === "enc") continue;
|
|
1270
|
+
if (jwk.use !== void 0 && jwk.use !== "sig") throw new KeysInvalid(`${named} has use ${JSON.stringify(jwk.use)}: sig, or none`);
|
|
1271
|
+
if (jwk.kid !== void 0 && (typeof jwk.kid !== "string" || jwk.kid === "" || jwk.kid.length > 256)) throw new KeysInvalid(`key ${i + 1}'s kid is not a string`);
|
|
1272
|
+
if (jwk.kid !== void 0 && keys.some((key) => key.kid === jwk.kid)) throw new KeysInvalid(`two keys are ${named}`);
|
|
1273
|
+
let alg;
|
|
1274
|
+
let key;
|
|
1275
|
+
if (jwk.kty === "RSA" && (jwk.alg === void 0 || jwk.alg === "RS256")) {
|
|
1276
|
+
alg = "RS256";
|
|
1277
|
+
const [n, e] = [integer(jwk.n), integer(jwk.e)];
|
|
1278
|
+
if (n === null || e === null) throw new KeysInvalid(`${named} is an RSA key without n and e, each a base64url integer in its fewest bytes`);
|
|
1279
|
+
if (n.toString(2).length < 2048) throw new KeysInvalid(`${named} is an RSA key under 2048 bits`);
|
|
1280
|
+
if (n % 2n === 0n) throw new KeysInvalid(`${named} is not a valid RSA key`);
|
|
1281
|
+
if (e % 2n === 0n || e <= 65536n || e >= 2n ** 256n) throw new KeysInvalid(`${named}'s exponent is not one an RSA key signs with: odd, from 65537`);
|
|
1282
|
+
key = {
|
|
1283
|
+
kty: "RSA",
|
|
1284
|
+
...kidAndUse(jwk),
|
|
1285
|
+
alg,
|
|
1286
|
+
n: jwk.n,
|
|
1287
|
+
e: jwk.e
|
|
1288
|
+
};
|
|
1289
|
+
} else if (jwk.kty === "EC" && jwk.crv === "P-256" && (jwk.alg === void 0 || jwk.alg === "ES256")) {
|
|
1290
|
+
alg = "ES256";
|
|
1291
|
+
if (!coordinate(jwk.x) || !coordinate(jwk.y)) throw new KeysInvalid(`${named} is an EC key without x and y, each 32 bytes in base64url`);
|
|
1292
|
+
key = {
|
|
1293
|
+
kty: "EC",
|
|
1294
|
+
...kidAndUse(jwk),
|
|
1295
|
+
alg,
|
|
1296
|
+
crv: "P-256",
|
|
1297
|
+
x: jwk.x,
|
|
1298
|
+
y: jwk.y
|
|
1299
|
+
};
|
|
1300
|
+
} else throw new KeysInvalid(`${named} is not an RSA key for RS256 or a P-256 key for ES256, the two coffre accepts`);
|
|
1301
|
+
try {
|
|
1302
|
+
await importJWK(key, alg);
|
|
1303
|
+
} catch {
|
|
1304
|
+
throw new KeysInvalid(`${named} is not a valid ${key.kty} key`);
|
|
1305
|
+
}
|
|
1306
|
+
keys.push(key);
|
|
1307
|
+
}
|
|
1308
|
+
if (keys.length === 0) throw new KeysInvalid("the key set holds no key that signs");
|
|
1309
|
+
const sorted = keys.map((key) => [`${thumbprint(key)} ${JSON.stringify(key)}`, key]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1310
|
+
const jwks = JSON.stringify({ keys: sorted.map(([, key]) => key) });
|
|
1311
|
+
return {
|
|
1312
|
+
jwks,
|
|
1313
|
+
digest: createHash("sha256").update(jwks, "utf8").digest("hex"),
|
|
1314
|
+
keys: keySummaries(jwks)
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
/** The keys of a set `uploadedKeys` kept, as an owner sees them. */
|
|
1318
|
+
function keySummaries(jwks) {
|
|
1319
|
+
return JSON.parse(jwks).keys.map((key) => ({
|
|
1320
|
+
kid: key.kid ?? null,
|
|
1321
|
+
alg: key.kty === "RSA" ? "RS256" : "ES256",
|
|
1322
|
+
thumbprint: thumbprint(key)
|
|
1323
|
+
}));
|
|
1324
|
+
}
|
|
1325
|
+
/** A key's RFC 7638 thumbprint: the SHA-256 of its required members alone, in lexicographic order. */
|
|
1326
|
+
function thumbprint(key) {
|
|
1327
|
+
const required = key.kty === "RSA" ? {
|
|
1328
|
+
e: key.e,
|
|
1329
|
+
kty: key.kty,
|
|
1330
|
+
n: key.n
|
|
1331
|
+
} : {
|
|
1332
|
+
crv: key.crv,
|
|
1333
|
+
kty: key.kty,
|
|
1334
|
+
x: key.x,
|
|
1335
|
+
y: key.y
|
|
1336
|
+
};
|
|
1337
|
+
return createHash("sha256").update(JSON.stringify(required), "utf8").digest("base64url");
|
|
1338
|
+
}
|
|
1339
|
+
const BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
1340
|
+
/** A P-256 coordinate as JWK writes one: 32 bytes, in base64url exactly, so that one key has one spelling. */
|
|
1341
|
+
function coordinate(value) {
|
|
1342
|
+
if (typeof value !== "string" || !BASE64URL.test(value)) return false;
|
|
1343
|
+
const bytes = Buffer.from(value, "base64url");
|
|
1344
|
+
return bytes.length === 32 && bytes.toString("base64url") === value;
|
|
1345
|
+
}
|
|
1346
|
+
/** An unsigned integer as JWK writes one, base64url in its fewest bytes; null for anything else. */
|
|
1347
|
+
function integer(value) {
|
|
1348
|
+
if (typeof value !== "string" || !BASE64URL.test(value)) return null;
|
|
1349
|
+
const bytes = Buffer.from(value, "base64url");
|
|
1350
|
+
if (bytes.length === 0 || bytes[0] === 0 || bytes.toString("base64url") !== value) return null;
|
|
1351
|
+
return BigInt(`0x${bytes.toString("hex")}`);
|
|
1352
|
+
}
|
|
1353
|
+
function kidAndUse(jwk) {
|
|
1354
|
+
return {
|
|
1355
|
+
...jwk.kid === void 0 ? {} : { kid: jwk.kid },
|
|
1356
|
+
...jwk.use === void 0 ? {} : { use: "sig" }
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1208
1359
|
//#endregion
|
|
1209
|
-
export { ACCESS_JWT_HEADER, AccessIdentityVerifier, BindingInvalid, CLOCK_TOLERANCE_SECONDS, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, GitHubApp, GitHubAppUnavailable, GitHubSigninProvider, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_LISTING_REQUESTS, MAX_TOKEN_AGE_SECONDS, MAX_TOKEN_BYTES, OidcSigninProvider, REQUIRED_CLAIMS, SigninError, TOKEN_MARGIN_MS, WORKLOAD_ALGORITHMS, WORKLOAD_PROFILES, WorkloadTokenRefused, canonicalClaims, checkBinding, checkFetchUrl, claimValues, cloudflareAccess, decodeWorkloadToken, defaultIssuer, defineSignin, deriveKey, differingClaims, fullRef, generateToken, github, githubApp, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, google, hashToken, isCoffreToken, issuedRefusal, microsoft, oidc, pkcs8Of, publicOrigin, seal, signin, toPrincipal, tokenHint, tokenKind, unseal, verifyWorkloadToken, workflowRef };
|
|
1360
|
+
export { ACCESS_JWT_HEADER, AccessIdentityVerifier, BindingInvalid, CLOCK_TOLERANCE_SECONDS, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, GitHubApp, GitHubAppUnavailable, GitHubSigninProvider, KeysInvalid, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, MAX_KEYS, MAX_LISTING_REQUESTS, MAX_TOKEN_AGE_SECONDS, MAX_TOKEN_BYTES, OidcSigninProvider, REQUIRED_CLAIMS, SigninError, TOKEN_MARGIN_MS, WORKLOAD_ALGORITHMS, WORKLOAD_PROFILES, WorkloadTokenRefused, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, cloudflareAccess, decodeWorkloadToken, defaultIssuer, defineSignin, deriveKey, differingClaims, fullRef, generateToken, github, githubApp, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, google, hashToken, isCoffreToken, issuedRefusal, keySummaries, microsoft, oidc, pkcs8Of, publicOrigin, seal, signin, toPrincipal, tokenHint, tokenKind, unseal, uploadedKeys, verifyWorkloadToken, workflowRef };
|
package/dist/kek.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as KekProvider, c as WrappedDek, i as KekCancelledError, n as DEK_BYTES, o as KekUnavailableError, r as KekBadClaimError, s as KeyOperation, t as KekRegistry, u as SecretContext } from "./registry-
|
|
1
|
+
import { a as KekProvider, c as WrappedDek, i as KekCancelledError, n as DEK_BYTES, o as KekUnavailableError, r as KekBadClaimError, s as KeyOperation, t as KekRegistry, u as SecretContext } from "./registry-Bd7hPoY4.js";
|
|
2
2
|
//#region src/kek/local.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* A KEK held in local process memory, loaded from configuration.
|
package/dist/kek.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deriveLogKey } from "./audit.js";
|
|
2
|
-
import { i as KekUnavailableError, n as KekBadClaimError, o as checkContext, r as KekCancelledError, s as encodeAad, t as DEK_BYTES } from "./types-
|
|
2
|
+
import { i as KekUnavailableError, n as KekBadClaimError, o as checkContext, r as KekCancelledError, s as encodeAad, t as DEK_BYTES } from "./types-E1Sz50n6.js";
|
|
3
3
|
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
//#region src/kek/cancellation.ts
|
|
5
5
|
function checkOperation(operation, uncertain = false) {
|
|
@@ -425,7 +425,8 @@ function encryptionContext(ctx) {
|
|
|
425
425
|
return {
|
|
426
426
|
"coffre:project": ctx.projectId,
|
|
427
427
|
"coffre:environment": ctx.environmentId,
|
|
428
|
-
"coffre:secret": ctx.secretId
|
|
428
|
+
"coffre:secret": ctx.secretId,
|
|
429
|
+
...ctx.owner === void 0 ? {} : { "coffre:owner": ctx.owner }
|
|
429
430
|
};
|
|
430
431
|
}
|
|
431
432
|
/**
|