@sudp-protocol/authorizer 0.1.1 → 0.2.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/README.md +12 -1
- package/dist/aad.d.ts +1 -1
- package/dist/aad.js +1 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.d.ts.map +1 -1
- package/dist/bytes.js +23 -0
- package/dist/bytes.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/record.d.ts +78 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +93 -0
- package/dist/record.js.map +1 -0
- package/package.json +1 -1
- package/src/aad.ts +1 -1
- package/src/bytes.ts +25 -0
- package/src/index.ts +11 -0
- package/src/record.ts +147 -0
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ shapes, and the wrap-key derivation.
|
|
|
20
20
|
computeBinding, computeBatchBinding,
|
|
21
21
|
deriveWrappingKey, wrapBindingAd, sealAd,
|
|
22
22
|
aeadSeal, aeadOpen, aeadEncrypt, base64url helpers,
|
|
23
|
-
|
|
23
|
+
sealRecord, unsealRecord, recordAad, deriveItemKey, ← per-record (per-item) seal
|
|
24
|
+
DS_BIND / DS_WRAP / DS_SEAL / DS_ITEM constants
|
|
24
25
|
|
|
25
26
|
@sudp-protocol/authorizer/webauthn ← WebAuthn-specific adapter
|
|
26
27
|
prfToUserKey(prfOutput) → 32-byte y_c
|
|
@@ -70,6 +71,16 @@ const wrapped = aeadSeal(Wc, plaintext, wrapBindingAd(credentialId));
|
|
|
70
71
|
// part of the grant.
|
|
71
72
|
```
|
|
72
73
|
|
|
74
|
+
For batch grants, swap `computeBinding` for `computeBatchBinding(DS_BIND, r, ops)` — same math, one signature covers `ops = (o_1, …, o_n)`.
|
|
75
|
+
|
|
76
|
+
For **per-item vaults** (a vault stored as many independently-encrypted records
|
|
77
|
+
instead of one blob), seal/open a single record with `sealRecord(k, ctx, pt)` /
|
|
78
|
+
`unsealRecord(k, ctx, sealed)`, where `ctx: SealCtx` is `{ domain, vault, id,
|
|
79
|
+
version }`. sudp binds `ctx` into the AEAD AAD (anti cross-vault / cross-id /
|
|
80
|
+
version-mismatch); the record body, id derivation, and conflict resolution are
|
|
81
|
+
the caller's. Byte-identical to the Rust crate's `seal_record` — see the
|
|
82
|
+
[conformance map](../../README.md#cross-language-alignment).
|
|
83
|
+
|
|
73
84
|
## End-to-end protocol walkthrough
|
|
74
85
|
|
|
75
86
|
For how this package fits with the Rust Custodian and the Requester
|
package/dist/aad.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const WRAP_VERSION = 1;
|
|
|
11
11
|
* DS_WRAP ‖ credentialId ‖ ver_be(u16, big-endian)
|
|
12
12
|
*
|
|
13
13
|
* Bound as associated data when sealing/opening `K̂_c` under `W_c`, so a
|
|
14
|
-
*
|
|
14
|
+
* per-credential-wrapped record cannot be substituted across credentials or versions.
|
|
15
15
|
*/
|
|
16
16
|
export declare function wrapBindingAd(credentialId: Uint8Array, wrapVersion?: number): Uint8Array;
|
|
17
17
|
/**
|
package/dist/aad.js
CHANGED
|
@@ -12,7 +12,7 @@ export const WRAP_VERSION = 0x0001;
|
|
|
12
12
|
* DS_WRAP ‖ credentialId ‖ ver_be(u16, big-endian)
|
|
13
13
|
*
|
|
14
14
|
* Bound as associated data when sealing/opening `K̂_c` under `W_c`, so a
|
|
15
|
-
*
|
|
15
|
+
* per-credential-wrapped record cannot be substituted across credentials or versions.
|
|
16
16
|
*/
|
|
17
17
|
export function wrapBindingAd(credentialId, wrapVersion = WRAP_VERSION) {
|
|
18
18
|
return concatBytes(DS_WRAP, credentialId, u16beBytes(wrapVersion));
|
package/dist/bytes.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export declare function utf8(s: string): Uint8Array;
|
|
2
2
|
export declare function concatBytes(...parts: readonly Uint8Array[]): Uint8Array;
|
|
3
3
|
export declare function u16beBytes(n: number): Uint8Array;
|
|
4
|
+
export declare function u32beBytes(n: number): Uint8Array;
|
|
5
|
+
/**
|
|
6
|
+
* 8-byte big-endian encoding of a `u64`. Takes a `bigint` so the full unsigned
|
|
7
|
+
* 64-bit range is exact (a `number` is only safe to 2^53). MUST match the Rust
|
|
8
|
+
* crate's `u64::to_be_bytes`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function u64beBytes(n: bigint): Uint8Array;
|
|
4
11
|
export declare function bytesToB64Url(b: Uint8Array): string;
|
|
5
12
|
export declare function b64UrlToBytes(s: string): Uint8Array;
|
|
6
13
|
//# sourceMappingURL=bytes.d.ts.map
|
package/dist/bytes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAEA,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAE1C;AAED,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAUvE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAKhD;AAcD,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAgBnD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAiCnD"}
|
|
1
|
+
{"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAEA,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAE1C;AAED,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAUvE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAKhD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAKhD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAWhD;AAcD,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAgBnD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAiCnD"}
|
package/dist/bytes.js
CHANGED
|
@@ -20,6 +20,29 @@ export function u16beBytes(n) {
|
|
|
20
20
|
}
|
|
21
21
|
return new Uint8Array([(n >> 8) & 0xff, n & 0xff]);
|
|
22
22
|
}
|
|
23
|
+
export function u32beBytes(n) {
|
|
24
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xffffffff) {
|
|
25
|
+
throw new Error(`u32beBytes: out of range: ${n}`);
|
|
26
|
+
}
|
|
27
|
+
return new Uint8Array([(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 8-byte big-endian encoding of a `u64`. Takes a `bigint` so the full unsigned
|
|
31
|
+
* 64-bit range is exact (a `number` is only safe to 2^53). MUST match the Rust
|
|
32
|
+
* crate's `u64::to_be_bytes`.
|
|
33
|
+
*/
|
|
34
|
+
export function u64beBytes(n) {
|
|
35
|
+
if (n < 0n || n > 0xffffffffffffffffn) {
|
|
36
|
+
throw new Error(`u64beBytes: out of range: ${n}`);
|
|
37
|
+
}
|
|
38
|
+
const out = new Uint8Array(8);
|
|
39
|
+
let v = n;
|
|
40
|
+
for (let i = 7; i >= 0; i--) {
|
|
41
|
+
out[i] = Number(v & 0xffn);
|
|
42
|
+
v >>= 8n;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
23
46
|
const B64URL_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
24
47
|
const B64URL_LOOKUP = (() => {
|
|
25
48
|
const t = new Int8Array(256).fill(-1);
|
package/dist/bytes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bytes.js","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bytes.js","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
* For the WebAuthn PRF → y_c adapter and assertion helpers, import from
|
|
11
11
|
* `@sudp-protocol/authorizer/webauthn`.
|
|
12
12
|
*/
|
|
13
|
-
export { utf8, concatBytes, u16beBytes, bytesToB64Url, b64UrlToBytes, } from "./bytes.js";
|
|
13
|
+
export { utf8, concatBytes, u16beBytes, u32beBytes, u64beBytes, bytesToB64Url, b64UrlToBytes, } from "./bytes.js";
|
|
14
14
|
export { canonicalize } from "./canonical.js";
|
|
15
15
|
export { sha256 } from "./hash.js";
|
|
16
16
|
export { computeBatchBinding, computeBinding, DS_BIND } from "./binding.js";
|
|
17
17
|
export { deriveWrappingKey } from "./kdf.js";
|
|
18
18
|
export { wrapBindingAd, sealAd, DS_WRAP, DS_SEAL, WRAP_VERSION } from "./aad.js";
|
|
19
19
|
export { aeadEncrypt, aeadSeal, aeadOpen } from "./aead.js";
|
|
20
|
+
export { recordAad, deriveItemKey, sealRecord, unsealRecord, DS_ITEM, RECORD_SUITE_XCHACHA20POLY1305, type SealCtx, } from "./record.js";
|
|
20
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,IAAI,EACJ,WAAW,EACX,UAAU,EACV,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,IAAI,EACJ,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,YAAY,EACZ,OAAO,EACP,8BAA8B,EAC9B,KAAK,OAAO,GACb,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
* For the WebAuthn PRF → y_c adapter and assertion helpers, import from
|
|
11
11
|
* `@sudp-protocol/authorizer/webauthn`.
|
|
12
12
|
*/
|
|
13
|
-
export { utf8, concatBytes, u16beBytes, bytesToB64Url, b64UrlToBytes, } from "./bytes.js";
|
|
13
|
+
export { utf8, concatBytes, u16beBytes, u32beBytes, u64beBytes, bytesToB64Url, b64UrlToBytes, } from "./bytes.js";
|
|
14
14
|
export { canonicalize } from "./canonical.js";
|
|
15
15
|
export { sha256 } from "./hash.js";
|
|
16
16
|
export { computeBatchBinding, computeBinding, DS_BIND } from "./binding.js";
|
|
17
17
|
export { deriveWrappingKey } from "./kdf.js";
|
|
18
18
|
export { wrapBindingAd, sealAd, DS_WRAP, DS_SEAL, WRAP_VERSION } from "./aad.js";
|
|
19
19
|
export { aeadEncrypt, aeadSeal, aeadOpen } from "./aead.js";
|
|
20
|
+
export { recordAad, deriveItemKey, sealRecord, unsealRecord, DS_ITEM, RECORD_SUITE_XCHACHA20POLY1305, } from "./record.js";
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,IAAI,EACJ,WAAW,EACX,UAAU,EACV,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,IAAI,EACJ,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,YAAY,EACZ,OAAO,EACP,8BAA8B,GAE/B,MAAM,aAAa,CAAC"}
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-record seal/unseal — the per-item vault primitive, authorizer-side mirror
|
|
3
|
+
* of the Rust crate's `sudp::state::{seal_record, unseal_record}`.
|
|
4
|
+
*
|
|
5
|
+
* Eats bytes, spits bytes: the record's business structure, serialization,
|
|
6
|
+
* id derivation, version comparison, merge, tombstones, and GC are ALL the
|
|
7
|
+
* caller's concern. This module only seals one record under the vault key `K`
|
|
8
|
+
* bound — via AEAD associated data — to a structured {@link SealCtx}, so a
|
|
9
|
+
* record can never be opened under a different vault / id / version / domain.
|
|
10
|
+
*
|
|
11
|
+
* The AAD binding detects cross-vault splicing, cross-id substitution,
|
|
12
|
+
* version/ciphertext mismatch, and cross-domain confusion. It does NOT by
|
|
13
|
+
* itself prevent rollback (an attacker substituting a strictly older but
|
|
14
|
+
* validly-sealed record with its matching older `version`) — keep a monotonic
|
|
15
|
+
* per-id version store on the caller side, and put richer causal/merge metadata
|
|
16
|
+
* (HLC, device id, …) INSIDE the plaintext so the AEAD authenticates it too.
|
|
17
|
+
*
|
|
18
|
+
* Every byte layout here (canonical AAD, HKDF info, sealed framing) MUST stay
|
|
19
|
+
* byte-for-byte aligned with `custodian/rust/src/state/record.rs`; the
|
|
20
|
+
* conformance vectors in `test/conformance.test.ts` are pinned against the Rust
|
|
21
|
+
* `assert_eq!` anchors.
|
|
22
|
+
*/
|
|
23
|
+
/** Per-record (per-item) domain-separation label. */
|
|
24
|
+
export declare const DS_ITEM: Uint8Array<ArrayBufferLike>;
|
|
25
|
+
/** Suite/format tag: `0x01` = XChaCha20-Poly1305 + canonical AAD v1. */
|
|
26
|
+
export declare const RECORD_SUITE_XCHACHA20POLY1305 = 1;
|
|
27
|
+
/**
|
|
28
|
+
* Structured sealing context for one record. sudp builds the AEAD associated
|
|
29
|
+
* data from these fields; callers MUST NOT hand-assemble opaque AAD.
|
|
30
|
+
*/
|
|
31
|
+
export interface SealCtx {
|
|
32
|
+
/** Purpose separation within the per-record domain, e.g. `"item"` / `"keyset"`. */
|
|
33
|
+
domain: string;
|
|
34
|
+
/** Vault identifier (anti cross-vault splice). */
|
|
35
|
+
vault: Uint8Array;
|
|
36
|
+
/** Opaque record id — typically `HMAC_K(name)`; never interpreted (anti X-as-Y). */
|
|
37
|
+
id: Uint8Array;
|
|
38
|
+
/**
|
|
39
|
+
* Opaque, monotonic-per-id ordering value. sudp binds it into the AAD but
|
|
40
|
+
* never interprets it — meaning (server sequence number, Lamport/HLC stamp,
|
|
41
|
+
* version-vector, …) and the whole conflict-resolution strategy are the
|
|
42
|
+
* caller's. Recommended scalar encoding for the common case: `u64`
|
|
43
|
+
* big-endian (`u64beBytes(n)`). Binds the ciphertext to a version (detects
|
|
44
|
+
* mismatch, not rollback).
|
|
45
|
+
*/
|
|
46
|
+
version: Uint8Array;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Canonical, length-prefixed associated data for a per-record seal:
|
|
50
|
+
*
|
|
51
|
+
* AAD = suite(1) ‖ lp(DS_ITEM) ‖ lp(domain_utf8) ‖ lp(vault) ‖ lp(id) ‖ lp(version)
|
|
52
|
+
* where lp(x) = len_be(x.length as u32, 4 bytes) ‖ x
|
|
53
|
+
*
|
|
54
|
+
* MUST be byte-identical to the Rust crate's `record_aad`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function recordAad(suite: number, ctx: SealCtx): Uint8Array;
|
|
57
|
+
/**
|
|
58
|
+
* Derive the per-record AEAD key `K_aead` from the vault key `k`:
|
|
59
|
+
*
|
|
60
|
+
* K_aead = HKDF-SHA-256(ikm = k, salt = "", info = DS_ITEM)
|
|
61
|
+
*
|
|
62
|
+
* Domain-separated so `K_aead` never shares raw bytes with the caller's
|
|
63
|
+
* `HMAC_K(name)` record-id derivation. MUST match the Rust crate's
|
|
64
|
+
* `derive_item_key`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function deriveItemKey(k: Uint8Array): Promise<Uint8Array>;
|
|
67
|
+
/**
|
|
68
|
+
* Seal one record. Output layout: `suite(1) ‖ nonce(24) ‖ ciphertext ‖ tag(16)`.
|
|
69
|
+
* A fresh random 24-byte nonce is generated per call (Web Crypto).
|
|
70
|
+
*/
|
|
71
|
+
export declare function sealRecord(k: Uint8Array, ctx: SealCtx, plaintext: Uint8Array): Promise<Uint8Array>;
|
|
72
|
+
/**
|
|
73
|
+
* Unseal one record. Throws on any authentication failure (AAD mismatch and
|
|
74
|
+
* tag mismatch are indistinguishable — the same Poly1305 check) or on a
|
|
75
|
+
* structurally malformed blob (empty / unknown suite tag).
|
|
76
|
+
*/
|
|
77
|
+
export declare function unsealRecord(k: Uint8Array, ctx: SealCtx, sealed: Uint8Array): Promise<Uint8Array>;
|
|
78
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,qDAAqD;AACrD,eAAO,MAAM,OAAO,6BAAuB,CAAC;AAE5C,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,IAAO,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,KAAK,EAAE,UAAU,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,UAAU,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,UAAU,CAAC;CACrB;AAOD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,UAAU,CASjE;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAmBtE;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,CAAC,EAAE,UAAU,EACb,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,UAAU,CAAC,CAQrB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,CAAC,EAAE,UAAU,EACb,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,UAAU,CAAC,CAWrB"}
|
package/dist/record.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { aeadOpen, aeadSeal } from "./aead.js";
|
|
2
|
+
import { concatBytes, u32beBytes, utf8 } from "./bytes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Per-record seal/unseal — the per-item vault primitive, authorizer-side mirror
|
|
5
|
+
* of the Rust crate's `sudp::state::{seal_record, unseal_record}`.
|
|
6
|
+
*
|
|
7
|
+
* Eats bytes, spits bytes: the record's business structure, serialization,
|
|
8
|
+
* id derivation, version comparison, merge, tombstones, and GC are ALL the
|
|
9
|
+
* caller's concern. This module only seals one record under the vault key `K`
|
|
10
|
+
* bound — via AEAD associated data — to a structured {@link SealCtx}, so a
|
|
11
|
+
* record can never be opened under a different vault / id / version / domain.
|
|
12
|
+
*
|
|
13
|
+
* The AAD binding detects cross-vault splicing, cross-id substitution,
|
|
14
|
+
* version/ciphertext mismatch, and cross-domain confusion. It does NOT by
|
|
15
|
+
* itself prevent rollback (an attacker substituting a strictly older but
|
|
16
|
+
* validly-sealed record with its matching older `version`) — keep a monotonic
|
|
17
|
+
* per-id version store on the caller side, and put richer causal/merge metadata
|
|
18
|
+
* (HLC, device id, …) INSIDE the plaintext so the AEAD authenticates it too.
|
|
19
|
+
*
|
|
20
|
+
* Every byte layout here (canonical AAD, HKDF info, sealed framing) MUST stay
|
|
21
|
+
* byte-for-byte aligned with `custodian/rust/src/state/record.rs`; the
|
|
22
|
+
* conformance vectors in `test/conformance.test.ts` are pinned against the Rust
|
|
23
|
+
* `assert_eq!` anchors.
|
|
24
|
+
*/
|
|
25
|
+
/** Per-record (per-item) domain-separation label. */
|
|
26
|
+
export const DS_ITEM = utf8("sudp/v1/item");
|
|
27
|
+
/** Suite/format tag: `0x01` = XChaCha20-Poly1305 + canonical AAD v1. */
|
|
28
|
+
export const RECORD_SUITE_XCHACHA20POLY1305 = 0x01;
|
|
29
|
+
/** Length-prefixed field: `len_be(u32) ‖ bytes`. */
|
|
30
|
+
function lp(field) {
|
|
31
|
+
return concatBytes(u32beBytes(field.byteLength), field);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Canonical, length-prefixed associated data for a per-record seal:
|
|
35
|
+
*
|
|
36
|
+
* AAD = suite(1) ‖ lp(DS_ITEM) ‖ lp(domain_utf8) ‖ lp(vault) ‖ lp(id) ‖ lp(version)
|
|
37
|
+
* where lp(x) = len_be(x.length as u32, 4 bytes) ‖ x
|
|
38
|
+
*
|
|
39
|
+
* MUST be byte-identical to the Rust crate's `record_aad`.
|
|
40
|
+
*/
|
|
41
|
+
export function recordAad(suite, ctx) {
|
|
42
|
+
return concatBytes(new Uint8Array([suite & 0xff]), lp(DS_ITEM), lp(utf8(ctx.domain)), lp(ctx.vault), lp(ctx.id), lp(ctx.version));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Derive the per-record AEAD key `K_aead` from the vault key `k`:
|
|
46
|
+
*
|
|
47
|
+
* K_aead = HKDF-SHA-256(ikm = k, salt = "", info = DS_ITEM)
|
|
48
|
+
*
|
|
49
|
+
* Domain-separated so `K_aead` never shares raw bytes with the caller's
|
|
50
|
+
* `HMAC_K(name)` record-id derivation. MUST match the Rust crate's
|
|
51
|
+
* `derive_item_key`.
|
|
52
|
+
*/
|
|
53
|
+
export async function deriveItemKey(k) {
|
|
54
|
+
const km = await crypto.subtle.importKey("raw", k, "HKDF", false, ["deriveBits"]);
|
|
55
|
+
const bits = await crypto.subtle.deriveBits({
|
|
56
|
+
name: "HKDF",
|
|
57
|
+
hash: "SHA-256",
|
|
58
|
+
salt: new Uint8Array(0),
|
|
59
|
+
info: DS_ITEM,
|
|
60
|
+
}, km, 256);
|
|
61
|
+
return new Uint8Array(bits);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Seal one record. Output layout: `suite(1) ‖ nonce(24) ‖ ciphertext ‖ tag(16)`.
|
|
65
|
+
* A fresh random 24-byte nonce is generated per call (Web Crypto).
|
|
66
|
+
*/
|
|
67
|
+
export async function sealRecord(k, ctx, plaintext) {
|
|
68
|
+
const kAead = await deriveItemKey(k);
|
|
69
|
+
const aad = recordAad(RECORD_SUITE_XCHACHA20POLY1305, ctx);
|
|
70
|
+
const sealed = aeadSeal(kAead, plaintext, aad); // nonce ‖ ct ‖ tag
|
|
71
|
+
const out = new Uint8Array(1 + sealed.byteLength);
|
|
72
|
+
out[0] = RECORD_SUITE_XCHACHA20POLY1305;
|
|
73
|
+
out.set(sealed, 1);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Unseal one record. Throws on any authentication failure (AAD mismatch and
|
|
78
|
+
* tag mismatch are indistinguishable — the same Poly1305 check) or on a
|
|
79
|
+
* structurally malformed blob (empty / unknown suite tag).
|
|
80
|
+
*/
|
|
81
|
+
export async function unsealRecord(k, ctx, sealed) {
|
|
82
|
+
if (sealed.byteLength < 1) {
|
|
83
|
+
throw new Error("unsealRecord: empty sealed blob");
|
|
84
|
+
}
|
|
85
|
+
const suite = sealed[0];
|
|
86
|
+
if (suite !== RECORD_SUITE_XCHACHA20POLY1305) {
|
|
87
|
+
throw new Error("unsealRecord: unknown suite tag");
|
|
88
|
+
}
|
|
89
|
+
const kAead = await deriveItemKey(k);
|
|
90
|
+
const aad = recordAad(suite, ctx);
|
|
91
|
+
return aeadOpen(kAead, sealed.slice(1), aad);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudp-protocol/authorizer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Authorizer-side primitives for SUDP (Secret-Use Delegation Protocol): canonical JSON, β computation, wrapping-key derivation, AEAD-as-wrap, plus a WebAuthn adapter under the ./webauthn subpath.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Miracle <xhyumiracle@gmail.com>",
|
package/src/aad.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const WRAP_VERSION = 0x0001;
|
|
|
15
15
|
* DS_WRAP ‖ credentialId ‖ ver_be(u16, big-endian)
|
|
16
16
|
*
|
|
17
17
|
* Bound as associated data when sealing/opening `K̂_c` under `W_c`, so a
|
|
18
|
-
*
|
|
18
|
+
* per-credential-wrapped record cannot be substituted across credentials or versions.
|
|
19
19
|
*/
|
|
20
20
|
export function wrapBindingAd(
|
|
21
21
|
credentialId: Uint8Array,
|
package/src/bytes.ts
CHANGED
|
@@ -23,6 +23,31 @@ export function u16beBytes(n: number): Uint8Array {
|
|
|
23
23
|
return new Uint8Array([(n >> 8) & 0xff, n & 0xff]);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export function u32beBytes(n: number): Uint8Array {
|
|
27
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xffffffff) {
|
|
28
|
+
throw new Error(`u32beBytes: out of range: ${n}`);
|
|
29
|
+
}
|
|
30
|
+
return new Uint8Array([(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 8-byte big-endian encoding of a `u64`. Takes a `bigint` so the full unsigned
|
|
35
|
+
* 64-bit range is exact (a `number` is only safe to 2^53). MUST match the Rust
|
|
36
|
+
* crate's `u64::to_be_bytes`.
|
|
37
|
+
*/
|
|
38
|
+
export function u64beBytes(n: bigint): Uint8Array {
|
|
39
|
+
if (n < 0n || n > 0xffffffffffffffffn) {
|
|
40
|
+
throw new Error(`u64beBytes: out of range: ${n}`);
|
|
41
|
+
}
|
|
42
|
+
const out = new Uint8Array(8);
|
|
43
|
+
let v = n;
|
|
44
|
+
for (let i = 7; i >= 0; i--) {
|
|
45
|
+
out[i] = Number(v & 0xffn);
|
|
46
|
+
v >>= 8n;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
const B64URL_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
27
52
|
const B64URL_LOOKUP = (() => {
|
|
28
53
|
const t = new Int8Array(256).fill(-1);
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ export {
|
|
|
15
15
|
utf8,
|
|
16
16
|
concatBytes,
|
|
17
17
|
u16beBytes,
|
|
18
|
+
u32beBytes,
|
|
19
|
+
u64beBytes,
|
|
18
20
|
bytesToB64Url,
|
|
19
21
|
b64UrlToBytes,
|
|
20
22
|
} from "./bytes.js";
|
|
@@ -25,3 +27,12 @@ export { computeBatchBinding, computeBinding, DS_BIND } from "./binding.js";
|
|
|
25
27
|
export { deriveWrappingKey } from "./kdf.js";
|
|
26
28
|
export { wrapBindingAd, sealAd, DS_WRAP, DS_SEAL, WRAP_VERSION } from "./aad.js";
|
|
27
29
|
export { aeadEncrypt, aeadSeal, aeadOpen } from "./aead.js";
|
|
30
|
+
export {
|
|
31
|
+
recordAad,
|
|
32
|
+
deriveItemKey,
|
|
33
|
+
sealRecord,
|
|
34
|
+
unsealRecord,
|
|
35
|
+
DS_ITEM,
|
|
36
|
+
RECORD_SUITE_XCHACHA20POLY1305,
|
|
37
|
+
type SealCtx,
|
|
38
|
+
} from "./record.js";
|
package/src/record.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { aeadOpen, aeadSeal } from "./aead.js";
|
|
2
|
+
import { concatBytes, u32beBytes, utf8 } from "./bytes.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-record seal/unseal — the per-item vault primitive, authorizer-side mirror
|
|
6
|
+
* of the Rust crate's `sudp::state::{seal_record, unseal_record}`.
|
|
7
|
+
*
|
|
8
|
+
* Eats bytes, spits bytes: the record's business structure, serialization,
|
|
9
|
+
* id derivation, version comparison, merge, tombstones, and GC are ALL the
|
|
10
|
+
* caller's concern. This module only seals one record under the vault key `K`
|
|
11
|
+
* bound — via AEAD associated data — to a structured {@link SealCtx}, so a
|
|
12
|
+
* record can never be opened under a different vault / id / version / domain.
|
|
13
|
+
*
|
|
14
|
+
* The AAD binding detects cross-vault splicing, cross-id substitution,
|
|
15
|
+
* version/ciphertext mismatch, and cross-domain confusion. It does NOT by
|
|
16
|
+
* itself prevent rollback (an attacker substituting a strictly older but
|
|
17
|
+
* validly-sealed record with its matching older `version`) — keep a monotonic
|
|
18
|
+
* per-id version store on the caller side, and put richer causal/merge metadata
|
|
19
|
+
* (HLC, device id, …) INSIDE the plaintext so the AEAD authenticates it too.
|
|
20
|
+
*
|
|
21
|
+
* Every byte layout here (canonical AAD, HKDF info, sealed framing) MUST stay
|
|
22
|
+
* byte-for-byte aligned with `custodian/rust/src/state/record.rs`; the
|
|
23
|
+
* conformance vectors in `test/conformance.test.ts` are pinned against the Rust
|
|
24
|
+
* `assert_eq!` anchors.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Per-record (per-item) domain-separation label. */
|
|
28
|
+
export const DS_ITEM = utf8("sudp/v1/item");
|
|
29
|
+
|
|
30
|
+
/** Suite/format tag: `0x01` = XChaCha20-Poly1305 + canonical AAD v1. */
|
|
31
|
+
export const RECORD_SUITE_XCHACHA20POLY1305 = 0x01;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Structured sealing context for one record. sudp builds the AEAD associated
|
|
35
|
+
* data from these fields; callers MUST NOT hand-assemble opaque AAD.
|
|
36
|
+
*/
|
|
37
|
+
export interface SealCtx {
|
|
38
|
+
/** Purpose separation within the per-record domain, e.g. `"item"` / `"keyset"`. */
|
|
39
|
+
domain: string;
|
|
40
|
+
/** Vault identifier (anti cross-vault splice). */
|
|
41
|
+
vault: Uint8Array;
|
|
42
|
+
/** Opaque record id — typically `HMAC_K(name)`; never interpreted (anti X-as-Y). */
|
|
43
|
+
id: Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Opaque, monotonic-per-id ordering value. sudp binds it into the AAD but
|
|
46
|
+
* never interprets it — meaning (server sequence number, Lamport/HLC stamp,
|
|
47
|
+
* version-vector, …) and the whole conflict-resolution strategy are the
|
|
48
|
+
* caller's. Recommended scalar encoding for the common case: `u64`
|
|
49
|
+
* big-endian (`u64beBytes(n)`). Binds the ciphertext to a version (detects
|
|
50
|
+
* mismatch, not rollback).
|
|
51
|
+
*/
|
|
52
|
+
version: Uint8Array;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Length-prefixed field: `len_be(u32) ‖ bytes`. */
|
|
56
|
+
function lp(field: Uint8Array): Uint8Array {
|
|
57
|
+
return concatBytes(u32beBytes(field.byteLength), field);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Canonical, length-prefixed associated data for a per-record seal:
|
|
62
|
+
*
|
|
63
|
+
* AAD = suite(1) ‖ lp(DS_ITEM) ‖ lp(domain_utf8) ‖ lp(vault) ‖ lp(id) ‖ lp(version)
|
|
64
|
+
* where lp(x) = len_be(x.length as u32, 4 bytes) ‖ x
|
|
65
|
+
*
|
|
66
|
+
* MUST be byte-identical to the Rust crate's `record_aad`.
|
|
67
|
+
*/
|
|
68
|
+
export function recordAad(suite: number, ctx: SealCtx): Uint8Array {
|
|
69
|
+
return concatBytes(
|
|
70
|
+
new Uint8Array([suite & 0xff]),
|
|
71
|
+
lp(DS_ITEM),
|
|
72
|
+
lp(utf8(ctx.domain)),
|
|
73
|
+
lp(ctx.vault),
|
|
74
|
+
lp(ctx.id),
|
|
75
|
+
lp(ctx.version),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Derive the per-record AEAD key `K_aead` from the vault key `k`:
|
|
81
|
+
*
|
|
82
|
+
* K_aead = HKDF-SHA-256(ikm = k, salt = "", info = DS_ITEM)
|
|
83
|
+
*
|
|
84
|
+
* Domain-separated so `K_aead` never shares raw bytes with the caller's
|
|
85
|
+
* `HMAC_K(name)` record-id derivation. MUST match the Rust crate's
|
|
86
|
+
* `derive_item_key`.
|
|
87
|
+
*/
|
|
88
|
+
export async function deriveItemKey(k: Uint8Array): Promise<Uint8Array> {
|
|
89
|
+
const km = await crypto.subtle.importKey(
|
|
90
|
+
"raw",
|
|
91
|
+
k as unknown as ArrayBuffer,
|
|
92
|
+
"HKDF",
|
|
93
|
+
false,
|
|
94
|
+
["deriveBits"],
|
|
95
|
+
);
|
|
96
|
+
const bits = await crypto.subtle.deriveBits(
|
|
97
|
+
{
|
|
98
|
+
name: "HKDF",
|
|
99
|
+
hash: "SHA-256",
|
|
100
|
+
salt: new Uint8Array(0) as unknown as ArrayBuffer,
|
|
101
|
+
info: DS_ITEM as unknown as ArrayBuffer,
|
|
102
|
+
},
|
|
103
|
+
km,
|
|
104
|
+
256,
|
|
105
|
+
);
|
|
106
|
+
return new Uint8Array(bits);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Seal one record. Output layout: `suite(1) ‖ nonce(24) ‖ ciphertext ‖ tag(16)`.
|
|
111
|
+
* A fresh random 24-byte nonce is generated per call (Web Crypto).
|
|
112
|
+
*/
|
|
113
|
+
export async function sealRecord(
|
|
114
|
+
k: Uint8Array,
|
|
115
|
+
ctx: SealCtx,
|
|
116
|
+
plaintext: Uint8Array,
|
|
117
|
+
): Promise<Uint8Array> {
|
|
118
|
+
const kAead = await deriveItemKey(k);
|
|
119
|
+
const aad = recordAad(RECORD_SUITE_XCHACHA20POLY1305, ctx);
|
|
120
|
+
const sealed = aeadSeal(kAead, plaintext, aad); // nonce ‖ ct ‖ tag
|
|
121
|
+
const out = new Uint8Array(1 + sealed.byteLength);
|
|
122
|
+
out[0] = RECORD_SUITE_XCHACHA20POLY1305;
|
|
123
|
+
out.set(sealed, 1);
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Unseal one record. Throws on any authentication failure (AAD mismatch and
|
|
129
|
+
* tag mismatch are indistinguishable — the same Poly1305 check) or on a
|
|
130
|
+
* structurally malformed blob (empty / unknown suite tag).
|
|
131
|
+
*/
|
|
132
|
+
export async function unsealRecord(
|
|
133
|
+
k: Uint8Array,
|
|
134
|
+
ctx: SealCtx,
|
|
135
|
+
sealed: Uint8Array,
|
|
136
|
+
): Promise<Uint8Array> {
|
|
137
|
+
if (sealed.byteLength < 1) {
|
|
138
|
+
throw new Error("unsealRecord: empty sealed blob");
|
|
139
|
+
}
|
|
140
|
+
const suite = sealed[0]!;
|
|
141
|
+
if (suite !== RECORD_SUITE_XCHACHA20POLY1305) {
|
|
142
|
+
throw new Error("unsealRecord: unknown suite tag");
|
|
143
|
+
}
|
|
144
|
+
const kAead = await deriveItemKey(k);
|
|
145
|
+
const aad = recordAad(suite, ctx);
|
|
146
|
+
return aeadOpen(kAead, sealed.slice(1), aad);
|
|
147
|
+
}
|