@sudp-protocol/authorizer 0.1.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 +93 -0
- package/dist/aad.d.ts +23 -0
- package/dist/aad.d.ts.map +1 -0
- package/dist/aad.js +28 -0
- package/dist/aad.js.map +1 -0
- package/dist/aead.d.ts +27 -0
- package/dist/aead.d.ts.map +1 -0
- package/dist/aead.js +49 -0
- package/dist/aead.js.map +1 -0
- package/dist/binding.d.ts +22 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +28 -0
- package/dist/binding.js.map +1 -0
- package/dist/bytes.d.ts +6 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +91 -0
- package/dist/bytes.js.map +1 -0
- package/dist/canonical.d.ts +18 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +53 -0
- package/dist/canonical.js.map +1 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +9 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/kdf.d.ts +16 -0
- package/dist/kdf.d.ts.map +1 -0
- package/dist/kdf.js +28 -0
- package/dist/kdf.js.map +1 -0
- package/dist/webauthn.d.ts +67 -0
- package/dist/webauthn.d.ts.map +1 -0
- package/dist/webauthn.js +61 -0
- package/dist/webauthn.js.map +1 -0
- package/package.json +56 -0
- package/src/aad.ts +34 -0
- package/src/aead.ts +65 -0
- package/src/binding.ts +33 -0
- package/src/bytes.ts +89 -0
- package/src/canonical.ts +54 -0
- package/src/hash.ts +8 -0
- package/src/index.ts +27 -0
- package/src/kdf.ts +43 -0
- package/src/webauthn.ts +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @sudp-protocol/authorizer
|
|
2
|
+
|
|
3
|
+
> Authorizer-side primitives for **SUDP** — the Secret-Use Delegation Protocol.
|
|
4
|
+
|
|
5
|
+
The Authorizer is the party that authorizes a secret-backed operation by
|
|
6
|
+
signing the binding hash `β = SHA-256(DS_BIND ‖ r ‖ H(canonical(o)))`
|
|
7
|
+
with an authenticator. This package ships the carrier-agnostic crypto the
|
|
8
|
+
Authorizer needs, plus an optional WebAuthn adapter.
|
|
9
|
+
|
|
10
|
+
The Rust counterpart that runs in the Custodian is the
|
|
11
|
+
[`sudp` crate](../../custodian/rust/) in the same repository. Both sides
|
|
12
|
+
must agree byte-for-byte on canonical encoding, β, the AEAD-as-wrap AAD
|
|
13
|
+
shapes, and the wrap-key derivation.
|
|
14
|
+
|
|
15
|
+
## Layout
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
@sudp-protocol/authorizer ← carrier-agnostic protocol primitives
|
|
19
|
+
canonicalize, sha256, computeBinding,
|
|
20
|
+
deriveWrappingKey, wrapBindingAd, sealAd,
|
|
21
|
+
aeadSeal, aeadOpen, base64url helpers,
|
|
22
|
+
DS_BIND / DS_WRAP / DS_SEAL constants
|
|
23
|
+
|
|
24
|
+
@sudp-protocol/authorizer/webauthn ← WebAuthn-specific adapter
|
|
25
|
+
prfToUserKey(prfOutput) → 32-byte y_c
|
|
26
|
+
assertionToWire(assertion) → wire-shape assertion
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Other authenticator realisations (YubiKey static, secure-enclave, HSM,
|
|
30
|
+
mock) bring their own adapters and do **not** touch the WebAuthn subpath.
|
|
31
|
+
The core remains agnostic of how `y_c` was produced.
|
|
32
|
+
|
|
33
|
+
## Usage sketch
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
computeBinding,
|
|
38
|
+
DS_BIND,
|
|
39
|
+
deriveWrappingKey,
|
|
40
|
+
wrapBindingAd,
|
|
41
|
+
aeadSeal,
|
|
42
|
+
} from "@sudp-protocol/authorizer";
|
|
43
|
+
import { prfToUserKey, assertionToWire } from "@sudp-protocol/authorizer/webauthn";
|
|
44
|
+
|
|
45
|
+
// 1) Authorizer-side: compute the binding hash β.
|
|
46
|
+
const beta = await computeBinding(DS_BIND, rFreshness, operation);
|
|
47
|
+
|
|
48
|
+
// 2) Run the WebAuthn ceremony with `beta` as the challenge. The PRF
|
|
49
|
+
// extension returns raw bytes — turn them into the 32-byte y_c.
|
|
50
|
+
const cred = (await navigator.credentials.get({
|
|
51
|
+
publicKey: {
|
|
52
|
+
challenge: beta,
|
|
53
|
+
extensions: { prf: { eval: { first: prfSalt } } },
|
|
54
|
+
/* ... rpId, allowCredentials, userVerification: "required", etc. */
|
|
55
|
+
} as PublicKeyCredentialRequestOptions,
|
|
56
|
+
})) as PublicKeyCredential;
|
|
57
|
+
|
|
58
|
+
const prfOut = new Uint8Array(
|
|
59
|
+
(cred.getClientExtensionResults() as { prf?: { results?: { first?: ArrayBuffer } } })
|
|
60
|
+
.prf!.results!.first!,
|
|
61
|
+
);
|
|
62
|
+
const yc = await prfToUserKey(prfOut);
|
|
63
|
+
|
|
64
|
+
// 3) Derive W_c and use it to wrap key material destined for the custodian.
|
|
65
|
+
const Wc = await deriveWrappingKey(yc, prfSalt, credentialId);
|
|
66
|
+
const wrapped = aeadSeal(Wc, plaintext, wrapBindingAd(credentialId));
|
|
67
|
+
|
|
68
|
+
// 4) Ship `{ assertionToWire(cred), wrapped, ... }` to the custodian as
|
|
69
|
+
// part of the grant.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## End-to-end protocol walkthrough
|
|
73
|
+
|
|
74
|
+
For how this package fits with the Rust Custodian and the Requester
|
|
75
|
+
across all three phases:
|
|
76
|
+
|
|
77
|
+
- Runnable, three-process demo over HTTP:
|
|
78
|
+
[`../../examples/protocol-demo/`](../../examples/protocol-demo/).
|
|
79
|
+
- Byte-level conformance map: the [top-level
|
|
80
|
+
README](../../README.md#cross-language-alignment).
|
|
81
|
+
- Authorizer-side walkthrough as a conformance test:
|
|
82
|
+
[`test/protocol_flow.test.ts`](test/protocol_flow.test.ts).
|
|
83
|
+
|
|
84
|
+
## Status
|
|
85
|
+
|
|
86
|
+
Pre-1.0, alongside the Rust crate. Wire format and trait shapes may move
|
|
87
|
+
before the 1.0 cut. See the
|
|
88
|
+
[top-level README](https://github.com/xhyumiracle/sudp) and the
|
|
89
|
+
[Rust crate's CHANGELOG](../../custodian/rust/CHANGELOG.md).
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
Apache-2.0. See [LICENSE](../../LICENSE).
|
package/dist/aad.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const DS_WRAP: Uint8Array<ArrayBufferLike>;
|
|
2
|
+
export declare const DS_SEAL: Uint8Array<ArrayBufferLike>;
|
|
3
|
+
/**
|
|
4
|
+
* Current wrap version. Bumped together on both ends of the protocol if the
|
|
5
|
+
* AAD shape ever changes.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WRAP_VERSION = 1;
|
|
8
|
+
/**
|
|
9
|
+
* Canonical AAD for the AEAD-as-wrap profile:
|
|
10
|
+
*
|
|
11
|
+
* DS_WRAP ‖ credentialId ‖ ver_be(u16, big-endian)
|
|
12
|
+
*
|
|
13
|
+
* Bound as associated data when sealing/opening `K̂_c` under `W_c`, so a
|
|
14
|
+
* peer-wrapped record cannot be substituted across credentials or versions.
|
|
15
|
+
*/
|
|
16
|
+
export declare function wrapBindingAd(credentialId: Uint8Array, wrapVersion?: number): Uint8Array;
|
|
17
|
+
/**
|
|
18
|
+
* Canonical AAD for the sealed-body AEAD layer:
|
|
19
|
+
*
|
|
20
|
+
* DS_SEAL ‖ ver_be(u16, big-endian)
|
|
21
|
+
*/
|
|
22
|
+
export declare function sealAd(wrapVersion?: number): Uint8Array;
|
|
23
|
+
//# sourceMappingURL=aad.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aad.d.ts","sourceRoot":"","sources":["../src/aad.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,6BAAuB,CAAC;AAC5C,eAAO,MAAM,OAAO,6BAAuB,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,YAAY,IAAS,CAAC;AAEnC;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,UAAU,EACxB,WAAW,GAAE,MAAqB,GACjC,UAAU,CAEZ;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,WAAW,GAAE,MAAqB,GAAG,UAAU,CAErE"}
|
package/dist/aad.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { concatBytes, u16beBytes, utf8 } from "./bytes.js";
|
|
2
|
+
export const DS_WRAP = utf8("sudp/v1/wrap");
|
|
3
|
+
export const DS_SEAL = utf8("sudp/v1/seal");
|
|
4
|
+
/**
|
|
5
|
+
* Current wrap version. Bumped together on both ends of the protocol if the
|
|
6
|
+
* AAD shape ever changes.
|
|
7
|
+
*/
|
|
8
|
+
export const WRAP_VERSION = 0x0001;
|
|
9
|
+
/**
|
|
10
|
+
* Canonical AAD for the AEAD-as-wrap profile:
|
|
11
|
+
*
|
|
12
|
+
* DS_WRAP ‖ credentialId ‖ ver_be(u16, big-endian)
|
|
13
|
+
*
|
|
14
|
+
* Bound as associated data when sealing/opening `K̂_c` under `W_c`, so a
|
|
15
|
+
* peer-wrapped record cannot be substituted across credentials or versions.
|
|
16
|
+
*/
|
|
17
|
+
export function wrapBindingAd(credentialId, wrapVersion = WRAP_VERSION) {
|
|
18
|
+
return concatBytes(DS_WRAP, credentialId, u16beBytes(wrapVersion));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Canonical AAD for the sealed-body AEAD layer:
|
|
22
|
+
*
|
|
23
|
+
* DS_SEAL ‖ ver_be(u16, big-endian)
|
|
24
|
+
*/
|
|
25
|
+
export function sealAd(wrapVersion = WRAP_VERSION) {
|
|
26
|
+
return concatBytes(DS_SEAL, u16beBytes(wrapVersion));
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=aad.js.map
|
package/dist/aad.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aad.js","sourceRoot":"","sources":["../src/aad.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;AAC5C,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;AAE5C;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,YAAwB,EACxB,cAAsB,YAAY;IAElC,OAAO,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,cAAsB,YAAY;IACvD,OAAO,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/aead.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XChaCha20-Poly1305 raw encrypt with a caller-supplied nonce.
|
|
3
|
+
*
|
|
4
|
+
* Output is `ciphertext ‖ tag` (no nonce prefix). Use {@link aeadSeal} for
|
|
5
|
+
* the standard SUDP wire format that prepends a freshly random nonce.
|
|
6
|
+
*
|
|
7
|
+
* MUST stay byte-for-byte aligned with the Rust crate's
|
|
8
|
+
* `sudp::primitives::Aead::encrypt`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function aeadEncrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
11
|
+
/**
|
|
12
|
+
* XChaCha20-Poly1305 thin wrapper.
|
|
13
|
+
*
|
|
14
|
+
* Wire layout (MUST match `sudp::primitives::Aead::seal`):
|
|
15
|
+
*
|
|
16
|
+
* nonce(24 bytes) ‖ ciphertext ‖ tag(16 bytes)
|
|
17
|
+
*
|
|
18
|
+
* The 24-byte nonce is freshly generated per call. The caller supplies the
|
|
19
|
+
* canonical AAD (see {@link wrapBindingAd}, {@link sealAd}).
|
|
20
|
+
*/
|
|
21
|
+
export declare function aeadSeal(key: Uint8Array, plaintext: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
22
|
+
/**
|
|
23
|
+
* Counterpart to {@link aeadSeal}. Throws if the AAD/nonce/ciphertext are
|
|
24
|
+
* not authentic under `key`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function aeadOpen(key: Uint8Array, sealed: Uint8Array, aad: Uint8Array): Uint8Array;
|
|
27
|
+
//# sourceMappingURL=aead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aead.d.ts","sourceRoot":"","sources":["../src/aead.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,UAAU,GACd,UAAU,CAKZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,UAAU,GACd,UAAU,CAOZ;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,UAAU,GACd,UAAU,CAOZ"}
|
package/dist/aead.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
2
|
+
const XNONCE_LEN = 24;
|
|
3
|
+
const XTAG_LEN = 16;
|
|
4
|
+
/**
|
|
5
|
+
* XChaCha20-Poly1305 raw encrypt with a caller-supplied nonce.
|
|
6
|
+
*
|
|
7
|
+
* Output is `ciphertext ‖ tag` (no nonce prefix). Use {@link aeadSeal} for
|
|
8
|
+
* the standard SUDP wire format that prepends a freshly random nonce.
|
|
9
|
+
*
|
|
10
|
+
* MUST stay byte-for-byte aligned with the Rust crate's
|
|
11
|
+
* `sudp::primitives::Aead::encrypt`.
|
|
12
|
+
*/
|
|
13
|
+
export function aeadEncrypt(key, nonce, plaintext, aad) {
|
|
14
|
+
if (nonce.byteLength !== XNONCE_LEN) {
|
|
15
|
+
throw new Error(`aeadEncrypt: nonce must be ${XNONCE_LEN} bytes, got ${nonce.byteLength}`);
|
|
16
|
+
}
|
|
17
|
+
return xchacha20poly1305(key, nonce, aad).encrypt(plaintext);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* XChaCha20-Poly1305 thin wrapper.
|
|
21
|
+
*
|
|
22
|
+
* Wire layout (MUST match `sudp::primitives::Aead::seal`):
|
|
23
|
+
*
|
|
24
|
+
* nonce(24 bytes) ‖ ciphertext ‖ tag(16 bytes)
|
|
25
|
+
*
|
|
26
|
+
* The 24-byte nonce is freshly generated per call. The caller supplies the
|
|
27
|
+
* canonical AAD (see {@link wrapBindingAd}, {@link sealAd}).
|
|
28
|
+
*/
|
|
29
|
+
export function aeadSeal(key, plaintext, aad) {
|
|
30
|
+
const nonce = crypto.getRandomValues(new Uint8Array(XNONCE_LEN));
|
|
31
|
+
const ct = aeadEncrypt(key, nonce, plaintext, aad);
|
|
32
|
+
const out = new Uint8Array(XNONCE_LEN + ct.byteLength);
|
|
33
|
+
out.set(nonce, 0);
|
|
34
|
+
out.set(ct, XNONCE_LEN);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Counterpart to {@link aeadSeal}. Throws if the AAD/nonce/ciphertext are
|
|
39
|
+
* not authentic under `key`.
|
|
40
|
+
*/
|
|
41
|
+
export function aeadOpen(key, sealed, aad) {
|
|
42
|
+
if (sealed.byteLength <= XNONCE_LEN + XTAG_LEN) {
|
|
43
|
+
throw new Error("aeadOpen: sealed blob is too short");
|
|
44
|
+
}
|
|
45
|
+
const nonce = sealed.slice(0, XNONCE_LEN);
|
|
46
|
+
const ct = sealed.slice(XNONCE_LEN);
|
|
47
|
+
return xchacha20poly1305(key, nonce, aad).decrypt(ct);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=aead.js.map
|
package/dist/aead.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aead.js","sourceRoot":"","sources":["../src/aead.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,QAAQ,GAAG,EAAE,CAAC;AAEpB;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,GAAe,EACf,KAAiB,EACjB,SAAqB,EACrB,GAAe;IAEf,IAAI,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,8BAA8B,UAAU,eAAe,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAe,EACf,SAAqB,EACrB,GAAe;IAEf,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IACjE,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;IACvD,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAClB,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IACxB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAe,EACf,MAAkB,EAClB,GAAe;IAEf,IAAI,MAAM,CAAC,UAAU,IAAI,UAAU,GAAG,QAAQ,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACpC,OAAO,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain separation tag for the Phase II.2 binding hash. Matches the Rust
|
|
3
|
+
* crate's `sudp::primitives::domain::DS_BIND` byte-for-byte.
|
|
4
|
+
*
|
|
5
|
+
* β = SHA-256(DS_BIND ‖ r ‖ SHA-256(canonical(o)))
|
|
6
|
+
*/
|
|
7
|
+
export declare const DS_BIND: Uint8Array<ArrayBufferLike>;
|
|
8
|
+
/**
|
|
9
|
+
* Compute the binding hash `β` for a given operation, freshness `r`, and
|
|
10
|
+
* domain separation tag.
|
|
11
|
+
*
|
|
12
|
+
* β = SHA-256(domain ‖ r ‖ SHA-256(canonical(op)))
|
|
13
|
+
*
|
|
14
|
+
* The Authorizer signs `β` with its authenticator. The custodian recomputes
|
|
15
|
+
* `β` from the redeemed grant's `(o, r)` and verifies the signature.
|
|
16
|
+
*
|
|
17
|
+
* Pass {@link DS_BIND} for the default profile; other domains may be used
|
|
18
|
+
* by adjacent ceremonies (e.g. setup attestation) and live in the
|
|
19
|
+
* deployment.
|
|
20
|
+
*/
|
|
21
|
+
export declare function computeBinding(domain: Uint8Array, r: Uint8Array, op: unknown): Promise<Uint8Array>;
|
|
22
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,eAAO,MAAM,OAAO,6BAAuB,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,CAAC,EAAE,UAAU,EACb,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,UAAU,CAAC,CAGrB"}
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { concatBytes, utf8 } from "./bytes.js";
|
|
2
|
+
import { canonicalize } from "./canonical.js";
|
|
3
|
+
import { sha256 } from "./hash.js";
|
|
4
|
+
/**
|
|
5
|
+
* Domain separation tag for the Phase II.2 binding hash. Matches the Rust
|
|
6
|
+
* crate's `sudp::primitives::domain::DS_BIND` byte-for-byte.
|
|
7
|
+
*
|
|
8
|
+
* β = SHA-256(DS_BIND ‖ r ‖ SHA-256(canonical(o)))
|
|
9
|
+
*/
|
|
10
|
+
export const DS_BIND = utf8("sudp/v1/bind");
|
|
11
|
+
/**
|
|
12
|
+
* Compute the binding hash `β` for a given operation, freshness `r`, and
|
|
13
|
+
* domain separation tag.
|
|
14
|
+
*
|
|
15
|
+
* β = SHA-256(domain ‖ r ‖ SHA-256(canonical(op)))
|
|
16
|
+
*
|
|
17
|
+
* The Authorizer signs `β` with its authenticator. The custodian recomputes
|
|
18
|
+
* `β` from the redeemed grant's `(o, r)` and verifies the signature.
|
|
19
|
+
*
|
|
20
|
+
* Pass {@link DS_BIND} for the default profile; other domains may be used
|
|
21
|
+
* by adjacent ceremonies (e.g. setup attestation) and live in the
|
|
22
|
+
* deployment.
|
|
23
|
+
*/
|
|
24
|
+
export async function computeBinding(domain, r, op) {
|
|
25
|
+
const opHash = await sha256(canonicalize(op));
|
|
26
|
+
return sha256(concatBytes(domain, r, opHash));
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAkB,EAClB,CAAa,EACb,EAAW;IAEX,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/bytes.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function utf8(s: string): Uint8Array;
|
|
2
|
+
export declare function concatBytes(...parts: readonly Uint8Array[]): Uint8Array;
|
|
3
|
+
export declare function u16beBytes(n: number): Uint8Array;
|
|
4
|
+
export declare function bytesToB64Url(b: Uint8Array): string;
|
|
5
|
+
export declare function b64UrlToBytes(s: string): Uint8Array;
|
|
6
|
+
//# sourceMappingURL=bytes.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/bytes.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const enc = new TextEncoder();
|
|
2
|
+
export function utf8(s) {
|
|
3
|
+
return enc.encode(s);
|
|
4
|
+
}
|
|
5
|
+
export function concatBytes(...parts) {
|
|
6
|
+
let total = 0;
|
|
7
|
+
for (const p of parts)
|
|
8
|
+
total += p.byteLength;
|
|
9
|
+
const out = new Uint8Array(total);
|
|
10
|
+
let off = 0;
|
|
11
|
+
for (const p of parts) {
|
|
12
|
+
out.set(p, off);
|
|
13
|
+
off += p.byteLength;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
export function u16beBytes(n) {
|
|
18
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xffff) {
|
|
19
|
+
throw new Error(`u16beBytes: out of range: ${n}`);
|
|
20
|
+
}
|
|
21
|
+
return new Uint8Array([(n >> 8) & 0xff, n & 0xff]);
|
|
22
|
+
}
|
|
23
|
+
const B64URL_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
24
|
+
const B64URL_LOOKUP = (() => {
|
|
25
|
+
const t = new Int8Array(256).fill(-1);
|
|
26
|
+
for (let i = 0; i < B64URL_ALPHA.length; i++)
|
|
27
|
+
t[B64URL_ALPHA.charCodeAt(i)] = i;
|
|
28
|
+
return t;
|
|
29
|
+
})();
|
|
30
|
+
function alpha(i) {
|
|
31
|
+
// Index is always masked to 0..63, so B64URL_ALPHA[i] is defined.
|
|
32
|
+
return B64URL_ALPHA[i];
|
|
33
|
+
}
|
|
34
|
+
export function bytesToB64Url(b) {
|
|
35
|
+
let out = "";
|
|
36
|
+
let i = 0;
|
|
37
|
+
for (; i + 3 <= b.length; i += 3) {
|
|
38
|
+
const x = (b[i] << 16) | (b[i + 1] << 8) | b[i + 2];
|
|
39
|
+
out += alpha((x >> 18) & 0x3f) + alpha((x >> 12) & 0x3f) + alpha((x >> 6) & 0x3f) + alpha(x & 0x3f);
|
|
40
|
+
}
|
|
41
|
+
const rem = b.length - i;
|
|
42
|
+
if (rem === 1) {
|
|
43
|
+
const x = b[i] << 16;
|
|
44
|
+
out += alpha((x >> 18) & 0x3f) + alpha((x >> 12) & 0x3f);
|
|
45
|
+
}
|
|
46
|
+
else if (rem === 2) {
|
|
47
|
+
const x = (b[i] << 16) | (b[i + 1] << 8);
|
|
48
|
+
out += alpha((x >> 18) & 0x3f) + alpha((x >> 12) & 0x3f) + alpha((x >> 6) & 0x3f);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
export function b64UrlToBytes(s) {
|
|
53
|
+
const norm = s.replace(/=+$/, "");
|
|
54
|
+
const len = norm.length;
|
|
55
|
+
const fullGroups = Math.floor(len / 4);
|
|
56
|
+
const rem = len - fullGroups * 4;
|
|
57
|
+
if (rem === 1)
|
|
58
|
+
throw new Error("b64UrlToBytes: invalid length");
|
|
59
|
+
const out = new Uint8Array(fullGroups * 3 + (rem === 0 ? 0 : rem - 1));
|
|
60
|
+
let outOff = 0;
|
|
61
|
+
let i = 0;
|
|
62
|
+
for (; i + 4 <= len; i += 4) {
|
|
63
|
+
const a = B64URL_LOOKUP[norm.charCodeAt(i)];
|
|
64
|
+
const b = B64URL_LOOKUP[norm.charCodeAt(i + 1)];
|
|
65
|
+
const c = B64URL_LOOKUP[norm.charCodeAt(i + 2)];
|
|
66
|
+
const d = B64URL_LOOKUP[norm.charCodeAt(i + 3)];
|
|
67
|
+
if ((a | b | c | d) < 0)
|
|
68
|
+
throw new Error("b64UrlToBytes: invalid character");
|
|
69
|
+
out[outOff++] = (a << 2) | (b >> 4);
|
|
70
|
+
out[outOff++] = ((b & 0x0f) << 4) | (c >> 2);
|
|
71
|
+
out[outOff++] = ((c & 0x03) << 6) | d;
|
|
72
|
+
}
|
|
73
|
+
if (rem === 2) {
|
|
74
|
+
const a = B64URL_LOOKUP[norm.charCodeAt(i)];
|
|
75
|
+
const b = B64URL_LOOKUP[norm.charCodeAt(i + 1)];
|
|
76
|
+
if ((a | b) < 0)
|
|
77
|
+
throw new Error("b64UrlToBytes: invalid character");
|
|
78
|
+
out[outOff++] = (a << 2) | (b >> 4);
|
|
79
|
+
}
|
|
80
|
+
else if (rem === 3) {
|
|
81
|
+
const a = B64URL_LOOKUP[norm.charCodeAt(i)];
|
|
82
|
+
const b = B64URL_LOOKUP[norm.charCodeAt(i + 1)];
|
|
83
|
+
const c = B64URL_LOOKUP[norm.charCodeAt(i + 2)];
|
|
84
|
+
if ((a | b | c) < 0)
|
|
85
|
+
throw new Error("b64UrlToBytes: invalid character");
|
|
86
|
+
out[outOff++] = (a << 2) | (b >> 4);
|
|
87
|
+
out[outOff++] = ((b & 0x0f) << 4) | (c >> 2);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=bytes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytes.js","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JCS-style canonical JSON encoder (RFC 8785 subset).
|
|
3
|
+
*
|
|
4
|
+
* Properties:
|
|
5
|
+
* - Object keys are sorted lexicographically by UTF-16 code unit.
|
|
6
|
+
* - Strings use `JSON.stringify` escaping.
|
|
7
|
+
* - Numbers must be finite. Floats are rejected — they have no
|
|
8
|
+
* byte-reproducible canonical form across endpoints and would be a
|
|
9
|
+
* substitution vector against `H(o)`. Use integers, strings, booleans,
|
|
10
|
+
* nulls, arrays, or nested objects.
|
|
11
|
+
* - `undefined` is treated as `null`.
|
|
12
|
+
*
|
|
13
|
+
* This MUST stay byte-for-byte aligned with the Rust crate's
|
|
14
|
+
* `sudp::canonical::canonicalize_strict`. The `protocol/test_vectors/`
|
|
15
|
+
* directory carries the conformance suite.
|
|
16
|
+
*/
|
|
17
|
+
export declare function canonicalize(value: unknown): Uint8Array;
|
|
18
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAEvD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { utf8 } from "./bytes.js";
|
|
2
|
+
/**
|
|
3
|
+
* JCS-style canonical JSON encoder (RFC 8785 subset).
|
|
4
|
+
*
|
|
5
|
+
* Properties:
|
|
6
|
+
* - Object keys are sorted lexicographically by UTF-16 code unit.
|
|
7
|
+
* - Strings use `JSON.stringify` escaping.
|
|
8
|
+
* - Numbers must be finite. Floats are rejected — they have no
|
|
9
|
+
* byte-reproducible canonical form across endpoints and would be a
|
|
10
|
+
* substitution vector against `H(o)`. Use integers, strings, booleans,
|
|
11
|
+
* nulls, arrays, or nested objects.
|
|
12
|
+
* - `undefined` is treated as `null`.
|
|
13
|
+
*
|
|
14
|
+
* This MUST stay byte-for-byte aligned with the Rust crate's
|
|
15
|
+
* `sudp::canonical::canonicalize_strict`. The `protocol/test_vectors/`
|
|
16
|
+
* directory carries the conformance suite.
|
|
17
|
+
*/
|
|
18
|
+
export function canonicalize(value) {
|
|
19
|
+
return utf8(canonicalizeStr(value));
|
|
20
|
+
}
|
|
21
|
+
function canonicalizeStr(v) {
|
|
22
|
+
if (v === null || v === undefined)
|
|
23
|
+
return "null";
|
|
24
|
+
if (typeof v === "boolean")
|
|
25
|
+
return v ? "true" : "false";
|
|
26
|
+
if (typeof v === "number") {
|
|
27
|
+
if (!Number.isFinite(v)) {
|
|
28
|
+
throw new Error("canonicalize: non-finite number is not allowed");
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isInteger(v)) {
|
|
31
|
+
throw new Error("canonicalize: float values are rejected (no byte-reproducible canonical form)");
|
|
32
|
+
}
|
|
33
|
+
return v.toString();
|
|
34
|
+
}
|
|
35
|
+
if (typeof v === "bigint")
|
|
36
|
+
return v.toString();
|
|
37
|
+
if (typeof v === "string")
|
|
38
|
+
return JSON.stringify(v);
|
|
39
|
+
if (Array.isArray(v)) {
|
|
40
|
+
return "[" + v.map(canonicalizeStr).join(",") + "]";
|
|
41
|
+
}
|
|
42
|
+
if (typeof v === "object") {
|
|
43
|
+
const obj = v;
|
|
44
|
+
const keys = Object.keys(obj).sort();
|
|
45
|
+
return ("{" +
|
|
46
|
+
keys
|
|
47
|
+
.map((k) => JSON.stringify(k) + ":" + canonicalizeStr(obj[k]))
|
|
48
|
+
.join(",") +
|
|
49
|
+
"}");
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`canonicalize: unsupported value type: ${typeof v}`);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,eAAe,CAAC,CAAU;IACjC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACjD,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IACxD,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACtD,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,CAA4B,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,CACL,GAAG;YACH,IAAI;iBACD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;iBAC7D,IAAI,CAAC,GAAG,CAAC;YACZ,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,yCAAyC,OAAO,CAAC,EAAE,CAAC,CAAC;AACvE,CAAC"}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAIlE"}
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 over a byte buffer. Thin wrapper over the platform's WebCrypto.
|
|
3
|
+
*/
|
|
4
|
+
export async function sha256(data) {
|
|
5
|
+
// `crypto.subtle` is available in modern browsers and Node >= 20.
|
|
6
|
+
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
7
|
+
return new Uint8Array(buf);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=hash.js.map
|
package/dist/hash.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAgB;IAC3C,kEAAkE;IAClE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAA8B,CAAC,CAAC;IAClF,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@sudp-protocol/authorizer` — Authorizer-side primitives for the
|
|
3
|
+
* Secret-Use Delegation Protocol.
|
|
4
|
+
*
|
|
5
|
+
* This entry point is **carrier-agnostic**: it carries only the protocol
|
|
6
|
+
* cryptography (canonical JSON, β computation, wrapping-key derivation,
|
|
7
|
+
* AEAD-as-wrap) and intentionally does **not** know about WebAuthn,
|
|
8
|
+
* passkeys, HTTP, or any specific authenticator.
|
|
9
|
+
*
|
|
10
|
+
* For the WebAuthn PRF → y_c adapter and assertion helpers, import from
|
|
11
|
+
* `@sudp-protocol/authorizer/webauthn`.
|
|
12
|
+
*/
|
|
13
|
+
export { utf8, concatBytes, u16beBytes, bytesToB64Url, b64UrlToBytes, } from "./bytes.js";
|
|
14
|
+
export { canonicalize } from "./canonical.js";
|
|
15
|
+
export { sha256 } from "./hash.js";
|
|
16
|
+
export { computeBinding, DS_BIND } from "./binding.js";
|
|
17
|
+
export { deriveWrappingKey } from "./kdf.js";
|
|
18
|
+
export { wrapBindingAd, sealAd, DS_WRAP, DS_SEAL, WRAP_VERSION } from "./aad.js";
|
|
19
|
+
export { aeadEncrypt, aeadSeal, aeadOpen } from "./aead.js";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvD,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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@sudp-protocol/authorizer` — Authorizer-side primitives for the
|
|
3
|
+
* Secret-Use Delegation Protocol.
|
|
4
|
+
*
|
|
5
|
+
* This entry point is **carrier-agnostic**: it carries only the protocol
|
|
6
|
+
* cryptography (canonical JSON, β computation, wrapping-key derivation,
|
|
7
|
+
* AEAD-as-wrap) and intentionally does **not** know about WebAuthn,
|
|
8
|
+
* passkeys, HTTP, or any specific authenticator.
|
|
9
|
+
*
|
|
10
|
+
* For the WebAuthn PRF → y_c adapter and assertion helpers, import from
|
|
11
|
+
* `@sudp-protocol/authorizer/webauthn`.
|
|
12
|
+
*/
|
|
13
|
+
export { utf8, concatBytes, u16beBytes, bytesToB64Url, b64UrlToBytes, } from "./bytes.js";
|
|
14
|
+
export { canonicalize } from "./canonical.js";
|
|
15
|
+
export { sha256 } from "./hash.js";
|
|
16
|
+
export { computeBinding, DS_BIND } from "./binding.js";
|
|
17
|
+
export { deriveWrappingKey } from "./kdf.js";
|
|
18
|
+
export { wrapBindingAd, sealAd, DS_WRAP, DS_SEAL, WRAP_VERSION } from "./aad.js";
|
|
19
|
+
export { aeadEncrypt, aeadSeal, aeadOpen } from "./aead.js";
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvD,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"}
|
package/dist/kdf.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the per-credential wrapping key `W_c` from the Authorizer's
|
|
3
|
+
* user-key `y_c` (a 32-byte secret produced by the authenticator).
|
|
4
|
+
*
|
|
5
|
+
* W_c = HKDF-SHA-256(y_c, salt = prf_salt, info = DS_WRAP ‖ credId ‖ ver_be)
|
|
6
|
+
*
|
|
7
|
+
* `y_c` must arrive at the Authorizer side already shaped to 32 bytes; how
|
|
8
|
+
* it is produced is authenticator-specific and outside the SUDP core (see
|
|
9
|
+
* `./webauthn` for the WebAuthn PRF → y_c adapter, but custom authenticators
|
|
10
|
+
* may provide y_c directly).
|
|
11
|
+
*
|
|
12
|
+
* MUST stay byte-for-byte aligned with the Rust crate's
|
|
13
|
+
* `sudp::crypto::kdf::derive_wrapping_key`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deriveWrappingKey(userKey: Uint8Array, prfSalt: Uint8Array, credentialId: Uint8Array, wrapVersion?: number): Promise<Uint8Array>;
|
|
16
|
+
//# sourceMappingURL=kdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kdf.d.ts","sourceRoot":"","sources":["../src/kdf.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,UAAU,EACnB,YAAY,EAAE,UAAU,EACxB,WAAW,GAAE,MAAqB,GACjC,OAAO,CAAC,UAAU,CAAC,CAoBrB"}
|
package/dist/kdf.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { concatBytes, u16beBytes } from "./bytes.js";
|
|
2
|
+
import { DS_WRAP, WRAP_VERSION } from "./aad.js";
|
|
3
|
+
/**
|
|
4
|
+
* Derive the per-credential wrapping key `W_c` from the Authorizer's
|
|
5
|
+
* user-key `y_c` (a 32-byte secret produced by the authenticator).
|
|
6
|
+
*
|
|
7
|
+
* W_c = HKDF-SHA-256(y_c, salt = prf_salt, info = DS_WRAP ‖ credId ‖ ver_be)
|
|
8
|
+
*
|
|
9
|
+
* `y_c` must arrive at the Authorizer side already shaped to 32 bytes; how
|
|
10
|
+
* it is produced is authenticator-specific and outside the SUDP core (see
|
|
11
|
+
* `./webauthn` for the WebAuthn PRF → y_c adapter, but custom authenticators
|
|
12
|
+
* may provide y_c directly).
|
|
13
|
+
*
|
|
14
|
+
* MUST stay byte-for-byte aligned with the Rust crate's
|
|
15
|
+
* `sudp::crypto::kdf::derive_wrapping_key`.
|
|
16
|
+
*/
|
|
17
|
+
export async function deriveWrappingKey(userKey, prfSalt, credentialId, wrapVersion = WRAP_VERSION) {
|
|
18
|
+
const km = await crypto.subtle.importKey("raw", userKey, "HKDF", false, ["deriveBits"]);
|
|
19
|
+
const info = concatBytes(DS_WRAP, credentialId, u16beBytes(wrapVersion));
|
|
20
|
+
const bits = await crypto.subtle.deriveBits({
|
|
21
|
+
name: "HKDF",
|
|
22
|
+
hash: "SHA-256",
|
|
23
|
+
salt: prfSalt,
|
|
24
|
+
info: info,
|
|
25
|
+
}, km, 256);
|
|
26
|
+
return new Uint8Array(bits);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=kdf.js.map
|
package/dist/kdf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kdf.js","sourceRoot":"","sources":["../src/kdf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAmB,EACnB,OAAmB,EACnB,YAAwB,EACxB,cAAsB,YAAY;IAElC,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACtC,KAAK,EACL,OAAiC,EACjC,MAAM,EACN,KAAK,EACL,CAAC,YAAY,CAAC,CACf,CAAC;IACF,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CACzC;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAiC;QACvC,IAAI,EAAE,IAA8B;KACrC,EACD,EAAE,EACF,GAAG,CACJ,CAAC;IACF,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC"}
|