@coffre/core 0.1.6 → 0.1.7

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/kek.d.ts CHANGED
@@ -69,4 +69,20 @@ export declare class AwsKmsKekProvider implements KekProvider {
69
69
  unwrap(wrapped: WrappedDek, ctx: SecretContext, operation?: KeyOperation): Promise<Buffer>;
70
70
  }
71
71
  //#endregion
72
+ //#region src/kek/check.d.ts
73
+ /** The action of the vault's entry that holds a vault key's check. */
74
+ export declare const KEY_CHECK = "key.check";
75
+ /** The known value a check wraps: the size of a data key. */
76
+ export declare const KEY_CHECK_VALUE: Buffer;
77
+ /** The context a check is wrapped in, which no secret has: the nil UUID throughout. */
78
+ export declare const KEY_CHECK_CONTEXT: SecretContext;
79
+ /**
80
+ * Whether `key`, a local vault key, is the one that wrapped `check`: it
81
+ * opens it, under the vault ID the check names, to the known value. The ID
82
+ * is bound into the wrap, so a match also says the ID is the escrowed one's.
83
+ */
84
+ export declare function opensKeyCheck(key: Buffer, check: WrappedDek): Promise<boolean>;
85
+ /** The id the log records for entries the app signs with `key`, its APP_KEY. */
86
+ export declare function appLogKeyId(key: Uint8Array): string;
87
+ //#endregion
72
88
  export { type AwsCredentials, type AwsKmsOptions, DEK_BYTES, KekBadClaimError, KekCancelledError, type KekProvider, KekRegistry, KekUnavailableError, type KeyOperation, type WrappedDek };
package/dist/kek.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { deriveLogKey } from "./audit.js";
1
2
  import { i as KekUnavailableError, n as KekBadClaimError, o as checkContext, r as KekCancelledError, s as encodeAad, t as DEK_BYTES } from "./types-Mi7-Tzj9.js";
2
3
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
3
4
  //#region src/kek/cancellation.ts
@@ -483,4 +484,38 @@ var Slots = class {
483
484
  }
484
485
  };
485
486
  //#endregion
486
- export { AwsKmsKekProvider, DEK_BYTES, KekBadClaimError, KekCancelledError, KekRegistry, KekUnavailableError, LocalKekProvider, awsKms, equalBytes };
487
+ //#region src/kek/check.ts
488
+ /** The action of the vault's entry that holds a vault key's check. */
489
+ const KEY_CHECK = "key.check";
490
+ /** The known value a check wraps: the size of a data key. */
491
+ const KEY_CHECK_VALUE = createHash("sha256").update("coffre.kek.check.v1").digest();
492
+ const NIL = "00000000-0000-0000-0000-000000000000";
493
+ /** The context a check is wrapped in, which no secret has: the nil UUID throughout. */
494
+ const KEY_CHECK_CONTEXT = {
495
+ projectId: NIL,
496
+ environmentId: NIL,
497
+ secretId: NIL
498
+ };
499
+ /**
500
+ * Whether `key`, a local vault key, is the one that wrapped `check`: it
501
+ * opens it, under the vault ID the check names, to the known value. The ID
502
+ * is bound into the wrap, so a match also says the ID is the escrowed one's.
503
+ */
504
+ async function opensKeyCheck(key, check) {
505
+ if (check.kekProvider !== "local" || key.length !== 32) return false;
506
+ try {
507
+ const value = await new LocalKekProvider(key, check.kekId, check.kekVersion).unwrap(check, KEY_CHECK_CONTEXT);
508
+ const right = equalBytes(value, KEY_CHECK_VALUE);
509
+ value.fill(0);
510
+ return right;
511
+ } catch (error) {
512
+ if (error instanceof KekBadClaimError) return false;
513
+ throw error;
514
+ }
515
+ }
516
+ /** The id the log records for entries the app signs with `key`, its APP_KEY. */
517
+ function appLogKeyId(key) {
518
+ return deriveLogKey("app", key).keyId;
519
+ }
520
+ //#endregion
521
+ export { AwsKmsKekProvider, DEK_BYTES, KEY_CHECK, KEY_CHECK_CONTEXT, KEY_CHECK_VALUE, KekBadClaimError, KekCancelledError, KekRegistry, KekUnavailableError, LocalKekProvider, appLogKeyId, awsKms, equalBytes, opensKeyCheck };
package/dist/schemas.d.ts CHANGED
@@ -9,8 +9,8 @@ export declare const principalType: z.ZodEnum<{
9
9
  }>;
