@bradford-tech/supabase-integrity-attest 0.3.2 → 0.5.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/esm/assertion.d.ts +5 -2
- package/esm/assertion.d.ts.map +1 -1
- package/esm/assertion.js +5 -2
- package/esm/attestation.d.ts +6 -1
- package/esm/attestation.d.ts.map +1 -1
- package/esm/attestation.js +6 -1
- package/esm/mod.d.ts +84 -4
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +84 -4
- package/esm/src/attestation.d.ts +9 -1
- package/esm/src/attestation.d.ts.map +1 -1
- package/esm/src/attestation.js +14 -6
- package/esm/src/errors.d.ts +28 -3
- package/esm/src/errors.d.ts.map +1 -1
- package/esm/src/errors.js +25 -2
- package/esm/src/utils.d.ts.map +1 -1
- package/esm/src/utils.js +3 -5
- package/esm/src/with-assertion.d.ts +41 -3
- package/esm/src/with-assertion.d.ts.map +1 -1
- package/esm/src/with-assertion.js +31 -9
- package/esm/src/with-attestation.d.ts +79 -0
- package/esm/src/with-attestation.d.ts.map +1 -0
- package/esm/src/with-attestation.js +147 -0
- package/package.json +4 -7
- package/esm/_dnt.test_polyfills.d.ts.map +0 -1
- package/esm/_dnt.test_shims.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/diff.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/format.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/styles.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +0 -1
- package/esm/src/cose.d.ts.map +0 -1
- package/esm/tests/assertion-entry.test.d.ts.map +0 -1
- package/esm/tests/assertion.test.d.ts.map +0 -1
- package/esm/tests/attestation-entry.test.d.ts.map +0 -1
- package/esm/tests/attestation.test.d.ts.map +0 -1
- package/esm/tests/authdata.test.d.ts.map +0 -1
- package/esm/tests/certificate.test.d.ts.map +0 -1
- package/esm/tests/cose.test.d.ts.map +0 -1
- package/esm/tests/der.test.d.ts.map +0 -1
- package/esm/tests/errors.test.d.ts.map +0 -1
- package/esm/tests/fixtures/apple-attestation.d.ts.map +0 -1
- package/esm/tests/fixtures/generate-assertion.d.ts.map +0 -1
- package/esm/tests/utils.test.d.ts.map +0 -1
- package/esm/tests/with-assertion.test.d.ts.map +0 -1
package/esm/assertion.d.ts
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* `@noble/curves`, keeping the bundle minimal for assertion-only use cases.
|
|
4
4
|
*
|
|
5
5
|
* ```ts
|
|
6
|
-
* import {
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAssertion,
|
|
8
|
+
* withAssertion,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/assertion";
|
|
7
10
|
*
|
|
8
11
|
* const { signCount } = await verifyAssertion(
|
|
9
12
|
* { appId: "TEAMID.com.example.app" },
|
|
@@ -21,5 +24,5 @@ export type { AppInfo, AssertionResult } from "./src/assertion.js";
|
|
|
21
24
|
export { AssertionError, AssertionErrorCode } from "./src/errors.js";
|
|
22
25
|
export { withAssertion } from "./src/with-assertion.js";
|
|
23
26
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
|
24
|
-
export type { AssertionContext, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
27
|
+
export type { AssertionContext, AssertionTimings, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
25
28
|
//# sourceMappingURL=assertion.d.ts.map
|
package/esm/assertion.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../src/assertion.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../src/assertion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGrE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC"}
|
package/esm/assertion.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* `@noble/curves`, keeping the bundle minimal for assertion-only use cases.
|
|
4
4
|
*
|
|
5
5
|
* ```ts
|
|
6
|
-
* import {
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAssertion,
|
|
8
|
+
* withAssertion,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/assertion";
|
|
7
10
|
*
|
|
8
11
|
* const { signCount } = await verifyAssertion(
|
|
9
12
|
* { appId: "TEAMID.com.example.app" },
|
|
@@ -18,6 +21,6 @@
|
|
|
18
21
|
*/
|
|
19
22
|
export { verifyAssertion } from "./src/assertion.js";
|
|
20
23
|
export { AssertionError, AssertionErrorCode } from "./src/errors.js";
|
|
21
|
-
// withAssertion
|
|
24
|
+
// withAssertion middleware
|
|
22
25
|
export { withAssertion } from "./src/with-assertion.js";
|
|
23
26
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
package/esm/attestation.d.ts
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* dependencies (`asn1js`, `@noble/curves`).
|
|
4
4
|
*
|
|
5
5
|
* ```ts
|
|
6
|
-
* import {
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAttestation,
|
|
8
|
+
* withAttestation,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/attestation";
|
|
7
10
|
*
|
|
8
11
|
* const { publicKeyPem } = await verifyAttestation(
|
|
9
12
|
* { appId: "TEAMID.com.example.app" },
|
|
@@ -18,4 +21,6 @@
|
|
|
18
21
|
export { verifyAttestation } from "./src/attestation.js";
|
|
19
22
|
export type { AppInfo, AttestationResult, VerifyAttestationOptions, } from "./src/attestation.js";
|
|
20
23
|
export { AttestationError, AttestationErrorCode } from "./src/errors.js";
|
|
24
|
+
export { withAttestation } from "./src/with-attestation.js";
|
|
25
|
+
export type { AttestationContext, AttestationTimings, ExtractAttestationFn, WithAttestationOptions, } from "./src/with-attestation.js";
|
|
21
26
|
//# sourceMappingURL=attestation.d.ts.map
|
package/esm/attestation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGzE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC"}
|
package/esm/attestation.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* dependencies (`asn1js`, `@noble/curves`).
|
|
4
4
|
*
|
|
5
5
|
* ```ts
|
|
6
|
-
* import {
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAttestation,
|
|
8
|
+
* withAttestation,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/attestation";
|
|
7
10
|
*
|
|
8
11
|
* const { publicKeyPem } = await verifyAttestation(
|
|
9
12
|
* { appId: "TEAMID.com.example.app" },
|
|
@@ -17,3 +20,5 @@
|
|
|
17
20
|
*/
|
|
18
21
|
export { verifyAttestation } from "./src/attestation.js";
|
|
19
22
|
export { AttestationError, AttestationErrorCode } from "./src/errors.js";
|
|
23
|
+
// withAttestation middleware
|
|
24
|
+
export { withAttestation } from "./src/with-attestation.js";
|
package/esm/mod.d.ts
CHANGED
|
@@ -1,17 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Verify Apple App Attest attestations and assertions using WebCrypto.
|
|
3
3
|
*
|
|
4
|
+
* This library implements Apple's full
|
|
5
|
+
* [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity)
|
|
6
|
+
* server-side verification — CBOR decoding, X.509 certificate chain
|
|
7
|
+
* validation, nonce verification, ECDSA signature checks — using only
|
|
8
|
+
* WebCrypto APIs so it runs in Supabase Edge Functions, Deno Deploy,
|
|
9
|
+
* and other edge runtimes with no native dependencies.
|
|
10
|
+
*
|
|
11
|
+
* ## Attestation
|
|
12
|
+
*
|
|
13
|
+
* Verify a new device and extract its public key:
|
|
14
|
+
*
|
|
4
15
|
* ```ts
|
|
5
|
-
* import { verifyAttestation
|
|
16
|
+
* import { verifyAttestation } from "@bradford-tech/supabase-integrity-attest";
|
|
6
17
|
*
|
|
7
|
-
*
|
|
18
|
+
* // clientDataHash = SHA-256(challenge) — most client SDKs hash internally
|
|
19
|
+
* const clientDataHash = new Uint8Array(
|
|
20
|
+
* await crypto.subtle.digest("SHA-256", new TextEncoder().encode(challenge)),
|
|
21
|
+
* );
|
|
22
|
+
* const { publicKeyPem, receipt, signCount } = await verifyAttestation(
|
|
8
23
|
* { appId: "TEAMID.com.example.app" },
|
|
9
24
|
* keyId,
|
|
10
|
-
*
|
|
25
|
+
* clientDataHash,
|
|
11
26
|
* attestation,
|
|
12
27
|
* );
|
|
28
|
+
* // Store publicKeyPem and signCount for future assertion verification
|
|
13
29
|
* ```
|
|
14
30
|
*
|
|
31
|
+
* ## Assertion
|
|
32
|
+
*
|
|
33
|
+
* Verify ongoing requests from an already-attested device:
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { verifyAssertion } from "@bradford-tech/supabase-integrity-attest";
|
|
37
|
+
*
|
|
38
|
+
* const { signCount } = await verifyAssertion(
|
|
39
|
+
* { appId: "TEAMID.com.example.app" },
|
|
40
|
+
* assertion,
|
|
41
|
+
* clientData,
|
|
42
|
+
* publicKeyPem,
|
|
43
|
+
* previousSignCount,
|
|
44
|
+
* );
|
|
45
|
+
* // Persist the updated signCount
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Middleware
|
|
49
|
+
*
|
|
50
|
+
* Use the {@linkcode withAttestation} and {@linkcode withAssertion}
|
|
51
|
+
* wrappers for automatic verification, callback-driven storage, and
|
|
52
|
+
* typed error handling:
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* import {
|
|
56
|
+
* withAttestation,
|
|
57
|
+
* withAssertion,
|
|
58
|
+
* } from "@bradford-tech/supabase-integrity-attest";
|
|
59
|
+
*
|
|
60
|
+
* const attestHandler = withAttestation(
|
|
61
|
+
* {
|
|
62
|
+
* appId: "TEAMID.com.example.app",
|
|
63
|
+
* consumeChallenge,
|
|
64
|
+
* storeDeviceKey,
|
|
65
|
+
* },
|
|
66
|
+
* (_req, ctx) =>
|
|
67
|
+
* Response.json({ ok: true, deviceId: ctx.deviceId }),
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* const protectedHandler = withAssertion(
|
|
71
|
+
* {
|
|
72
|
+
* appId: "TEAMID.com.example.app",
|
|
73
|
+
* getDeviceKey,
|
|
74
|
+
* commitSignCount,
|
|
75
|
+
* },
|
|
76
|
+
* (_req, ctx) =>
|
|
77
|
+
* Response.json({ hello: ctx.deviceId, counter: ctx.signCount }),
|
|
78
|
+
* );
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* ## Subpath imports
|
|
82
|
+
*
|
|
83
|
+
* For smaller bundles, import only what you need:
|
|
84
|
+
*
|
|
85
|
+
* - `@bradford-tech/supabase-integrity-attest/attestation` — attestation
|
|
86
|
+
* (`verifyAttestation`, `withAttestation`). Full crypto deps.
|
|
87
|
+
* - `@bradford-tech/supabase-integrity-attest/assertion` — assertion
|
|
88
|
+
* (`verifyAssertion`, `withAssertion`). Excludes `asn1js` and
|
|
89
|
+
* `@noble/curves` to keep the bundle minimal.
|
|
90
|
+
*
|
|
91
|
+
* Full documentation: {@link https://integrity-attest.bradford.tech}
|
|
92
|
+
*
|
|
15
93
|
* @module
|
|
16
94
|
*/
|
|
17
95
|
export type { AppInfo, AttestationResult, VerifyAttestationOptions, } from "./src/attestation.js";
|
|
@@ -21,5 +99,7 @@ export { verifyAssertion } from "./src/assertion.js";
|
|
|
21
99
|
export { AssertionError, AssertionErrorCode, AttestationError, AttestationErrorCode, } from "./src/errors.js";
|
|
22
100
|
export { withAssertion } from "./src/with-assertion.js";
|
|
23
101
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
|
24
|
-
export type { AssertionContext, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
102
|
+
export type { AssertionContext, AssertionTimings, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
103
|
+
export { withAttestation } from "./src/with-attestation.js";
|
|
104
|
+
export type { AttestationContext, AttestationTimings, ExtractAttestationFn, WithAttestationOptions, } from "./src/with-attestation.js";
|
|
25
105
|
//# sourceMappingURL=mod.d.ts.map
|
package/esm/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AAEH,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC"}
|
package/esm/mod.js
CHANGED
|
@@ -1,22 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Verify Apple App Attest attestations and assertions using WebCrypto.
|
|
3
3
|
*
|
|
4
|
+
* This library implements Apple's full
|
|
5
|
+
* [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity)
|
|
6
|
+
* server-side verification — CBOR decoding, X.509 certificate chain
|
|
7
|
+
* validation, nonce verification, ECDSA signature checks — using only
|
|
8
|
+
* WebCrypto APIs so it runs in Supabase Edge Functions, Deno Deploy,
|
|
9
|
+
* and other edge runtimes with no native dependencies.
|
|
10
|
+
*
|
|
11
|
+
* ## Attestation
|
|
12
|
+
*
|
|
13
|
+
* Verify a new device and extract its public key:
|
|
14
|
+
*
|
|
4
15
|
* ```ts
|
|
5
|
-
* import { verifyAttestation
|
|
16
|
+
* import { verifyAttestation } from "@bradford-tech/supabase-integrity-attest";
|
|
6
17
|
*
|
|
7
|
-
*
|
|
18
|
+
* // clientDataHash = SHA-256(challenge) — most client SDKs hash internally
|
|
19
|
+
* const clientDataHash = new Uint8Array(
|
|
20
|
+
* await crypto.subtle.digest("SHA-256", new TextEncoder().encode(challenge)),
|
|
21
|
+
* );
|
|
22
|
+
* const { publicKeyPem, receipt, signCount } = await verifyAttestation(
|
|
8
23
|
* { appId: "TEAMID.com.example.app" },
|
|
9
24
|
* keyId,
|
|
10
|
-
*
|
|
25
|
+
* clientDataHash,
|
|
11
26
|
* attestation,
|
|
12
27
|
* );
|
|
28
|
+
* // Store publicKeyPem and signCount for future assertion verification
|
|
13
29
|
* ```
|
|
14
30
|
*
|
|
31
|
+
* ## Assertion
|
|
32
|
+
*
|
|
33
|
+
* Verify ongoing requests from an already-attested device:
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { verifyAssertion } from "@bradford-tech/supabase-integrity-attest";
|
|
37
|
+
*
|
|
38
|
+
* const { signCount } = await verifyAssertion(
|
|
39
|
+
* { appId: "TEAMID.com.example.app" },
|
|
40
|
+
* assertion,
|
|
41
|
+
* clientData,
|
|
42
|
+
* publicKeyPem,
|
|
43
|
+
* previousSignCount,
|
|
44
|
+
* );
|
|
45
|
+
* // Persist the updated signCount
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Middleware
|
|
49
|
+
*
|
|
50
|
+
* Use the {@linkcode withAttestation} and {@linkcode withAssertion}
|
|
51
|
+
* wrappers for automatic verification, callback-driven storage, and
|
|
52
|
+
* typed error handling:
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* import {
|
|
56
|
+
* withAttestation,
|
|
57
|
+
* withAssertion,
|
|
58
|
+
* } from "@bradford-tech/supabase-integrity-attest";
|
|
59
|
+
*
|
|
60
|
+
* const attestHandler = withAttestation(
|
|
61
|
+
* {
|
|
62
|
+
* appId: "TEAMID.com.example.app",
|
|
63
|
+
* consumeChallenge,
|
|
64
|
+
* storeDeviceKey,
|
|
65
|
+
* },
|
|
66
|
+
* (_req, ctx) =>
|
|
67
|
+
* Response.json({ ok: true, deviceId: ctx.deviceId }),
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* const protectedHandler = withAssertion(
|
|
71
|
+
* {
|
|
72
|
+
* appId: "TEAMID.com.example.app",
|
|
73
|
+
* getDeviceKey,
|
|
74
|
+
* commitSignCount,
|
|
75
|
+
* },
|
|
76
|
+
* (_req, ctx) =>
|
|
77
|
+
* Response.json({ hello: ctx.deviceId, counter: ctx.signCount }),
|
|
78
|
+
* );
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* ## Subpath imports
|
|
82
|
+
*
|
|
83
|
+
* For smaller bundles, import only what you need:
|
|
84
|
+
*
|
|
85
|
+
* - `@bradford-tech/supabase-integrity-attest/attestation` — attestation
|
|
86
|
+
* (`verifyAttestation`, `withAttestation`). Full crypto deps.
|
|
87
|
+
* - `@bradford-tech/supabase-integrity-attest/assertion` — assertion
|
|
88
|
+
* (`verifyAssertion`, `withAssertion`). Excludes `asn1js` and
|
|
89
|
+
* `@noble/curves` to keep the bundle minimal.
|
|
90
|
+
*
|
|
91
|
+
* Full documentation: {@link https://integrity-attest.bradford.tech}
|
|
92
|
+
*
|
|
15
93
|
* @module
|
|
16
94
|
*/
|
|
17
95
|
export { verifyAttestation } from "./src/attestation.js";
|
|
18
96
|
export { verifyAssertion } from "./src/assertion.js";
|
|
19
97
|
export { AssertionError, AssertionErrorCode, AttestationError, AttestationErrorCode, } from "./src/errors.js";
|
|
20
|
-
// withAssertion
|
|
98
|
+
// withAssertion middleware
|
|
21
99
|
export { withAssertion } from "./src/with-assertion.js";
|
|
22
100
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
|
101
|
+
// withAttestation middleware
|
|
102
|
+
export { withAttestation } from "./src/with-attestation.js";
|
package/esm/src/attestation.d.ts
CHANGED
|
@@ -50,7 +50,15 @@ export declare function decodeAttestationCbor(data: Uint8Array): AttestationCbor
|
|
|
50
50
|
* CBOR decode, certificate chain validation, nonce check, key extraction,
|
|
51
51
|
* AAGUID check, and credential ID verification.
|
|
52
52
|
*
|
|
53
|
+
* **Important:** The `clientDataHash` parameter corresponds to Apple's
|
|
54
|
+
* `clientDataHash` argument on `DCAppAttestService.attestKey(_:clientDataHash:)`.
|
|
55
|
+
* Most client SDKs (Expo's `attestKeyAsync`, native wrappers) hash the
|
|
56
|
+
* caller's challenge with SHA-256 before passing to Apple. If you are using
|
|
57
|
+
* the {@linkcode withAttestation} middleware, this hashing is handled
|
|
58
|
+
* automatically. If calling `verifyAttestation` directly, you must pass
|
|
59
|
+
* `SHA-256(challenge)` — not the raw challenge — as `clientDataHash`.
|
|
60
|
+
*
|
|
53
61
|
* @throws {AttestationError} If any verification step fails.
|
|
54
62
|
*/
|
|
55
|
-
export declare function verifyAttestation(appInfo: AppInfo, keyId: string,
|
|
63
|
+
export declare function verifyAttestation(appInfo: AppInfo, keyId: string, clientDataHash: Uint8Array | string, attestation: Uint8Array | string, options?: VerifyAttestationOptions): Promise<AttestationResult>;
|
|
56
64
|
//# sourceMappingURL=attestation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/src/attestation.ts"],"names":[],"mappings":"AAaA,yCAAyC;AACzC,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,OAAO,EAAE,UAAU,CAAC;IACpB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACvC,uFAAuF;IACvF,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QACP,GAAG,EAAE,UAAU,EAAE,CAAC;QAClB,OAAO,EAAE,UAAU,CAAC;KACrB,CAAC;IACF,QAAQ,EAAE,UAAU,CAAC;CACtB;AAsGD,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAyEvE;AAED
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/src/attestation.ts"],"names":[],"mappings":"AAaA,yCAAyC;AACzC,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,OAAO,EAAE,UAAU,CAAC;IACpB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACvC,uFAAuF;IACvF,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QACP,GAAG,EAAE,UAAU,EAAE,CAAC;QAClB,OAAO,EAAE,UAAU,CAAC;KACrB,CAAC;IACF,QAAQ,EAAE,UAAU,CAAC;CACtB;AAsGD,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAyEvE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,UAAU,GAAG,MAAM,EACnC,WAAW,EAAE,UAAU,GAAG,MAAM,EAChC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAkJ5B"}
|
package/esm/src/attestation.js
CHANGED
|
@@ -163,9 +163,17 @@ export function decodeAttestationCbor(data) {
|
|
|
163
163
|
* CBOR decode, certificate chain validation, nonce check, key extraction,
|
|
164
164
|
* AAGUID check, and credential ID verification.
|
|
165
165
|
*
|
|
166
|
+
* **Important:** The `clientDataHash` parameter corresponds to Apple's
|
|
167
|
+
* `clientDataHash` argument on `DCAppAttestService.attestKey(_:clientDataHash:)`.
|
|
168
|
+
* Most client SDKs (Expo's `attestKeyAsync`, native wrappers) hash the
|
|
169
|
+
* caller's challenge with SHA-256 before passing to Apple. If you are using
|
|
170
|
+
* the {@linkcode withAttestation} middleware, this hashing is handled
|
|
171
|
+
* automatically. If calling `verifyAttestation` directly, you must pass
|
|
172
|
+
* `SHA-256(challenge)` — not the raw challenge — as `clientDataHash`.
|
|
173
|
+
*
|
|
166
174
|
* @throws {AttestationError} If any verification step fails.
|
|
167
175
|
*/
|
|
168
|
-
export async function verifyAttestation(appInfo, keyId,
|
|
176
|
+
export async function verifyAttestation(appInfo, keyId, clientDataHash, attestation, options) {
|
|
169
177
|
// Decode attestation bytes from base64 if string
|
|
170
178
|
let attestationBytes;
|
|
171
179
|
if (typeof attestation === "string") {
|
|
@@ -199,11 +207,11 @@ export async function verifyAttestation(appInfo, keyId, challenge, attestation,
|
|
|
199
207
|
}
|
|
200
208
|
// Step 4: Verify certificate chain
|
|
201
209
|
await verifyCertificateChain(x5c, options?.checkDate);
|
|
202
|
-
// Step 5-6: Compute nonce = SHA-256(authData ||
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
const
|
|
206
|
-
const nonceInput = concat(authData,
|
|
210
|
+
// Step 5-6: Compute nonce = SHA-256(authData || clientDataHash)
|
|
211
|
+
// The clientDataHash is typically SHA-256(challenge) — see the JSDoc above.
|
|
212
|
+
// The withAttestation middleware handles this hashing automatically.
|
|
213
|
+
const clientDataHashBytes = toBytes(clientDataHash);
|
|
214
|
+
const nonceInput = concat(authData, clientDataHashBytes);
|
|
207
215
|
const computedNonce = new Uint8Array(await crypto.subtle.digest("SHA-256", nonceInput));
|
|
208
216
|
// Step 7: Extract nonce from leaf cert
|
|
209
217
|
const certNonce = extractNonceFromCert(x5c[0]);
|
package/esm/src/errors.d.ts
CHANGED
|
@@ -13,16 +13,32 @@ export declare enum AttestationErrorCode {
|
|
|
13
13
|
/** Sign count is not zero (required for attestation). */
|
|
14
14
|
INVALID_COUNTER = "INVALID_COUNTER",
|
|
15
15
|
/** AAGUID does not match the expected environment (production/development). */
|
|
16
|
-
INVALID_AAGUID = "INVALID_AAGUID"
|
|
16
|
+
INVALID_AAGUID = "INVALID_AAGUID",
|
|
17
|
+
/**
|
|
18
|
+
* The `consumeChallenge` callback returned `false`, meaning the challenge
|
|
19
|
+
* was missing, expired, or already consumed. Used by {@linkcode withAttestation}.
|
|
20
|
+
*/
|
|
21
|
+
CHALLENGE_INVALID = "CHALLENGE_INVALID",
|
|
22
|
+
/**
|
|
23
|
+
* An internal or storage callback error occurred (used by
|
|
24
|
+
* {@linkcode withAttestation}). The original error is attached via
|
|
25
|
+
* `Error.cause` and is deliberately NOT reflected in the HTTP response
|
|
26
|
+
* body to avoid leaking database schema or driver diagnostics.
|
|
27
|
+
*/
|
|
28
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
17
29
|
}
|
|
18
30
|
/** Thrown when attestation verification fails. */
|
|
19
31
|
export declare class AttestationError extends Error {
|
|
20
32
|
/** Machine-readable error code. */
|
|
21
33
|
readonly code: AttestationErrorCode;
|
|
34
|
+
/** Discriminant for `instanceof` checks in catch blocks. Always `"AttestationError"`. */
|
|
22
35
|
readonly name = "AttestationError";
|
|
36
|
+
/** Create an AttestationError with a machine-readable code and human-readable message. */
|
|
23
37
|
constructor(
|
|
24
38
|
/** Machine-readable error code. */
|
|
25
|
-
code: AttestationErrorCode, message: string
|
|
39
|
+
code: AttestationErrorCode, message: string, options?: {
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
});
|
|
26
42
|
}
|
|
27
43
|
/** Error codes returned by {@linkcode AssertionError}. */
|
|
28
44
|
export declare enum AssertionErrorCode {
|
|
@@ -37,13 +53,22 @@ export declare enum AssertionErrorCode {
|
|
|
37
53
|
/** No device key found for the given device ID (used by {@linkcode withAssertion}). */
|
|
38
54
|
DEVICE_NOT_FOUND = "DEVICE_NOT_FOUND",
|
|
39
55
|
/** An internal or storage callback error occurred (used by {@linkcode withAssertion}). */
|
|
40
|
-
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
56
|
+
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
57
|
+
/**
|
|
58
|
+
* The `commitSignCount` callback returned `false`, meaning another
|
|
59
|
+
* concurrent request already advanced the stored counter past this value.
|
|
60
|
+
* Indicates an expected race under concurrent load, not a client bug.
|
|
61
|
+
* Used by {@linkcode withAssertion}.
|
|
62
|
+
*/
|
|
63
|
+
SIGN_COUNT_STALE = "SIGN_COUNT_STALE"
|
|
41
64
|
}
|
|
42
65
|
/** Thrown when assertion verification fails. */
|
|
43
66
|
export declare class AssertionError extends Error {
|
|
44
67
|
/** Machine-readable error code. */
|
|
45
68
|
readonly code: AssertionErrorCode;
|
|
69
|
+
/** Discriminant for `instanceof` checks in catch blocks. Always `"AssertionError"`. */
|
|
46
70
|
readonly name = "AssertionError";
|
|
71
|
+
/** Create an AssertionError with a machine-readable code and human-readable message. */
|
|
47
72
|
constructor(
|
|
48
73
|
/** Machine-readable error code. */
|
|
49
74
|
code: AssertionErrorCode, message: string, options?: {
|
package/esm/src/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/src/errors.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,oBAAY,oBAAoB;IAC9B,qDAAqD;IACrD,cAAc,mBAAmB;IACjC,mDAAmD;IACnD,yBAAyB,8BAA8B;IACvD,uEAAuE;IACvE,cAAc,mBAAmB;IACjC,uDAAuD;IACvD,cAAc,mBAAmB;IACjC,0DAA0D;IAC1D,eAAe,oBAAoB;IACnC,yDAAyD;IACzD,eAAe,oBAAoB;IACnC,+EAA+E;IAC/E,cAAc,mBAAmB;CAClC;AAED,kDAAkD;AAClD,qBAAa,gBAAiB,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/src/errors.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,oBAAY,oBAAoB;IAC9B,qDAAqD;IACrD,cAAc,mBAAmB;IACjC,mDAAmD;IACnD,yBAAyB,8BAA8B;IACvD,uEAAuE;IACvE,cAAc,mBAAmB;IACjC,uDAAuD;IACvD,cAAc,mBAAmB;IACjC,0DAA0D;IAC1D,eAAe,oBAAoB;IACnC,yDAAyD;IACzD,eAAe,oBAAoB;IACnC,+EAA+E;IAC/E,cAAc,mBAAmB;IACjC;;;OAGG;IACH,iBAAiB,sBAAsB;IACvC;;;;;OAKG;IACH,cAAc,mBAAmB;CAClC;AAED,kDAAkD;AAClD,qBAAa,gBAAiB,SAAQ,KAAK;IAKvC,mCAAmC;aACnB,IAAI,EAAE,oBAAoB;IAL5C,yFAAyF;IACzF,SAAkB,IAAI,sBAAsB;IAC5C,0FAA0F;;IAExF,mCAAmC;IACnB,IAAI,EAAE,oBAAoB,EAC1C,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAIhC;AAED,0DAA0D;AAC1D,oBAAY,kBAAkB;IAC5B,qDAAqD;IACrD,cAAc,mBAAmB;IACjC,uDAAuD;IACvD,cAAc,mBAAmB;IACjC,mEAAmE;IACnE,uBAAuB,4BAA4B;IACnD,2CAA2C;IAC3C,iBAAiB,sBAAsB;IACvC,uFAAuF;IACvF,gBAAgB,qBAAqB;IACrC,0FAA0F;IAC1F,cAAc,mBAAmB;IACjC;;;;;OAKG;IACH,gBAAgB,qBAAqB;CACtC;AAED,gDAAgD;AAChD,qBAAa,cAAe,SAAQ,KAAK;IAKrC,mCAAmC;aACnB,IAAI,EAAE,kBAAkB;IAL1C,uFAAuF;IACvF,SAAkB,IAAI,oBAAoB;IAC1C,wFAAwF;;IAEtF,mCAAmC;IACnB,IAAI,EAAE,kBAAkB,EACxC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAIhC"}
|
package/esm/src/errors.js
CHANGED
|
@@ -16,19 +16,33 @@ export var AttestationErrorCode;
|
|
|
16
16
|
AttestationErrorCode["INVALID_COUNTER"] = "INVALID_COUNTER";
|
|
17
17
|
/** AAGUID does not match the expected environment (production/development). */
|
|
18
18
|
AttestationErrorCode["INVALID_AAGUID"] = "INVALID_AAGUID";
|
|
19
|
+
/**
|
|
20
|
+
* The `consumeChallenge` callback returned `false`, meaning the challenge
|
|
21
|
+
* was missing, expired, or already consumed. Used by {@linkcode withAttestation}.
|
|
22
|
+
*/
|
|
23
|
+
AttestationErrorCode["CHALLENGE_INVALID"] = "CHALLENGE_INVALID";
|
|
24
|
+
/**
|
|
25
|
+
* An internal or storage callback error occurred (used by
|
|
26
|
+
* {@linkcode withAttestation}). The original error is attached via
|
|
27
|
+
* `Error.cause` and is deliberately NOT reflected in the HTTP response
|
|
28
|
+
* body to avoid leaking database schema or driver diagnostics.
|
|
29
|
+
*/
|
|
30
|
+
AttestationErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
19
31
|
})(AttestationErrorCode || (AttestationErrorCode = {}));
|
|
20
32
|
/** Thrown when attestation verification fails. */
|
|
21
33
|
export class AttestationError extends Error {
|
|
34
|
+
/** Create an AttestationError with a machine-readable code and human-readable message. */
|
|
22
35
|
constructor(
|
|
23
36
|
/** Machine-readable error code. */
|
|
24
|
-
code, message) {
|
|
25
|
-
super(message);
|
|
37
|
+
code, message, options) {
|
|
38
|
+
super(message, options);
|
|
26
39
|
Object.defineProperty(this, "code", {
|
|
27
40
|
enumerable: true,
|
|
28
41
|
configurable: true,
|
|
29
42
|
writable: true,
|
|
30
43
|
value: code
|
|
31
44
|
});
|
|
45
|
+
/** Discriminant for `instanceof` checks in catch blocks. Always `"AttestationError"`. */
|
|
32
46
|
Object.defineProperty(this, "name", {
|
|
33
47
|
enumerable: true,
|
|
34
48
|
configurable: true,
|
|
@@ -52,9 +66,17 @@ export var AssertionErrorCode;
|
|
|
52
66
|
AssertionErrorCode["DEVICE_NOT_FOUND"] = "DEVICE_NOT_FOUND";
|
|
53
67
|
/** An internal or storage callback error occurred (used by {@linkcode withAssertion}). */
|
|
54
68
|
AssertionErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
69
|
+
/**
|
|
70
|
+
* The `commitSignCount` callback returned `false`, meaning another
|
|
71
|
+
* concurrent request already advanced the stored counter past this value.
|
|
72
|
+
* Indicates an expected race under concurrent load, not a client bug.
|
|
73
|
+
* Used by {@linkcode withAssertion}.
|
|
74
|
+
*/
|
|
75
|
+
AssertionErrorCode["SIGN_COUNT_STALE"] = "SIGN_COUNT_STALE";
|
|
55
76
|
})(AssertionErrorCode || (AssertionErrorCode = {}));
|
|
56
77
|
/** Thrown when assertion verification fails. */
|
|
57
78
|
export class AssertionError extends Error {
|
|
79
|
+
/** Create an AssertionError with a machine-readable code and human-readable message. */
|
|
58
80
|
constructor(
|
|
59
81
|
/** Machine-readable error code. */
|
|
60
82
|
code, message, options) {
|
|
@@ -65,6 +87,7 @@ export class AssertionError extends Error {
|
|
|
65
87
|
writable: true,
|
|
66
88
|
value: code
|
|
67
89
|
});
|
|
90
|
+
/** Discriminant for `instanceof` checks in catch blocks. Always `"AssertionError"`. */
|
|
68
91
|
Object.defineProperty(this, "name", {
|
|
69
92
|
enumerable: true,
|
|
70
93
|
configurable: true,
|
package/esm/src/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/src/utils.ts"],"names":[],"mappings":"AAGA,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAU1D;AAED,mDAAmD;AACnD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/src/utils.ts"],"names":[],"mappings":"AAGA,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAU1D;AAED,mDAAmD;AACnD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAMvE;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAGxE;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAG9D;AAED,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAaxE;AAED,qDAAqD;AACrD,wBAAsB,cAAc,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAIpE"}
|
package/esm/src/utils.js
CHANGED
|
@@ -15,10 +15,8 @@ export function concat(...arrays) {
|
|
|
15
15
|
}
|
|
16
16
|
/** Constant-time comparison of two byte arrays. */
|
|
17
17
|
export function constantTimeEqual(a, b) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let diff = 0;
|
|
21
|
-
for (let i = 0; i < a.length; i++) {
|
|
18
|
+
let diff = a.length ^ b.length;
|
|
19
|
+
for (let i = 0; i < a.length && i < b.length; i++) {
|
|
22
20
|
diff |= a[i] ^ b[i];
|
|
23
21
|
}
|
|
24
22
|
return diff === 0;
|
|
@@ -47,6 +45,6 @@ export async function importPemPublicKey(pem) {
|
|
|
47
45
|
/** Export a CryptoKey to PEM-encoded SPKI format. */
|
|
48
46
|
export async function exportKeyToPem(key) {
|
|
49
47
|
const spki = await crypto.subtle.exportKey("spki", key);
|
|
50
|
-
const base64 = encodeBase64(spki);
|
|
48
|
+
const base64 = encodeBase64(spki).match(/.{1,64}/g).join("\n");
|
|
51
49
|
return `-----BEGIN PUBLIC KEY-----\n${base64}\n-----END PUBLIC KEY-----`;
|
|
52
50
|
}
|
|
@@ -10,6 +10,21 @@ export type DeviceKey = {
|
|
|
10
10
|
/** Last verified sign count for this device. */
|
|
11
11
|
signCount: number;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Library-internal timing spans for an assertion verification, in
|
|
15
|
+
* milliseconds. Exposed on {@linkcode AssertionContext.timings} so the
|
|
16
|
+
* wrapped handler can emit them as part of its own Server-Timing response.
|
|
17
|
+
*/
|
|
18
|
+
export type AssertionTimings = {
|
|
19
|
+
/** Parse request headers and read raw body bytes. */
|
|
20
|
+
extractMs: number;
|
|
21
|
+
/** `getDeviceKey` callback wall-clock duration. */
|
|
22
|
+
getDeviceKeyMs: number;
|
|
23
|
+
/** Cryptographic verification (CBOR decode, ECDSA verify, counter check). */
|
|
24
|
+
verifyMs: number;
|
|
25
|
+
/** `commitSignCount` callback wall-clock duration. */
|
|
26
|
+
commitMs: number;
|
|
27
|
+
};
|
|
13
28
|
/** Context passed to the inner handler after successful assertion verification. */
|
|
14
29
|
export type AssertionContext = {
|
|
15
30
|
/** Device identifier from the request. */
|
|
@@ -18,6 +33,8 @@ export type AssertionContext = {
|
|
|
18
33
|
signCount: number;
|
|
19
34
|
/** Raw request body bytes (the client data that was signed). */
|
|
20
35
|
rawBody: Uint8Array;
|
|
36
|
+
/** Library-internal timings, ready to merge into Server-Timing. */
|
|
37
|
+
timings: AssertionTimings;
|
|
21
38
|
};
|
|
22
39
|
/** Custom function to extract assertion data from an incoming request. */
|
|
23
40
|
export type ExtractAssertionFn = (req: Request) => Promise<{
|
|
@@ -33,8 +50,24 @@ export type WithAssertionOptions = {
|
|
|
33
50
|
developmentEnv?: boolean;
|
|
34
51
|
/** Retrieve the stored device key for a given device ID. Return `null` if not found. */
|
|
35
52
|
getDeviceKey: (deviceId: string) => Promise<DeviceKey | null>;
|
|
36
|
-
/**
|
|
37
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Atomically advance the stored sign count. MUST be implemented as a
|
|
55
|
+
* compare-and-swap: only update if the currently stored value is strictly
|
|
56
|
+
* less than `newSignCount`. Returns `true` if the row was advanced,
|
|
57
|
+
* `false` if another concurrent request already advanced past this value.
|
|
58
|
+
*
|
|
59
|
+
* Recommended SQL pattern against Postgres:
|
|
60
|
+
*
|
|
61
|
+
* ```sql
|
|
62
|
+
* UPDATE app_attest_devices
|
|
63
|
+
* SET sign_count = $1, last_seen_at = now()
|
|
64
|
+
* WHERE device_id = $2 AND sign_count < $1
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* Return `rowCount > 0`. The library converts `false` into
|
|
68
|
+
* `AssertionError(SIGN_COUNT_STALE)`.
|
|
69
|
+
*/
|
|
70
|
+
commitSignCount: (deviceId: string, newSignCount: number) => Promise<boolean>;
|
|
38
71
|
/** Override the default header-based assertion extraction. */
|
|
39
72
|
extractAssertion?: ExtractAssertionFn;
|
|
40
73
|
/** Custom error response handler. Defaults to JSON error responses. */
|
|
@@ -44,8 +77,13 @@ export type WithAssertionOptions = {
|
|
|
44
77
|
* Request handler middleware that verifies App Attest assertions.
|
|
45
78
|
*
|
|
46
79
|
* Wraps a handler function with automatic assertion verification,
|
|
47
|
-
* device key lookup, and sign
|
|
80
|
+
* device key lookup, and atomic sign-count commit. Returns a new handler
|
|
48
81
|
* that rejects unauthenticated requests with appropriate HTTP error responses.
|
|
82
|
+
*
|
|
83
|
+
* The `commitSignCount` callback MUST implement compare-and-swap semantics
|
|
84
|
+
* (see {@linkcode WithAssertionOptions.commitSignCount}) — a non-atomic
|
|
85
|
+
* unconditional write will silently corrupt replay protection under
|
|
86
|
+
* concurrent load.
|
|
49
87
|
*/
|
|
50
88
|
export declare function withAssertion(options: WithAssertionOptions, handler: (req: Request, context: AssertionContext) => Response | Promise<Response>): (req: Request) => Promise<Response>;
|
|
51
89
|
//# sourceMappingURL=with-assertion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-assertion.d.ts","sourceRoot":"","sources":["../../src/src/with-assertion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAsB,MAAM,aAAa,CAAC;AAEjE,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AACjE,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG;IACtB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,OAAO,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"with-assertion.d.ts","sourceRoot":"","sources":["../../src/src/with-assertion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAsB,MAAM,aAAa,CAAC;AAEjE,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AACjE,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG;IACtB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,OAAO,EAAE,UAAU,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,gBAAgB,CAAC;CAC3B,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC,CAAC;AAEH,kEAAkE;AAClE,MAAM,MAAM,oBAAoB,GAAG;IACjC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC9D;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,kBAAkB,CAAC;IACtC,uEAAuE;IACvE,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC,CAAC;AAmCF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,EAC7B,OAAO,EAAE,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,gBAAgB,KACtB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiGrC"}
|