@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 +5 -1
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +5 -1
- 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/utils.d.ts.map +1 -1
- package/esm/src/utils.js +3 -5
- package/esm/src/with-assertion.d.ts.map +1 -1
- package/esm/src/with-assertion.js +3 -5
- package/esm/src/with-attestation.d.ts.map +1 -1
- package/esm/src/with-attestation.js +13 -1
- package/package.json +1 -1
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
25
|
+
* clientDataHash,
|
|
22
26
|
* attestation,
|
|
23
27
|
* );
|
|
24
28
|
* // Store publicKeyPem and signCount for future assertion verification
|
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/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
|
}
|
|
@@ -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,
|
|
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
|
-
|
|
92
|
-
|
|
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,
|
|
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,
|
|
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