10
10
  export declare const principalId: z.ZodString;
11
11
  export declare const instanceRole: z.ZodEnum<{
12
- owner: "owner";
13
12
  user: "user";
13
+ owner: "owner";
14
14
  }>;
15
15
  export declare const grantId: z.ZodString;
16
16
  export declare const emailAddress: z.ZodString;
package/dist/vault.d.ts CHANGED
@@ -155,6 +155,12 @@ export interface Vault {
155
155
  checkpointKeys: Record<string, CheckpointKey>;
156
156
  rootAdmins: string[];
157
157
  }>;
158
+ /**
159
+ * What an escrowed vault key is checked against: the vault key it wraps
160
+ * under now, and each one's check, as the vault wrote it. None of it is
161
+ * secret, and the vault answers no guess: a check opens only under its key.
162
+ */
163
+ keyChecks(): Promise<KeyChecks>;
158
164
  /**
159
165
  * Check the whole log: rehash it from the first entry, the vault's
160
166
  * entries by their MACs, every checkpoint against the prefix it signed,
@@ -234,6 +240,21 @@ export type WrappedKey = {
234
240
  kekVersion: string;
235
241
  bytes: string;
236
242
  };
243
+ /**
244
+ * The vault key the vault wraps under now, by provider and ID, and each
245
+ * vault key's check, newest first: a known value wrapped under it
246
+ * (`KEY_CHECK_VALUE` in `@coffre/core/kek`), in its newest `key.check`
247
+ * entry that carries the vault's MAC, at `seq`.
248
+ */
249
+ export type KeyChecks = {
250
+ current: {
251
+ kekProvider: string;
252
+ kekId: string;
253
+ };
254
+ checks: (WrappedKey & {
255
+ seq: number;
256
+ })[];
257
+ };
237
258
  /** Why someone reads: shown in the log, and the same rules apply to each. */
238
259
  export type Purpose = 'reveal' | 'run' | 'compare' | 'sync';
