@bradford-tech/supabase-integrity-attest 0.7.0 → 0.8.1
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 +0 -1
- package/esm/src/assertion.d.ts +0 -2
- package/esm/src/assertion.d.ts.map +1 -1
- package/esm/src/assertion.js +7 -1
- package/esm/src/attestation.d.ts +6 -1
- package/esm/src/attestation.d.ts.map +1 -1
- package/esm/src/attestation.js +86 -72
- package/esm/src/authdata.d.ts +0 -1
- package/esm/src/authdata.d.ts.map +1 -1
- package/esm/src/authdata.js +0 -2
- package/esm/src/certificate.d.ts.map +1 -1
- package/esm/src/certificate.js +116 -95
- package/esm/src/with-assertion.d.ts +0 -2
- package/esm/src/with-assertion.d.ts.map +1 -1
- package/esm/src/with-assertion.js +11 -6
- package/esm/src/with-attestation.d.ts.map +1 -1
- package/esm/src/with-attestation.js +7 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -61,7 +61,6 @@ const supabase = createClient(
|
|
|
61
61
|
|
|
62
62
|
Deno.serve(withAssertion({
|
|
63
63
|
appId: Deno.env.get("APP_ATTEST_APP_ID")!,
|
|
64
|
-
developmentEnv: Deno.env.get("APP_ATTEST_ENV") === "development",
|
|
65
64
|
getDeviceKey: async (deviceId) => {
|
|
66
65
|
const { data } = await supabase
|
|
67
66
|
.from("device_attestations")
|
package/esm/src/assertion.d.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
export interface AppInfo {
|
|
3
3
|
/** Apple App ID in the format `TEAMID.bundleId`. */
|
|
4
4
|
appId: string;
|
|
5
|
-
/** Set to `true` when verifying assertions from the development environment. */
|
|
6
|
-
developmentEnv?: boolean;
|
|
7
5
|
}
|
|
8
6
|
/** Successful assertion verification result. */
|
|
9
7
|
export interface AssertionResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../../src/src/assertion.ts"],"names":[],"mappings":"AAaA,8DAA8D;AAC9D,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../../src/src/assertion.ts"],"names":[],"mappings":"AAaA,8DAA8D;AAC9D,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,UAAU,GAAG,MAAM,EAC9B,UAAU,EAAE,UAAU,GAAG,MAAM,EAC/B,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,eAAe,CAAC,CAmH1B"}
|
package/esm/src/assertion.js
CHANGED
|
@@ -14,7 +14,13 @@ import { concat, constantTimeEqual, decodeBase64Bytes, importPemPublicKey, toByt
|
|
|
14
14
|
* @throws {AssertionError} If any verification step fails.
|
|
15
15
|
*/
|
|
16
16
|
export async function verifyAssertion(appInfo, assertion, clientData, publicKeyPem, previousSignCount) {
|
|
17
|
-
|
|
17
|
+
let assertionBytes;
|
|
18
|
+
try {
|
|
19
|
+
assertionBytes = decodeBase64Bytes(assertion);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new AssertionError(AssertionErrorCode.INVALID_FORMAT, "Invalid base64-encoded assertion");
|
|
23
|
+
}
|
|
18
24
|
const clientDataBytes = toBytes(clientData);
|
|
19
25
|
// Step 1: CBOR decode
|
|
20
26
|
let decoded;
|
package/esm/src/attestation.d.ts
CHANGED
|
@@ -40,7 +40,12 @@ export interface AttestationCbor {
|
|
|
40
40
|
};
|
|
41
41
|
authData: Uint8Array;
|
|
42
42
|
}
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Decode an Apple App Attest attestation object from raw CBOR bytes.
|
|
45
|
+
*
|
|
46
|
+
* @throws {AttestationError} with code `INVALID_FORMAT` if the data is not
|
|
47
|
+
* a valid attestation CBOR structure.
|
|
48
|
+
*/
|
|
44
49
|
export declare function decodeAttestationCbor(data: Uint8Array): AttestationCbor;
|
|
45
50
|
/**
|
|
46
51
|
* Verify an Apple App Attest attestation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/src/attestation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/src/attestation.ts"],"names":[],"mappings":"AAgBA,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;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAwFvE;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,CAoJ5B"}
|
package/esm/src/attestation.js
CHANGED
|
@@ -3,7 +3,7 @@ import { decodeBase64 } from "../deps/jsr.io/@std/encoding/1.0.10/base64.js";
|
|
|
3
3
|
import { extractNonceFromCert, extractPublicKeyFromCert, verifyCertificateChain, } from "./certificate.js";
|
|
4
4
|
import { AAGUID_DEVELOPMENT, AAGUID_PRODUCTION } from "./constants.js";
|
|
5
5
|
import { AttestationError, AttestationErrorCode } from "./errors.js";
|
|
6
|
-
import { parseAttestationAuthData } from "./authdata.js";
|
|
6
|
+
import { parseAttestationAuthData, } from "./authdata.js";
|
|
7
7
|
import { concat, constantTimeEqual, exportKeyToPem, toBytes } from "./utils.js";
|
|
8
8
|
/** Read a CBOR unsigned integer (additional info + following bytes). */
|
|
9
9
|
function readCborUint(data, offset) {
|
|
@@ -88,72 +88,86 @@ function findCborTextKey(data, key, startOffset) {
|
|
|
88
88
|
}
|
|
89
89
|
return -1;
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Decode an Apple App Attest attestation object from raw CBOR bytes.
|
|
93
|
+
*
|
|
94
|
+
* @throws {AttestationError} with code `INVALID_FORMAT` if the data is not
|
|
95
|
+
* a valid attestation CBOR structure.
|
|
96
|
+
*/
|
|
92
97
|
export function decodeAttestationCbor(data) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
98
|
+
try {
|
|
99
|
+
// Verify top-level is a CBOR map
|
|
100
|
+
const majorType = (data[0] >> 5) & 0x07;
|
|
101
|
+
if (majorType !== 5) {
|
|
102
|
+
throw new Error("Expected CBOR map at top level");
|
|
103
|
+
}
|
|
104
|
+
// Find "fmt" key and read its value
|
|
105
|
+
const fmtKeyPos = findCborTextKey(data, "fmt", 0);
|
|
106
|
+
if (fmtKeyPos === -1)
|
|
107
|
+
throw new Error('Missing "fmt" key');
|
|
108
|
+
const fmtKeyEnd = fmtKeyPos + 1 + 3; // 0x63 + "fmt"
|
|
109
|
+
const { value: fmt } = readCborText(data, fmtKeyEnd);
|
|
110
|
+
// Find "attStmt" key
|
|
111
|
+
const attStmtKeyPos = findCborTextKey(data, "attStmt", 0);
|
|
112
|
+
if (attStmtKeyPos === -1)
|
|
113
|
+
throw new Error('Missing "attStmt" key');
|
|
114
|
+
// After "attStmt" key: 0x67 + "attStmt" = 8 bytes
|
|
115
|
+
const attStmtValuePos = attStmtKeyPos + 8;
|
|
116
|
+
// attStmt value should be a map
|
|
117
|
+
const attStmtMajor = (data[attStmtValuePos] >> 5) & 0x07;
|
|
118
|
+
if (attStmtMajor !== 5)
|
|
119
|
+
throw new Error("attStmt is not a CBOR map");
|
|
120
|
+
// Find "x5c" key within attStmt
|
|
121
|
+
const x5cKeyPos = findCborTextKey(data, "x5c", attStmtValuePos);
|
|
122
|
+
if (x5cKeyPos === -1)
|
|
123
|
+
throw new Error('Missing "x5c" key in attStmt');
|
|
124
|
+
const x5cValuePos = x5cKeyPos + 4; // 0x63 + "x5c"
|
|
125
|
+
// x5c value is an array
|
|
126
|
+
const x5cMajor = (data[x5cValuePos] >> 5) & 0x07;
|
|
127
|
+
if (x5cMajor !== 4)
|
|
128
|
+
throw new Error("x5c is not a CBOR array");
|
|
129
|
+
const { value: x5cCount, end: x5cFirstItemPos } = readCborUint(data, x5cValuePos);
|
|
130
|
+
// Read each certificate byte string
|
|
131
|
+
const x5c = [];
|
|
132
|
+
let pos = x5cFirstItemPos;
|
|
133
|
+
for (let i = 0; i < x5cCount; i++) {
|
|
134
|
+
const { value: cert, end } = readCborBytes(data, pos);
|
|
135
|
+
x5c.push(cert);
|
|
136
|
+
pos = end;
|
|
137
|
+
}
|
|
138
|
+
// After x5c, find "receipt" key
|
|
139
|
+
const receiptKeyPos = findCborTextKey(data, "receipt", pos);
|
|
140
|
+
if (receiptKeyPos === -1) {
|
|
141
|
+
throw new Error('Missing "receipt" key in attStmt');
|
|
142
|
+
}
|
|
143
|
+
// Find "authData" key - search from after the receipt key position
|
|
144
|
+
const authDataKeyPos = findCborTextKey(data, "authData", receiptKeyPos);
|
|
145
|
+
if (authDataKeyPos === -1) {
|
|
146
|
+
throw new Error('Missing "authData" key');
|
|
147
|
+
}
|
|
148
|
+
// The receipt value is the bytes between the receipt key end and the authData key start.
|
|
149
|
+
// Receipt key: 0x67 + "receipt" = 8 bytes
|
|
150
|
+
const receiptValueStart = receiptKeyPos + 8;
|
|
151
|
+
// Read the receipt CBOR header to get the data start offset
|
|
152
|
+
const receiptMajor = (data[receiptValueStart] >> 5) & 0x07;
|
|
153
|
+
if (receiptMajor !== 2) {
|
|
154
|
+
throw new Error("receipt is not a CBOR byte string");
|
|
155
|
+
}
|
|
156
|
+
const { end: receiptDataStart } = readCborUint(data, receiptValueStart);
|
|
157
|
+
// The actual receipt data extends to just before the authData key.
|
|
158
|
+
// This handles the case where Apple's CBOR length header is incorrect.
|
|
159
|
+
const receipt = data.slice(receiptDataStart, authDataKeyPos);
|
|
160
|
+
// Read authData value
|
|
161
|
+
// authData key: 0x68 + "authData" = 9 bytes
|
|
162
|
+
const authDataValuePos = authDataKeyPos + 9;
|
|
163
|
+
const { value: authData } = readCborBytes(data, authDataValuePos);
|
|
164
|
+
return { fmt, attStmt: { x5c, receipt }, authData };
|
|
131
165
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
throw new Error(
|
|
136
|
-
// Find "authData" key - search from after the receipt key position
|
|
137
|
-
const authDataKeyPos = findCborTextKey(data, "authData", receiptKeyPos);
|
|
138
|
-
if (authDataKeyPos === -1) {
|
|
139
|
-
throw new Error('Missing "authData" key');
|
|
166
|
+
catch (e) {
|
|
167
|
+
if (e instanceof AttestationError)
|
|
168
|
+
throw e;
|
|
169
|
+
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, `Failed to CBOR-decode attestation object: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
140
170
|
}
|
|
141
|
-
// The receipt value is the bytes between the receipt key end and the authData key start.
|
|
142
|
-
// Receipt key: 0x67 + "receipt" = 8 bytes
|
|
143
|
-
const receiptValueStart = receiptKeyPos + 8;
|
|
144
|
-
// Read the receipt CBOR header to get the data start offset
|
|
145
|
-
const receiptMajor = (data[receiptValueStart] >> 5) & 0x07;
|
|
146
|
-
if (receiptMajor !== 2)
|
|
147
|
-
throw new Error("receipt is not a CBOR byte string");
|
|
148
|
-
const { end: receiptDataStart } = readCborUint(data, receiptValueStart);
|
|
149
|
-
// The actual receipt data extends to just before the authData key.
|
|
150
|
-
// This handles the case where Apple's CBOR length header is incorrect.
|
|
151
|
-
const receipt = data.slice(receiptDataStart, authDataKeyPos);
|
|
152
|
-
// Read authData value
|
|
153
|
-
// authData key: 0x68 + "authData" = 9 bytes
|
|
154
|
-
const authDataValuePos = authDataKeyPos + 9;
|
|
155
|
-
const { value: authData } = readCborBytes(data, authDataValuePos);
|
|
156
|
-
return { fmt, attStmt: { x5c, receipt }, authData };
|
|
157
171
|
}
|
|
158
172
|
/**
|
|
159
173
|
* Verify an Apple App Attest attestation.
|
|
@@ -188,13 +202,7 @@ export async function verifyAttestation(appInfo, keyId, clientDataHash, attestat
|
|
|
188
202
|
attestationBytes = attestation;
|
|
189
203
|
}
|
|
190
204
|
// Step 1: CBOR decode attestation -> { fmt, attStmt: { x5c, receipt }, authData }
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
decoded = decodeAttestationCbor(attestationBytes);
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, "Failed to CBOR-decode attestation object");
|
|
197
|
-
}
|
|
205
|
+
const decoded = decodeAttestationCbor(attestationBytes);
|
|
198
206
|
// Step 2: Validate fmt === "apple-appattest"
|
|
199
207
|
if (decoded.fmt !== "apple-appattest") {
|
|
200
208
|
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, `Invalid attestation format: expected "apple-appattest", got "${decoded.fmt}"`);
|
|
@@ -228,7 +236,13 @@ export async function verifyAttestation(appInfo, keyId, clientDataHash, attestat
|
|
|
228
236
|
throw new AttestationError(AttestationErrorCode.KEY_ID_MISMATCH, "Public key hash does not match keyId");
|
|
229
237
|
}
|
|
230
238
|
// Step 11: Parse authData
|
|
231
|
-
|
|
239
|
+
let parsedAuthData;
|
|
240
|
+
try {
|
|
241
|
+
parsedAuthData = parseAttestationAuthData(authData);
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, `Invalid authenticatorData: ${e instanceof Error ? e.message : String(e)}`);
|
|
245
|
+
}
|
|
232
246
|
// Step 12: Verify rpIdHash === SHA-256(appId)
|
|
233
247
|
const appIdHash = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(appInfo.appId)));
|
|
234
248
|
if (!constantTimeEqual(parsedAuthData.rpIdHash, appIdHash)) {
|
package/esm/src/authdata.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ export interface AssertionAuthData {
|
|
|
6
6
|
export interface AttestationAuthData extends AssertionAuthData {
|
|
7
7
|
aaguid: Uint8Array;
|
|
8
8
|
credentialId: Uint8Array;
|
|
9
|
-
coseKeyBytes: Uint8Array;
|
|
10
9
|
}
|
|
11
10
|
export declare function parseAssertionAuthData(data: Uint8Array): AssertionAuthData;
|
|
12
11
|
export declare function parseAttestationAuthData(data: Uint8Array): AttestationAuthData;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authdata.d.ts","sourceRoot":"","sources":["../../src/src/authdata.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,UAAU,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"authdata.d.ts","sourceRoot":"","sources":["../../src/src/authdata.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,UAAU,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,UAAU,CAAC;CAC1B;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAgB1E;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,UAAU,GACf,mBAAmB,CA+BrB"}
|
package/esm/src/authdata.js
CHANGED
|
@@ -19,11 +19,9 @@ export function parseAttestationAuthData(data) {
|
|
|
19
19
|
throw new Error(`authenticatorData truncated: credentialIdLength=${credentialIdLength} but only ${data.length - 55} bytes remain`);
|
|
20
20
|
}
|
|
21
21
|
const credentialId = data.slice(55, 55 + credentialIdLength);
|
|
22
|
-
const coseKeyBytes = data.slice(55 + credentialIdLength);
|
|
23
22
|
return {
|
|
24
23
|
...base,
|
|
25
24
|
aaguid,
|
|
26
25
|
credentialId,
|
|
27
|
-
coseKeyBytes,
|
|
28
26
|
};
|
|
29
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certificate.d.ts","sourceRoot":"","sources":["../../src/src/certificate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"certificate.d.ts","sourceRoot":"","sources":["../../src/src/certificate.ts"],"names":[],"mappings":"AAmWA;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,UAAU,EAAE,EACjB,SAAS,CAAC,EAAE,IAAI,GACf,OAAO,CAAC,IAAI,CAAC,CAkEf;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,CAwCpE;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CAqBrB"}
|
package/esm/src/certificate.js
CHANGED
|
@@ -60,98 +60,105 @@ function parseCertificate(der) {
|
|
|
60
60
|
if (asn1.offset === -1) {
|
|
61
61
|
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, "Failed to parse certificate DER");
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
firstChild.idBlock.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
child.idBlock.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
63
|
+
try {
|
|
64
|
+
const certSeq = asn1.result;
|
|
65
|
+
const certChildren = certSeq.valueBlock.value;
|
|
66
|
+
// certChildren: [tbsCertificate, signatureAlgorithm, signatureValue]
|
|
67
|
+
const tbsElement = certChildren[0];
|
|
68
|
+
const sigAlgElement = certChildren[1];
|
|
69
|
+
const sigValueElement = certChildren[2];
|
|
70
|
+
// TBS bytes — slice from the element's full encoding (tag + length + value)
|
|
71
|
+
const tbsView = tbsElement
|
|
72
|
+
.valueBeforeDecodeView;
|
|
73
|
+
const tbsCertificateDer = sliceFromView(tbsView);
|
|
74
|
+
// Signature algorithm OID
|
|
75
|
+
const sigAlgOid = sigAlgElement.valueBlock.value[0].valueBlock
|
|
76
|
+
.toString();
|
|
77
|
+
// Signature value — unused-bits byte already stripped by asn1js
|
|
78
|
+
const signatureValue = new Uint8Array(sigValueElement.valueBlock.valueHexView);
|
|
79
|
+
// TBS children — check for v3 version tag
|
|
80
|
+
const tbsChildren = tbsElement.valueBlock.value;
|
|
81
|
+
let offset = 0;
|
|
82
|
+
// First child is explicit [0] version tag for v3 certs
|
|
83
|
+
const firstChild = tbsChildren[0];
|
|
84
|
+
if (firstChild.idBlock.tagClass === 3 && // CONTEXT-SPECIFIC
|
|
85
|
+
firstChild.idBlock.tagNumber === 0) {
|
|
86
|
+
offset = 1; // Version tag present, shift all indices
|
|
87
|
+
}
|
|
88
|
+
// TBS layout (with offset): serial, sigAlg, issuer, validity, subject, SPKI, [extensions]
|
|
89
|
+
// [offset+0]=serial, [offset+1]=sigAlg, [offset+2]=issuer, [offset+3]=validity,
|
|
90
|
+
// [offset+4]=subject, [offset+5]=SPKI
|
|
91
|
+
const issuerElement = tbsChildren[offset + 2];
|
|
92
|
+
const issuerView = issuerElement
|
|
93
|
+
.valueBeforeDecodeView;
|
|
94
|
+
const issuer = sliceFromView(issuerView);
|
|
95
|
+
const validityElement = tbsChildren[offset + 3];
|
|
96
|
+
const validityChildren = validityElement.valueBlock.value;
|
|
97
|
+
const validityNotBefore = parseAsn1Date(validityChildren[0]);
|
|
98
|
+
const validityNotAfter = parseAsn1Date(validityChildren[1]);
|
|
99
|
+
const subjectElement = tbsChildren[offset + 4];
|
|
100
|
+
const subjectView = subjectElement
|
|
101
|
+
.valueBeforeDecodeView;
|
|
102
|
+
const subject = sliceFromView(subjectView);
|
|
103
|
+
const spkiElement = tbsChildren[offset + 5];
|
|
104
|
+
const spkiView = spkiElement
|
|
105
|
+
.valueBeforeDecodeView;
|
|
106
|
+
const subjectPublicKeyInfoDer = sliceFromView(spkiView);
|
|
107
|
+
// Extract curve OID from SPKI's AlgorithmIdentifier
|
|
108
|
+
const spkiSeq = spkiElement;
|
|
109
|
+
const spkiAlgId = spkiSeq.valueBlock.value[0];
|
|
110
|
+
const curveOidElement = spkiAlgId.valueBlock
|
|
111
|
+
.value[1];
|
|
112
|
+
const publicKeyCurveOid = curveOidElement.valueBlock.toString();
|
|
113
|
+
// Extensions — found in explicit [3] tag within TBS
|
|
114
|
+
const extensions = [];
|
|
115
|
+
for (let i = offset + 6; i < tbsChildren.length; i++) {
|
|
116
|
+
const child = tbsChildren[i];
|
|
117
|
+
if (child.idBlock.tagClass === 3 && // CONTEXT-SPECIFIC
|
|
118
|
+
child.idBlock.tagNumber === 3) {
|
|
119
|
+
// This is the extensions wrapper: explicit [3] containing a SEQUENCE of extensions
|
|
120
|
+
const extWrapper = child;
|
|
121
|
+
const extSeqOfSeq = extWrapper.valueBlock.value[0];
|
|
122
|
+
for (const extSeq of extSeqOfSeq.valueBlock.value) {
|
|
123
|
+
const extChildren = extSeq.valueBlock.value;
|
|
124
|
+
const oid = extChildren[0].valueBlock
|
|
125
|
+
.toString();
|
|
126
|
+
let critical = false;
|
|
127
|
+
let valueElement;
|
|
128
|
+
if (extChildren[1] instanceof asn1js.Boolean) {
|
|
129
|
+
critical = extChildren[1].getValue();
|
|
130
|
+
valueElement = extChildren[2];
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
valueElement = extChildren[1];
|
|
134
|
+
}
|
|
135
|
+
extensions.push({
|
|
136
|
+
oid,
|
|
137
|
+
critical,
|
|
138
|
+
value: new Uint8Array(valueElement.valueBlock.valueHexView),
|
|
139
|
+
});
|
|
133
140
|
}
|
|
134
|
-
|
|
135
|
-
oid,
|
|
136
|
-
critical,
|
|
137
|
-
value: new Uint8Array(valueElement.valueBlock.valueHexView),
|
|
138
|
-
});
|
|
141
|
+
break;
|
|
139
142
|
}
|
|
140
|
-
break;
|
|
141
143
|
}
|
|
144
|
+
return {
|
|
145
|
+
tbsCertificateDer,
|
|
146
|
+
signatureAlgorithmOid: sigAlgOid,
|
|
147
|
+
signatureValue,
|
|
148
|
+
issuer,
|
|
149
|
+
subject,
|
|
150
|
+
validityNotBefore,
|
|
151
|
+
validityNotAfter,
|
|
152
|
+
subjectPublicKeyInfoDer,
|
|
153
|
+
publicKeyCurveOid,
|
|
154
|
+
extensions,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
if (e instanceof AttestationError)
|
|
159
|
+
throw e;
|
|
160
|
+
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, `Invalid X.509 certificate structure: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
142
161
|
}
|
|
143
|
-
return {
|
|
144
|
-
tbsCertificateDer,
|
|
145
|
-
signatureAlgorithmOid: sigAlgOid,
|
|
146
|
-
signatureValue,
|
|
147
|
-
issuer,
|
|
148
|
-
subject,
|
|
149
|
-
validityNotBefore,
|
|
150
|
-
validityNotAfter,
|
|
151
|
-
subjectPublicKeyInfoDer,
|
|
152
|
-
publicKeyCurveOid,
|
|
153
|
-
extensions,
|
|
154
|
-
};
|
|
155
162
|
}
|
|
156
163
|
// ── extractRawPublicKeyFromSpki ─────────────────────────────────────
|
|
157
164
|
/**
|
|
@@ -164,9 +171,16 @@ function extractRawPublicKeyFromSpki(spkiDer) {
|
|
|
164
171
|
if (asn1.offset === -1) {
|
|
165
172
|
throw new AttestationError(AttestationErrorCode.INVALID_CERTIFICATE_CHAIN, "Failed to parse SPKI DER");
|
|
166
173
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
try {
|
|
175
|
+
const spkiSeq = asn1.result;
|
|
176
|
+
const publicKeyBitString = spkiSeq.valueBlock.value[1];
|
|
177
|
+
return new Uint8Array(publicKeyBitString.valueBlock.valueHexView);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
if (e instanceof AttestationError)
|
|
181
|
+
throw e;
|
|
182
|
+
throw new AttestationError(AttestationErrorCode.INVALID_CERTIFICATE_CHAIN, `Invalid SPKI structure: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
183
|
+
}
|
|
170
184
|
}
|
|
171
185
|
// ── verifySignature ─────────────────────────────────────────────────
|
|
172
186
|
/**
|
|
@@ -286,10 +300,17 @@ export function extractNonceFromCert(certDer) {
|
|
|
286
300
|
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, "Failed to parse nonce extension ASN.1");
|
|
287
301
|
}
|
|
288
302
|
// Navigate: SEQUENCE -> tagged [1] -> OCTET STRING
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
try {
|
|
304
|
+
const sequence = extAsn1.result;
|
|
305
|
+
const tagged = sequence.valueBlock.value[0];
|
|
306
|
+
const octetString = tagged.valueBlock.value[0];
|
|
307
|
+
return new Uint8Array(octetString.valueBlock.valueHexView);
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
if (e instanceof AttestationError)
|
|
311
|
+
throw e;
|
|
312
|
+
throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, `Invalid nonce extension structure: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
313
|
+
}
|
|
293
314
|
}
|
|
294
315
|
/**
|
|
295
316
|
* Extract the raw (uncompressed) public key from a DER-encoded certificate.
|
|
@@ -46,8 +46,6 @@ export type ExtractAssertionFn = (req: Request) => Promise<{
|
|
|
46
46
|
export type WithAssertionOptions = {
|
|
47
47
|
/** Apple App ID in the format `TEAMID.bundleId`. */
|
|
48
48
|
appId: string;
|
|
49
|
-
/** Set to `true` for development environment attestations. */
|
|
50
|
-
developmentEnv?: boolean;
|
|
51
49
|
/** Retrieve the stored device key for a given device ID. Return `null` if not found. */
|
|
52
50
|
getDeviceKey: (deviceId: string) => Promise<DeviceKey | null>;
|
|
53
51
|
/**
|
|
@@ -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,
|
|
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,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;AAqCF;;;;;;;;;;;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,CAmGrC"}
|
|
@@ -19,7 +19,9 @@ function defaultErrorResponse(error) {
|
|
|
19
19
|
? 500
|
|
20
20
|
: error.code === AssertionErrorCode.INVALID_FORMAT
|
|
21
21
|
? 400
|
|
22
|
-
:
|
|
22
|
+
: error.code === AssertionErrorCode.SIGN_COUNT_STALE
|
|
23
|
+
? 409
|
|
24
|
+
: 401;
|
|
23
25
|
return new Response(JSON.stringify({ error: error.message, code: error.code }), { status, headers: { "Content-Type": "application/json" } });
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
@@ -35,10 +37,7 @@ function defaultErrorResponse(error) {
|
|
|
35
37
|
* concurrent load.
|
|
36
38
|
*/
|
|
37
39
|
export function withAssertion(options, handler) {
|
|
38
|
-
const appInfo = {
|
|
39
|
-
appId: options.appId,
|
|
40
|
-
developmentEnv: options.developmentEnv ?? false,
|
|
41
|
-
};
|
|
40
|
+
const appInfo = { appId: options.appId };
|
|
42
41
|
const extract = options.extractAssertion ?? defaultExtractAssertion;
|
|
43
42
|
return async (req) => {
|
|
44
43
|
let deviceId;
|
|
@@ -90,7 +89,13 @@ export function withAssertion(options, handler) {
|
|
|
90
89
|
const error = err instanceof AssertionError ? err : new AssertionError(AssertionErrorCode.INTERNAL_ERROR, "Internal error", {
|
|
91
90
|
cause: err,
|
|
92
91
|
});
|
|
93
|
-
|
|
92
|
+
try {
|
|
93
|
+
return await options.onError?.(error, req) ??
|
|
94
|
+
defaultErrorResponse(error);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return defaultErrorResponse(error);
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
100
|
// Step 5: handler — outside try/catch, errors bubble up
|
|
96
101
|
return await handler(req, {
|
|
@@ -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,gEAAgE;IAChE,SAAS,EAAE,UAAU,CAAC;IACtB;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,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;AA8EF;;;;;;;;;;;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,gEAAgE;IAChE,SAAS,EAAE,UAAU,CAAC;IACtB;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,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;AA8EF;;;;;;;;;;;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,CAgIrC"}
|
|
@@ -146,7 +146,13 @@ export function withAttestation(options, handler) {
|
|
|
146
146
|
const error = err instanceof AttestationError
|
|
147
147
|
? err
|
|
148
148
|
: new AttestationError(AttestationErrorCode.INTERNAL_ERROR, "Internal error", { cause: err });
|
|
149
|
-
|
|
149
|
+
try {
|
|
150
|
+
return await options.onError?.(error, req) ??
|
|
151
|
+
defaultErrorResponse(error);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return defaultErrorResponse(error);
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
return await handler(req, {
|
|
152
158
|
deviceId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bradford-tech/supabase-integrity-attest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Verify Apple App Attest attestations and assertions using WebCrypto.",
|
|
5
5
|
"homepage": "https://integrity-attest.bradford.tech",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"scripts": {},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@noble/curves": "^2.0
|
|
29
|
-
"asn1js": "^3.0.
|
|
30
|
-
"cborg": "^
|
|
28
|
+
"@noble/curves": "^2.2.0",
|
|
29
|
+
"asn1js": "^3.0.10",
|
|
30
|
+
"cborg": "^5.1.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^20.9.0"
|