@motebit/crypto-appattest 1.0.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/LICENSE +206 -0
- package/NOTICE +19 -0
- package/README.md +48 -0
- package/dist/apple-root.d.ts +41 -0
- package/dist/apple-root.d.ts.map +1 -0
- package/dist/apple-root.js +55 -0
- package/dist/apple-root.js.map +1 -0
- package/dist/cbor.d.ts +32 -0
- package/dist/cbor.d.ts.map +1 -0
- package/dist/cbor.js +88 -0
- package/dist/cbor.js.map +1 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/verify.d.ts +109 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +536 -0
- package/dist/verify.js.map +1 -0
- package/package.json +80 -0
package/dist/verify.js
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Attest receipt verifier — the core judgment function this
|
|
3
|
+
* package exports.
|
|
4
|
+
*
|
|
5
|
+
* Flow (matches Apple's published verification recipe for App Attest,
|
|
6
|
+
* plus the motebit-specific identity-key binding step):
|
|
7
|
+
*
|
|
8
|
+
* 1. Split the receipt into (attestationObjectBase64,
|
|
9
|
+
* keyIdBase64, clientDataHashBase64).
|
|
10
|
+
* 2. CBOR-decode the attestation object to {fmt, attStmt:{x5c,
|
|
11
|
+
* receipt}, authData}. Assert fmt === "apple-appattest".
|
|
12
|
+
* 3. Parse leaf + intermediate as X.509. Walk the chain from leaf to
|
|
13
|
+
* a self-signed root and verify every signature, every CA bit,
|
|
14
|
+
* every validity window. The terminal cert's DER must equal the
|
|
15
|
+
* pinned Apple App Attest root.
|
|
16
|
+
* 4. Extract OID `1.2.840.113635.100.8.2` from the leaf's
|
|
17
|
+
* extensions. Assert its payload equals SHA256(authData ||
|
|
18
|
+
* clientDataHash) where clientDataHash is the transmitted hash.
|
|
19
|
+
* (In App Attest parlance: the "nonce" binding.)
|
|
20
|
+
* 5. Parse authData (WebAuthn format). First 32 bytes is
|
|
21
|
+
* `rpIdHash`; assert it equals SHA256(bundleId). (The "bundle"
|
|
22
|
+
* binding.)
|
|
23
|
+
* 6. Reconstruct the JCS-canonical attestation body from
|
|
24
|
+
* (motebit_id, device_id, identity_public_key, attested_at) the
|
|
25
|
+
* caller supplies, SHA-256 it, and byte-compare against the
|
|
26
|
+
* transmitted `clientDataHash`. This is the cross-stack binding
|
|
27
|
+
* — without it, every other step would prove only that *some*
|
|
28
|
+
* Apple-attested iOS device did something, not that the Ed25519
|
|
29
|
+
* key the credential subject claims is actually on that device.
|
|
30
|
+
*
|
|
31
|
+
* Apple's own inner `receipt` field is NOT verified here — that
|
|
32
|
+
* requires Apple's server-side refresh endpoint and is out of scope
|
|
33
|
+
* for v1. The outer chain + nonce binding + bundle binding + motebit
|
|
34
|
+
* identity binding is enough for third-party self-verification of
|
|
35
|
+
* device-attested identity.
|
|
36
|
+
*/
|
|
37
|
+
import * as x509 from "@peculiar/x509";
|
|
38
|
+
import { APPLE_APPATTEST_FMT, APPLE_APPATTEST_ROOT_PEM } from "./apple-root.js";
|
|
39
|
+
import { parseAppAttestCbor } from "./cbor.js";
|
|
40
|
+
/** OID for Apple's nonce-binding extension inside the App Attest leaf. */
|
|
41
|
+
const APPLE_NONCE_EXTENSION_OID = "1.2.840.113635.100.8.2";
|
|
42
|
+
/** OID for X.509 basic-constraints extension — carries the CA bit. */
|
|
43
|
+
const BASIC_CONSTRAINTS_OID = "2.5.29.19";
|
|
44
|
+
/**
|
|
45
|
+
* Apple App Attest attestation verifier.
|
|
46
|
+
*
|
|
47
|
+
* Pure. No network. No filesystem. Deterministic given `now()`.
|
|
48
|
+
*
|
|
49
|
+
* `claim` is the `HardwareAttestationClaim` as carried inside the
|
|
50
|
+
* motebit AgentTrustCredential. For App Attest, the
|
|
51
|
+
* `attestation_receipt` field is expected to be three base64url
|
|
52
|
+
* segments separated by `.`:
|
|
53
|
+
*
|
|
54
|
+
* `{attestationObjectB64}.{keyIdB64}.{clientDataHashB64}`
|
|
55
|
+
*
|
|
56
|
+
* The mobile mint path constructs this shape; see
|
|
57
|
+
* `apps/mobile/src/mint-hardware-credential.ts`.
|
|
58
|
+
*/
|
|
59
|
+
export async function verifyAppAttestReceipt(claim, opts) {
|
|
60
|
+
const errors = [];
|
|
61
|
+
let cert_chain_valid = false;
|
|
62
|
+
let nonce_bound = false;
|
|
63
|
+
let bundle_bound = false;
|
|
64
|
+
let identity_bound = false;
|
|
65
|
+
if (!claim.attestation_receipt) {
|
|
66
|
+
errors.push({ message: "device_check claim missing `attestation_receipt`" });
|
|
67
|
+
return fail(errors);
|
|
68
|
+
}
|
|
69
|
+
const parts = claim.attestation_receipt.split(".");
|
|
70
|
+
if (parts.length !== 3) {
|
|
71
|
+
errors.push({
|
|
72
|
+
message: `attestation_receipt must be 3 base64url parts (attObj.keyId.clientDataHash); got ${parts.length}`,
|
|
73
|
+
});
|
|
74
|
+
return fail(errors);
|
|
75
|
+
}
|
|
76
|
+
const [attObjB64, _keyIdB64, clientDataHashB64] = parts;
|
|
77
|
+
let attestationObjectBytes;
|
|
78
|
+
let clientDataHashBytes;
|
|
79
|
+
try {
|
|
80
|
+
attestationObjectBytes = fromBase64Url(attObjB64);
|
|
81
|
+
clientDataHashBytes = fromBase64Url(clientDataHashB64);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
errors.push({ message: `base64url decode failed: ${messageOf(err)}` });
|
|
85
|
+
return fail(errors);
|
|
86
|
+
}
|
|
87
|
+
let cbor;
|
|
88
|
+
try {
|
|
89
|
+
cbor = parseAppAttestCbor(attestationObjectBytes);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
errors.push({ message: `CBOR decode: ${messageOf(err)}` });
|
|
93
|
+
return fail(errors);
|
|
94
|
+
}
|
|
95
|
+
const expectedFmt = opts.expectedFmt ?? APPLE_APPATTEST_FMT;
|
|
96
|
+
if (cbor.fmt !== expectedFmt) {
|
|
97
|
+
errors.push({ message: `attestation fmt is \`${cbor.fmt}\`; expected \`${expectedFmt}\`` });
|
|
98
|
+
return fail(errors);
|
|
99
|
+
}
|
|
100
|
+
if (cbor.x5c.length < 2) {
|
|
101
|
+
errors.push({
|
|
102
|
+
message: `x5c must carry at least [leaf, intermediate]; got ${cbor.x5c.length}`,
|
|
103
|
+
});
|
|
104
|
+
return fail(errors);
|
|
105
|
+
}
|
|
106
|
+
// ── Step 3: chain verify leaf → intermediate → pinned Apple root ──
|
|
107
|
+
// Walk the chain, enforce CA constraints on every non-leaf, verify
|
|
108
|
+
// every signature, and assert the terminal cert is self-signed and
|
|
109
|
+
// byte-equal to the pinned root's DER.
|
|
110
|
+
let leafCert;
|
|
111
|
+
let intermediateCert;
|
|
112
|
+
let rootCert;
|
|
113
|
+
try {
|
|
114
|
+
leafCert = new x509.X509Certificate(toArrayBuffer(cbor.x5c[0]));
|
|
115
|
+
intermediateCert = new x509.X509Certificate(toArrayBuffer(cbor.x5c[1]));
|
|
116
|
+
rootCert = new x509.X509Certificate(opts.rootPem ?? APPLE_APPATTEST_ROOT_PEM);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
errors.push({ message: `x509 parse: ${messageOf(err)}` });
|
|
120
|
+
return fail(errors);
|
|
121
|
+
}
|
|
122
|
+
const nowDate = new Date(opts.now ? opts.now() : Date.now());
|
|
123
|
+
try {
|
|
124
|
+
const chainResult = await verifyCertChain({
|
|
125
|
+
leaf: leafCert,
|
|
126
|
+
intermediate: intermediateCert,
|
|
127
|
+
root: rootCert,
|
|
128
|
+
nowDate,
|
|
129
|
+
});
|
|
130
|
+
cert_chain_valid = chainResult.valid;
|
|
131
|
+
if (!cert_chain_valid) {
|
|
132
|
+
errors.push({ message: chainResult.reason });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
errors.push({ message: `chain verify crashed: ${messageOf(err)}` });
|
|
137
|
+
return fail(errors, { cert_chain_valid, nonce_bound, bundle_bound, identity_bound });
|
|
138
|
+
}
|
|
139
|
+
// ── Step 4: nonce binding ──
|
|
140
|
+
// The leaf carries OID 1.2.840.113635.100.8.2 whose value (after
|
|
141
|
+
// OCTET STRING unwrapping) is SHA256(authData || clientDataHash).
|
|
142
|
+
try {
|
|
143
|
+
const nonceExt = leafCert.getExtension(APPLE_NONCE_EXTENSION_OID);
|
|
144
|
+
if (!nonceExt) {
|
|
145
|
+
errors.push({
|
|
146
|
+
message: `leaf missing Apple nonce extension OID ${APPLE_NONCE_EXTENSION_OID}`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// `Extension.value` is the DER-encoded extension value — the inner
|
|
151
|
+
// bytes from the `OCTET STRING` wrapper of the Extension struct.
|
|
152
|
+
// Apple's payload sits one more level deep: `SEQUENCE { [1] EXPLICIT OCTET STRING }`.
|
|
153
|
+
const nonceFromExt = extractAppleNoncePayload(new Uint8Array(nonceExt.value));
|
|
154
|
+
const nonceExpected = await sha256Concat(cbor.authData, clientDataHashBytes);
|
|
155
|
+
if (bytesEq(nonceFromExt, nonceExpected)) {
|
|
156
|
+
nonce_bound = true;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
errors.push({
|
|
160
|
+
message: "Apple nonce extension payload does not equal SHA256(authData || clientDataHash)",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
errors.push({ message: `nonce extension parse: ${messageOf(err)}` });
|
|
167
|
+
}
|
|
168
|
+
// ── Step 5: bundle binding ──
|
|
169
|
+
// authData layout (WebAuthn): first 32 bytes are rpIdHash. For App
|
|
170
|
+
// Attest, rpIdHash = SHA256(bundleId).
|
|
171
|
+
try {
|
|
172
|
+
if (cbor.authData.length < 32) {
|
|
173
|
+
errors.push({ message: `authData shorter than 32 bytes (got ${cbor.authData.length})` });
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const rpIdHash = cbor.authData.subarray(0, 32);
|
|
177
|
+
const expected = await sha256Bytes(new TextEncoder().encode(opts.expectedBundleId));
|
|
178
|
+
if (bytesEq(rpIdHash, expected)) {
|
|
179
|
+
bundle_bound = true;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
errors.push({
|
|
183
|
+
message: `authData.rpIdHash does not equal SHA256("${opts.expectedBundleId}")`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
errors.push({ message: `bundle check crashed: ${messageOf(err)}` });
|
|
190
|
+
}
|
|
191
|
+
// ── Step 6: motebit identity-key binding ──
|
|
192
|
+
// Reconstruct the JCS-canonical attestation body the Swift mint path
|
|
193
|
+
// signed (byte-identical to the string assembled in
|
|
194
|
+
// `apps/mobile/modules/expo-app-attest/ios/ExpoAppAttestModule.swift`
|
|
195
|
+
// `CanonicalBody.encode`), SHA-256 it, and byte-compare against
|
|
196
|
+
// `clientDataHashBytes`. A malicious native client that substitutes
|
|
197
|
+
// any other body — including one naming a different Ed25519 key,
|
|
198
|
+
// motebit_id, device_id, or timestamp — produces a different hash
|
|
199
|
+
// and fails here.
|
|
200
|
+
try {
|
|
201
|
+
if (typeof opts.expectedIdentityPublicKeyHex !== "string" ||
|
|
202
|
+
opts.expectedIdentityPublicKeyHex.length === 0) {
|
|
203
|
+
errors.push({
|
|
204
|
+
message: "identity_bound: expectedIdentityPublicKeyHex not supplied",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else if (typeof opts.expectedMotebitId !== "string" || opts.expectedMotebitId.length === 0) {
|
|
208
|
+
errors.push({
|
|
209
|
+
message: "identity_bound: expectedMotebitId not supplied (required for body re-derivation)",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else if (typeof opts.expectedDeviceId !== "string" || opts.expectedDeviceId.length === 0) {
|
|
213
|
+
errors.push({
|
|
214
|
+
message: "identity_bound: expectedDeviceId not supplied (required for body re-derivation)",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else if (typeof opts.expectedAttestedAt !== "number" ||
|
|
218
|
+
!Number.isFinite(opts.expectedAttestedAt)) {
|
|
219
|
+
errors.push({
|
|
220
|
+
message: "identity_bound: expectedAttestedAt not supplied (required for body re-derivation)",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const canonicalBody = buildCanonicalAttestationBody({
|
|
225
|
+
attested_at: opts.expectedAttestedAt,
|
|
226
|
+
device_id: opts.expectedDeviceId,
|
|
227
|
+
identity_public_key: opts.expectedIdentityPublicKeyHex.toLowerCase(),
|
|
228
|
+
motebit_id: opts.expectedMotebitId,
|
|
229
|
+
});
|
|
230
|
+
const derived = await sha256Bytes(new TextEncoder().encode(canonicalBody));
|
|
231
|
+
if (bytesEq(derived, clientDataHashBytes)) {
|
|
232
|
+
identity_bound = true;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
errors.push({
|
|
236
|
+
message: "identity_bound: reconstructed SHA256(canonical body) does not equal transmitted clientDataHash — body naming the caller's identity was not the body Apple signed over",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
errors.push({ message: `identity binding crashed: ${messageOf(err)}` });
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
valid: cert_chain_valid && nonce_bound && bundle_bound && identity_bound,
|
|
246
|
+
cert_chain_valid,
|
|
247
|
+
nonce_bound,
|
|
248
|
+
bundle_bound,
|
|
249
|
+
identity_bound,
|
|
250
|
+
errors,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
254
|
+
function fail(errors, partial) {
|
|
255
|
+
return {
|
|
256
|
+
valid: false,
|
|
257
|
+
cert_chain_valid: partial?.cert_chain_valid ?? false,
|
|
258
|
+
nonce_bound: partial?.nonce_bound ?? false,
|
|
259
|
+
bundle_bound: partial?.bundle_bound ?? false,
|
|
260
|
+
identity_bound: partial?.identity_bound ?? false,
|
|
261
|
+
errors,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function messageOf(err) {
|
|
265
|
+
return err instanceof Error ? err.message : String(err);
|
|
266
|
+
}
|
|
267
|
+
async function sha256Bytes(data) {
|
|
268
|
+
const buf = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
269
|
+
return new Uint8Array(buf);
|
|
270
|
+
}
|
|
271
|
+
async function sha256Concat(a, b) {
|
|
272
|
+
const out = new Uint8Array(a.length + b.length);
|
|
273
|
+
out.set(a, 0);
|
|
274
|
+
out.set(b, a.length);
|
|
275
|
+
return sha256Bytes(out);
|
|
276
|
+
}
|
|
277
|
+
function bytesEq(a, b) {
|
|
278
|
+
if (a.length !== b.length)
|
|
279
|
+
return false;
|
|
280
|
+
for (let i = 0; i < a.length; i++)
|
|
281
|
+
if (a[i] !== b[i])
|
|
282
|
+
return false;
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Build the App Attest chain from the three supplied certs, then
|
|
287
|
+
* assert every link, validity window, CA constraint, and the terminal
|
|
288
|
+
* root-DER match. Mirrors what `@peculiar/x509`'s `X509ChainBuilder`
|
|
289
|
+
* offers (chain construction by issuer/subject matching) plus the
|
|
290
|
+
* motebit-specific invariants the pinned-root model requires.
|
|
291
|
+
*
|
|
292
|
+
* Invariants asserted:
|
|
293
|
+
* 1. `X509ChainBuilder.build(leaf)` finds a complete chain terminating
|
|
294
|
+
* at a self-signed cert using only the three supplied certs.
|
|
295
|
+
* 2. The chain's terminal cert's DER equals the pinned root's DER —
|
|
296
|
+
* the pinned cert is the only acceptable trust anchor.
|
|
297
|
+
* 3. Every non-leaf cert in the chain carries
|
|
298
|
+
* `basicConstraints.cA === true`. A misissued leaf presented as an
|
|
299
|
+
* intermediate (no CA bit) fails here even if its signature chains.
|
|
300
|
+
* 4. Every cert's signature verifies under its issuer's public key.
|
|
301
|
+
* 5. Every cert is within its validity window at `nowDate`.
|
|
302
|
+
*/
|
|
303
|
+
async function verifyCertChain(input) {
|
|
304
|
+
const { leaf, intermediate, root, nowDate } = input;
|
|
305
|
+
// Use @peculiar/x509's chain builder with the three candidate certs.
|
|
306
|
+
// It walks issuer→subject links and returns a chain terminating at
|
|
307
|
+
// a self-signed cert (or the best anchor it can find within the
|
|
308
|
+
// supplied pool).
|
|
309
|
+
const builder = new x509.X509ChainBuilder({
|
|
310
|
+
certificates: [leaf, intermediate, root],
|
|
311
|
+
});
|
|
312
|
+
// `builder.build(leaf)` walks issuer→subject links in the supplied
|
|
313
|
+
// pool and returns the longest chain it can construct starting from
|
|
314
|
+
// the leaf. Exceptions escape to the outer `verify.ts` catch, which
|
|
315
|
+
// funnels them into the structured error result — so we don't need a
|
|
316
|
+
// local try/catch here.
|
|
317
|
+
const chain = await builder.build(leaf);
|
|
318
|
+
// Terminal cert must be self-signed AND byte-equal to the pinned root
|
|
319
|
+
// DER. The self-signed check catches a chain that accidentally
|
|
320
|
+
// terminates at an intermediate (chain too short to reach a root, or
|
|
321
|
+
// a leaf mis-labelled as its own issuer); the DER-equality check
|
|
322
|
+
// catches a chain rooted at a different self-signed anchor the
|
|
323
|
+
// attacker owns.
|
|
324
|
+
const terminal = chain[chain.length - 1];
|
|
325
|
+
const terminalSelfSigned = await terminal.isSelfSigned();
|
|
326
|
+
if (!terminalSelfSigned) {
|
|
327
|
+
return {
|
|
328
|
+
valid: false,
|
|
329
|
+
reason: "chain does not terminate at a self-signed root",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (!bytesEq(new Uint8Array(terminal.rawData), new Uint8Array(root.rawData))) {
|
|
333
|
+
return {
|
|
334
|
+
valid: false,
|
|
335
|
+
reason: "chain terminal cert DER does not match the pinned root",
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// Verify every link: signature, validity, and (for non-leaves) CA
|
|
339
|
+
// constraint. `chain[0]` is the leaf; `chain[i+1]` issues `chain[i]`.
|
|
340
|
+
for (let i = 0; i < chain.length; i++) {
|
|
341
|
+
const cert = chain[i];
|
|
342
|
+
if (nowDate < cert.notBefore || nowDate > cert.notAfter) {
|
|
343
|
+
return {
|
|
344
|
+
valid: false,
|
|
345
|
+
reason: `cert at chain position ${i} is outside its validity window at ${nowDate.toISOString()}`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const isLeaf = i === 0;
|
|
349
|
+
if (!isLeaf && !certHasCaTrue(cert)) {
|
|
350
|
+
return {
|
|
351
|
+
valid: false,
|
|
352
|
+
reason: `cert at chain position ${i} lacks basicConstraints.cA=true (CA constraint not enforced)`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
// Signature: non-terminal certs are signed by chain[i+1]; terminal
|
|
356
|
+
// cert is self-signed (issuer === subject, verified against its
|
|
357
|
+
// own public key). Verification crashes bubble up to the outer
|
|
358
|
+
// catch.
|
|
359
|
+
const issuer = i === chain.length - 1 ? cert : chain[i + 1];
|
|
360
|
+
const sigOk = await cert.verify({ publicKey: issuer.publicKey, date: nowDate });
|
|
361
|
+
if (!sigOk) {
|
|
362
|
+
return {
|
|
363
|
+
valid: false,
|
|
364
|
+
reason: `cert at chain position ${i} signature did not verify under its issuer's public key`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// The supplied intermediate must appear in the built chain — if the
|
|
369
|
+
// builder routed around it (leaf mis-labelled as its own issuer, or
|
|
370
|
+
// pool somehow resolved a different intermediate), the pinned-root
|
|
371
|
+
// DER check above will already have caught the divergence. Sanity
|
|
372
|
+
// check dropped as redundant.
|
|
373
|
+
void intermediate; // explicit: the value is used only as a pool input.
|
|
374
|
+
return { valid: true, reason: "ok" };
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Read basicConstraints and return true iff `cA === true`. Uses the
|
|
378
|
+
* library's typed extension shape (`BasicConstraintsExtension.ca`); a
|
|
379
|
+
* cert that simply omits the extension fails the check.
|
|
380
|
+
*/
|
|
381
|
+
function certHasCaTrue(cert) {
|
|
382
|
+
const ext = cert.getExtension(BASIC_CONSTRAINTS_OID);
|
|
383
|
+
if (!ext)
|
|
384
|
+
return false;
|
|
385
|
+
return ext.ca === true;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Reconstruct the byte-identical canonical body the Swift mint path
|
|
389
|
+
* composes at App Attest time. Must stay byte-equal to
|
|
390
|
+
* `CanonicalBody.encode` in
|
|
391
|
+
* `apps/mobile/modules/expo-app-attest/ios/ExpoAppAttestModule.swift`.
|
|
392
|
+
*
|
|
393
|
+
* Ordering: alphabetical (JCS), which is what Swift emits literally:
|
|
394
|
+
* attested_at, device_id, identity_public_key, motebit_id, platform,
|
|
395
|
+
* version.
|
|
396
|
+
*
|
|
397
|
+
* `platform` is always `"device_check"` and `version` is always `"1"` —
|
|
398
|
+
* both constants live in the Swift and must match exactly.
|
|
399
|
+
*/
|
|
400
|
+
function buildCanonicalAttestationBody(input) {
|
|
401
|
+
return (`{"attested_at":${input.attested_at}` +
|
|
402
|
+
`,"device_id":${jsonEscapeString(input.device_id)}` +
|
|
403
|
+
`,"identity_public_key":${jsonEscapeString(input.identity_public_key)}` +
|
|
404
|
+
`,"motebit_id":${jsonEscapeString(input.motebit_id)}` +
|
|
405
|
+
`,"platform":"device_check"` +
|
|
406
|
+
`,"version":"1"}`);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Emit a JSON-escaped string literal (with quotes) byte-equal to the
|
|
410
|
+
* Swift `jsonString` escape policy:
|
|
411
|
+
* - " → \"
|
|
412
|
+
* - \ → \\
|
|
413
|
+
* - \n, \r, \t → short forms
|
|
414
|
+
* - other controls (< 0x20) → \u00XX
|
|
415
|
+
* - everything else passes through as-is.
|
|
416
|
+
*/
|
|
417
|
+
function jsonEscapeString(s) {
|
|
418
|
+
let out = '"';
|
|
419
|
+
for (const ch of s) {
|
|
420
|
+
const code = ch.codePointAt(0);
|
|
421
|
+
if (ch === '"')
|
|
422
|
+
out += '\\"';
|
|
423
|
+
else if (ch === "\\")
|
|
424
|
+
out += "\\\\";
|
|
425
|
+
else if (ch === "\n")
|
|
426
|
+
out += "\\n";
|
|
427
|
+
else if (ch === "\r")
|
|
428
|
+
out += "\\r";
|
|
429
|
+
else if (ch === "\t")
|
|
430
|
+
out += "\\t";
|
|
431
|
+
else if (code < 0x20)
|
|
432
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
433
|
+
else
|
|
434
|
+
out += ch;
|
|
435
|
+
}
|
|
436
|
+
out += '"';
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Apple's nonce extension wraps its payload as:
|
|
441
|
+
*
|
|
442
|
+
* SEQUENCE {
|
|
443
|
+
* [1] EXPLICIT {
|
|
444
|
+
* OCTET STRING <32 bytes of SHA256(authData || clientDataHash)>
|
|
445
|
+
* }
|
|
446
|
+
* }
|
|
447
|
+
*
|
|
448
|
+
* We decode enough ASN.1 to locate the inner 32-byte octet string. If
|
|
449
|
+
* the shape ever diverges (Apple adds tagged fields, rotates the
|
|
450
|
+
* wrapping structure), the parser fails closed with a descriptive
|
|
451
|
+
* error, not a silent mismatch.
|
|
452
|
+
*/
|
|
453
|
+
function extractAppleNoncePayload(derBytes) {
|
|
454
|
+
// Walk a minimal DER parser: SEQUENCE → context[1] EXPLICIT → OCTET
|
|
455
|
+
// STRING. Bail with an exception if anything unexpected shows up.
|
|
456
|
+
const reader = new DerReader(derBytes);
|
|
457
|
+
const outer = reader.readTlv();
|
|
458
|
+
if (outer.tag !== 0x30)
|
|
459
|
+
throw new Error(`expected SEQUENCE (0x30), got 0x${outer.tag.toString(16)}`);
|
|
460
|
+
const inner = new DerReader(outer.value);
|
|
461
|
+
// [1] EXPLICIT → context-specific class (0x80) + constructed (0x20) + tag 1 = 0xa1
|
|
462
|
+
while (inner.hasMore()) {
|
|
463
|
+
const tlv = inner.readTlv();
|
|
464
|
+
if (tlv.tag === 0xa1) {
|
|
465
|
+
const payload = new DerReader(tlv.value).readTlv();
|
|
466
|
+
if (payload.tag !== 0x04) {
|
|
467
|
+
throw new Error(`expected OCTET STRING inside context[1], got 0x${payload.tag.toString(16)}`);
|
|
468
|
+
}
|
|
469
|
+
return payload.value;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
throw new Error("context[1] OCTET STRING not found in nonce extension");
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Dependency-free DER reader. We only need enough DER to extract
|
|
476
|
+
* the Apple nonce extension. Full DER/BER parsing is `@peculiar/asn1-*`
|
|
477
|
+
* territory; we'd rather not pull a second ASN.1 dependency for a
|
|
478
|
+
* single extension shape.
|
|
479
|
+
*/
|
|
480
|
+
class DerReader {
|
|
481
|
+
bytes;
|
|
482
|
+
offset = 0;
|
|
483
|
+
constructor(bytes) {
|
|
484
|
+
this.bytes = bytes;
|
|
485
|
+
}
|
|
486
|
+
hasMore() {
|
|
487
|
+
return this.offset < this.bytes.length;
|
|
488
|
+
}
|
|
489
|
+
readTlv() {
|
|
490
|
+
if (this.offset + 2 > this.bytes.length)
|
|
491
|
+
throw new Error("unexpected end of DER");
|
|
492
|
+
const tag = this.bytes[this.offset++];
|
|
493
|
+
let len = this.bytes[this.offset++];
|
|
494
|
+
if (len & 0x80) {
|
|
495
|
+
const nBytes = len & 0x7f;
|
|
496
|
+
if (nBytes > 4)
|
|
497
|
+
throw new Error("DER length exceeds 4 bytes");
|
|
498
|
+
len = 0;
|
|
499
|
+
for (let i = 0; i < nBytes; i++) {
|
|
500
|
+
if (this.offset >= this.bytes.length)
|
|
501
|
+
throw new Error("DER length truncated");
|
|
502
|
+
len = (len << 8) | this.bytes[this.offset++];
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (this.offset + len > this.bytes.length)
|
|
506
|
+
throw new Error("DER value truncated");
|
|
507
|
+
const value = this.bytes.subarray(this.offset, this.offset + len);
|
|
508
|
+
this.offset += len;
|
|
509
|
+
return { tag, value };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function toArrayBuffer(bytes) {
|
|
513
|
+
// Force a fresh ArrayBuffer copy — @peculiar/x509's constructor typing
|
|
514
|
+
// is strict about `ArrayBuffer` vs `SharedArrayBuffer`; returning the
|
|
515
|
+
// underlying buffer directly can hit the SAB case in some TS lib
|
|
516
|
+
// configurations.
|
|
517
|
+
const copy = new Uint8Array(bytes.length);
|
|
518
|
+
copy.set(bytes);
|
|
519
|
+
return copy.buffer;
|
|
520
|
+
}
|
|
521
|
+
function fromBase64Url(str) {
|
|
522
|
+
let b64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
523
|
+
const pad = b64.length % 4;
|
|
524
|
+
if (pad === 2)
|
|
525
|
+
b64 += "==";
|
|
526
|
+
else if (pad === 3)
|
|
527
|
+
b64 += "=";
|
|
528
|
+
else if (pad === 1)
|
|
529
|
+
throw new Error("invalid base64url length");
|
|
530
|
+
const binary = atob(b64);
|
|
531
|
+
const out = new Uint8Array(binary.length);
|
|
532
|
+
for (let i = 0; i < binary.length; i++)
|
|
533
|
+
out[i] = binary.charCodeAt(i);
|
|
534
|
+
return out;
|
|
535
|
+
}
|
|
536
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@motebit/crypto-appattest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Apache-2.0 verifier for Apple App Attest hardware-attestation credentials — offline chain verification against the pinned Apple App Attest root CA. Plugs into @motebit/crypto's HardwareAttestationVerifiers dispatcher to validate iOS device-attested motebit identities.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/**/*.js",
|
|
16
|
+
"dist/**/*.js.map",
|
|
17
|
+
"dist/**/*.d.ts",
|
|
18
|
+
"dist/**/*.d.ts.map",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"NOTICE",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"motebit",
|
|
27
|
+
"apple",
|
|
28
|
+
"app-attest",
|
|
29
|
+
"device-check",
|
|
30
|
+
"attestation",
|
|
31
|
+
"ios",
|
|
32
|
+
"hardware-attestation",
|
|
33
|
+
"x509",
|
|
34
|
+
"cbor",
|
|
35
|
+
"verify"
|
|
36
|
+
],
|
|
37
|
+
"homepage": "https://github.com/motebit/motebit/tree/main/packages/crypto-appattest#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/motebit/motebit/issues"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/motebit/motebit",
|
|
44
|
+
"directory": "packages/crypto-appattest"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"motebit": {
|
|
50
|
+
"implements": [
|
|
51
|
+
"spec/credential-v1.md"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@peculiar/x509": "^1.12.0",
|
|
56
|
+
"cbor2": "^1.9.0",
|
|
57
|
+
"@motebit/protocol": "1.0.0",
|
|
58
|
+
"@motebit/crypto": "1.0.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@noble/curves": "~1.9.0",
|
|
62
|
+
"@noble/hashes": "~1.6.0",
|
|
63
|
+
"@peculiar/webcrypto": "^1.5.0",
|
|
64
|
+
"@types/node": "^22.0.0",
|
|
65
|
+
"typescript": "^5.6.0",
|
|
66
|
+
"vitest": "^2.1.0"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsc -b",
|
|
73
|
+
"test": "vitest run",
|
|
74
|
+
"test:coverage": "vitest run --coverage",
|
|
75
|
+
"typecheck": "tsc --noEmit",
|
|
76
|
+
"lint": "eslint --parser-options=project:tsconfig.eslint.json src/",
|
|
77
|
+
"lint:pack": "publint --strict && attw --pack --profile esm-only",
|
|
78
|
+
"clean": "rm -rf dist .turbo *.tsbuildinfo"
|
|
79
|
+
}
|
|
80
|
+
}
|