239
260
  /** Ties the vault's entries to the app's request and audit rows. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/core",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "What coffre's packages share: roles, envelopes, identity, the audit chain and the vault's contract.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -65,10 +65,11 @@
65
65
  "zod": "4.4.3"
66
66
  },
67
67
  "devDependencies": {
68
+ "@hegeldev/hegel": "0.4.7",
68
69
  "@types/node": "26.1.1",
69
70
  "tsdown": "0.23.0",
70
71
  "typescript": "5.9.3",
71
- "@coffre/conformance": "0.1.6"
72
+ "@coffre/conformance": "0.1.7"
72
73
  },
73
74
  "scripts": {
74
75
  "build": "tsdown",
@@ -0,0 +1,46 @@
1
+ // What a vault key, or the app key, can be checked against without opening
2
+ // anything else: the vault's `key.check` entries, each a known value wrapped
3
+ // under one vault key, and the key id the log records for the app's entries,
4
+ // a fingerprint of the key they are signed with. The vault writes the
5
+ // checks; `coffre verify keys` holds an escrowed key to them, on the
6
+ // operator's machine, the key never sent anywhere.
7
+ import { createHash } from 'node:crypto';
8
+
9
+ import { deriveLogKey } from '../audit/chain.ts';
10
+ import type { SecretContext } from '../context.ts';
11
+ import { equalBytes, LocalKekProvider } from './local.ts';
12
+ import { KekBadClaimError, type WrappedDek } from './types.ts';
13
+
14
+ /** The action of the vault's entry that holds a vault key's check. */
15
+ export const KEY_CHECK = 'key.check';
16
+
17
+ /** The known value a check wraps: the size of a data key. */
18
+ export const KEY_CHECK_VALUE: Buffer = createHash('sha256').update('coffre.kek.check.v1').digest();
19
+
20
+ const NIL = '00000000-0000-0000-0000-000000000000';
21
+
22
+ /** The context a check is wrapped in, which no secret has: the nil UUID throughout. */
23
+ export const KEY_CHECK_CONTEXT: SecretContext = { projectId: NIL, environmentId: NIL, secretId: NIL };
24
+
25
+ /**
26
+ * Whether `key`, a local vault key, is the one that wrapped `check`: it
27
+ * opens it, under the vault ID the check names, to the known value. The ID
28
+ * is bound into the wrap, so a match also says the ID is the escrowed one's.
29
+ */
30
+ export async function opensKeyCheck(key: Buffer, check: WrappedDek): Promise<boolean> {
31
+ if (check.kekProvider !== 'local' || key.length !== 32) return false;
32
+ try {
33
+ const value = await new LocalKekProvider(key, check.kekId, check.kekVersion).unwrap(check, KEY_CHECK_CONTEXT);
34
+ const right = equalBytes(value, KEY_CHECK_VALUE);
35
+ value.fill(0);
36
+ return right;
37
+ } catch (error) {
38
+ if (error instanceof KekBadClaimError) return false;
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ /** The id the log records for entries the app signs with `key`, its APP_KEY. */
44
+ export function appLogKeyId(key: Uint8Array): string {
45
+ return deriveLogKey('app', key).keyId;
46
+ }
package/src/kek/index.ts CHANGED
@@ -3,4 +3,5 @@ export { DEK_BYTES, KekUnavailableError, KekCancelledError, KekBadClaimError } f
3
3
  export { LocalKekProvider, equalBytes } from './local.ts';
4
4
  export { KekRegistry } from './registry.ts';
5
5
  export { AwsKmsKekProvider, awsKms, type AwsKmsOptions } from './aws-kms.ts';
6
+ export { appLogKeyId, KEY_CHECK, KEY_CHECK_CONTEXT, KEY_CHECK_VALUE, opensKeyCheck } from './check.ts';
6
7
  export type { AwsCredentials } from './sigv4.ts';
package/src/vault.ts CHANGED
@@ -70,6 +70,12 @@ export interface Vault {
70
70
  * admins, as principals.
71
71
  */
72
72
  about(): Promise<{ checkpointKeys: Record<string, CheckpointKey>; rootAdmins: string[] }>;
73
+ /**
74
+ * What an escrowed vault key is checked against: the vault key it wraps
75
+ * under now, and each one's check, as the vault wrote it. None of it is
76
+ * secret, and the vault answers no guess: a check opens only under its key.
77
+ */
78
+ keyChecks(): Promise<KeyChecks>;
73
79
  /**
74
80
  * Check the whole log: rehash it from the first entry, the vault's
75
81
  * entries by their MACs, every checkpoint against the prefix it signed,
@@ -143,6 +149,14 @@ export type SecretRef = {
143
149
  /** A wrapped data key and the key encryption key that wrapped it; `bytes` is base64. */
144
150
  export type WrappedKey = { kekProvider: string; kekId: string; kekVersion: string; bytes: string };
145
151
 
152
+ /**
153
+ * The vault key the vault wraps under now, by provider and ID, and each
154
+ * vault key's check, newest first: a known value wrapped under it
155
+ * (`KEY_CHECK_VALUE` in `@coffre/core/kek`), in its newest `key.check`
156
+ * entry that carries the vault's MAC, at `seq`.
157
+ */
158
+ export type KeyChecks = { current: { kekProvider: string; kekId: string }; checks: (WrappedKey & { seq: number })[] };
159
+
146
160
  /** Why someone reads: shown in the log, and the same rules apply to each. */
147
161
  export type Purpose = 'reveal' | 'run' | 'compare' | 'sync';
148
162