@bradford-tech/supabase-integrity-attest 0.4.1 → 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/mod.d.ts CHANGED
@@ -15,10 +15,14 @@
15
15
  * ```ts
16
16
  * import { verifyAttestation } from "@bradford-tech/supabase-integrity-attest";
17
17
  *
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
+ * );
18
22
  * const { publicKeyPem, receipt, signCount } = await verifyAttestation(
19
23
  * { appId: "TEAMID.com.example.app" },
20
24
  * keyId,
21
- * challenge,
25
+ * clientDataHash,
22
26
  * attestation,
23
27
  * );
24
28
  * // Store publicKeyPem and signCount for future assertion verification
package/esm/mod.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;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"}
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
@@ -15,10 +15,14 @@
15
15
  * ```ts
16
16
  * import { verifyAttestation } from "@bradford-tech/supabase-integrity-attest";
17
17
  *
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
+ * );
18
22
  * const { publicKeyPem, receipt, signCount } = await verifyAttestation(
19
23
  * { appId: "TEAMID.com.example.app" },
20
24
  * keyId,
21
- * challenge,
25
+ * clientDataHash,
22
26
  * attestation,
23
27
  * );
24
28
  * // Store publicKeyPem and signCount for future assertion verification
@@ -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, challenge: Uint8Array | string, attestation: Uint8Array | string, options?: VerifyAttestationOptions): Promise<AttestationResult>;
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;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,UAAU,GAAG,MAAM,EAC9B,WAAW,EAAE,UAAU,GAAG,MAAM,EAChC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAkJ5B"}
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"}
@@ -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, challenge, attestation, options) {
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 || challenge)
203
- // Apple's nonce verification uses the raw challenge bytes concatenated with authData,
204
- // NOT a hash of the challenge.
205
- const challengeBytes = toBytes(challenge);
206
- const nonceInput = concat(authData, challengeBytes);
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]);
@@ -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,CAOvE;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"}
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
- if (a.length !== b.length)
19
- return false;
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
  }
@@ -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;;;;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,CA+FrC"}
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"}
@@ -87,11 +87,9 @@ export function withAssertion(options, handler) {
87
87
  newSignCount = result.signCount;
88
88
  }
89
89
  catch (err) {
90
- const error = err instanceof AssertionError
91
- ? err
92
- : new AssertionError(AssertionErrorCode.INTERNAL_ERROR, String(err), {
93
- cause: err,
94
- });
90
+ const error = err instanceof AssertionError ? err : new AssertionError(AssertionErrorCode.INTERNAL_ERROR, "Internal error", {
91
+ cause: err,
92
+ });
95
93
  return options.onError?.(error, req) ?? defaultErrorResponse(error);
96
94
  }
97
95
  // Step 5: handler — outside try/catch, errors bubble up
@@ -1 +1 @@
1
- {"version":3,"file":"with-attestation.d.ts","sourceRoot":"","sources":["../../src/src/with-attestation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,UAAU,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG;IACnC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D;;;;OAIG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,UAAU,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,uEAAuE;IACvE,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC,CAAC;AAwEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,KACxB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAkGrC"}
1
+ {"version":3,"file":"with-attestation.d.ts","sourceRoot":"","sources":["../../src/src/with-attestation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,UAAU,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG;IACnC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D;;;;OAIG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,UAAU,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,uEAAuE;IACvE,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC,CAAC;AAwEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,KACxB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiHrC"}
@@ -79,6 +79,11 @@ export function withAttestation(options, handler) {
79
79
  const extracted = await extract(req);
80
80
  timings.extractMs = performance.now() - extractStart;
81
81
  deviceId = extracted.deviceId;
82
+ // Consume the challenge BEFORE verification to prevent a TOCTOU race:
83
+ // two concurrent requests with the same challenge could both pass
84
+ // verifyAttestation before either consumes. The trade-off is that a
85
+ // verification failure (malformed attestation, cert-chain error) burns
86
+ // the challenge, requiring the client to request a new one.
82
87
  const consumeStart = performance.now();
83
88
  let challengeOk;
84
89
  try {
@@ -95,8 +100,15 @@ export function withAttestation(options, handler) {
95
100
  if (!challengeOk) {
96
101
  throw new AttestationError(AttestationErrorCode.CHALLENGE_INVALID, "Challenge is missing, expired, or already consumed");
97
102
  }
103
+ // Hash the raw challenge to produce clientDataHash. Client SDKs
104
+ // (Expo's attestKeyAsync, native DCAppAttestService wrappers) hash
105
+ // the challenge with SHA-256 before passing to Apple's attestKey
106
+ // API, so the attestation certificate's nonce is computed over the
107
+ // hash, not the raw bytes. verifyAttestation expects clientDataHash
108
+ // (the hash), not the raw challenge.
109
+ const clientDataHash = new Uint8Array(await crypto.subtle.digest("SHA-256", extracted.challenge));
98
110
  const verifyStart = performance.now();
99
- const result = await verifyAttestation(appInfo, deviceId, extracted.challenge, extracted.attestation);
111
+ const result = await verifyAttestation(appInfo, deviceId, clientDataHash, extracted.attestation);
100
112
  timings.verifyMs = performance.now() - verifyStart;
101
113
  publicKeyPem = result.publicKeyPem;
102
114
  receipt = result.receipt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bradford-tech/supabase-integrity-attest",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Verify Apple App Attest attestations and assertions using WebCrypto.",
5
5
  "homepage": "https://integrity-attest.bradford.tech",
6
6
  "repository": {