@motebit/crypto 0.8.0 → 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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Hardware attestation — verify that a motebit's Ed25519 identity key
3
+ * is bound to a hardware-backed ECDSA P-256 key held in a platform
4
+ * trust anchor (Apple Secure Enclave today; TPM / Play Integrity /
5
+ * DeviceCheck as future additive adapters).
6
+ *
7
+ * ## Why this exists
8
+ *
9
+ * Motebit's identity key is Ed25519, stored in the OS keyring on
10
+ * desktop and in equivalent app-sandboxed stores on mobile/web. That
11
+ * key is *software-custody*: the private bytes are readable by any
12
+ * process running as the user. The moat thesis — "accumulated trust
13
+ * that a third party can verify" — is categorically weaker without a
14
+ * hardware root. Hardware attestation bridges the gap without forcing
15
+ * a cryptosuite migration: a separate hardware-native keypair (Apple
16
+ * Secure Enclave generates ECDSA P-256) signs a canonical claim that
17
+ * binds itself to the Ed25519 identity. The identity stays where it
18
+ * is; the hardware signature is *additional* evidence a verifier can
19
+ * rank against.
20
+ *
21
+ * Same shape as FIDO / WebAuthn attestation — the platform root key
22
+ * is distinct from the user-facing identity, and one attests the
23
+ * other.
24
+ *
25
+ * ## Receipt format (`platform: "secure_enclave"`)
26
+ *
27
+ * attestation_receipt = base64url(canonical_body_json) + "." +
28
+ * base64url(ecdsa_p256_signature_der)
29
+ *
30
+ * canonical_body_json = JCS-canonicalized JSON of:
31
+ * {
32
+ * version: "1",
33
+ * algorithm: "ecdsa-p256-sha256",
34
+ * motebit_id: string,
35
+ * device_id: string,
36
+ * identity_public_key: Ed25519 hex lowercase,
37
+ * se_public_key: P-256 compressed-point hex lowercase,
38
+ * attested_at: unix ms,
39
+ * }
40
+ *
41
+ * The P-256 signature is over `SHA-256(canonical_body_json)` — standard
42
+ * ECDSA-on-SHA256. The verifier recovers the SE public key from
43
+ * `body.se_public_key` (self-contained; zero relay contact), verifies
44
+ * the signature, then checks that `body.identity_public_key` equals the
45
+ * Ed25519 key the credential subject is claimed for.
46
+ *
47
+ * ## Non-goals in v1
48
+ *
49
+ * - Other platforms (TPM / DeviceCheck / Play Integrity) — each
50
+ * returns `valid: false` + a named-missing-adapter error. Additive
51
+ * platform adapters plug in behind the same result shape.
52
+ * - Revocation — claims expire with their parent credential's
53
+ * expiry. No separate revocation channel.
54
+ * - Chain-of-trust verification — the SE public key is the
55
+ * self-asserted root in v1. Future platform adapters verify the
56
+ * platform's own attestation chain (Apple's root CA, Google's
57
+ * verified-boot chain, etc.) as glucose per the metabolic
58
+ * principle.
59
+ *
60
+ * Permissive floor (Apache-2.0), no I/O, deterministic. Safe to run in any
61
+ * environment that can parse UTF-8 JSON.
62
+ */
63
+ import type { HardwareAttestationClaim } from "@motebit/protocol";
64
+ /**
65
+ * Platform identifier mirrored from `HardwareAttestationClaim.platform`.
66
+ * Declared locally so hardware-attestation.ts isn't coupled to whether
67
+ * protocol exports it as a named type — the union literal is the
68
+ * contract.
69
+ */
70
+ export type AttestationPlatform = HardwareAttestationClaim["platform"];
71
+ /**
72
+ * One verification error in the result. Matches the shape used by the
73
+ * other `@motebit/crypto` verify functions so callers can surface
74
+ * errors uniformly.
75
+ */
76
+ export interface HardwareAttestationError {
77
+ readonly message: string;
78
+ }
79
+ /**
80
+ * Result of verifying one `HardwareAttestationClaim`. `valid` reflects
81
+ * only the platform-verification outcome for the receipt — identity-key
82
+ * binding is checked separately via `expectedIdentityPublicKeyHex`.
83
+ *
84
+ * For the `secure_enclave` platform, a `valid: true` result asserts:
85
+ * 1. The receipt is well-formed JWS-shape (body . signature).
86
+ * 2. The body's algorithm field is `ecdsa-p256-sha256`.
87
+ * 3. The P-256 signature verifies against the body bytes + the
88
+ * SE public key carried inside the body.
89
+ * 4. The body's `identity_public_key` equals the expected Ed25519
90
+ * key the caller provided.
91
+ *
92
+ * Other platforms are not implemented in v1 and return
93
+ * `valid: false, errors: [{message: "...adapter not shipped..."}]`.
94
+ * Adapters plug in behind this same result shape; a verifier that
95
+ * ignores the `se_public_key` field stays forward-compatible.
96
+ */
97
+ export interface HardwareAttestationVerifyResult {
98
+ readonly valid: boolean;
99
+ readonly platform: AttestationPlatform | null;
100
+ /** P-256 pubkey (compressed hex) recovered from a verified SE receipt. */
101
+ readonly se_public_key?: string;
102
+ /** Unix ms timestamp from a verified body, if any. */
103
+ readonly attested_at?: number;
104
+ readonly errors: readonly HardwareAttestationError[];
105
+ }
106
+ /**
107
+ * Context fields the dispatcher lifts out of the VC subject and hands
108
+ * to the `deviceCheck` arm so it can re-derive the JCS body Apple
109
+ * signed over. motebit_id / device_id / attested_at participate in
110
+ * that body alongside identity_public_key; without them the verifier
111
+ * cannot bind the receipt to the caller's identity. Each field is
112
+ * optional at the type level so an older credential subject that
113
+ * doesn't carry them flows through with `identity_bound: false`
114
+ * rather than crashing the verifier.
115
+ */
116
+ export interface DeviceCheckVerifierContext {
117
+ readonly expectedMotebitId?: string;
118
+ readonly expectedDeviceId?: string;
119
+ readonly expectedAttestedAt?: number;
120
+ }
121
+ /**
122
+ * Optional platform-verifier dispatch injected at call site by the
123
+ * consumer. Each slot takes the claim + the expected Ed25519 identity
124
+ * key (lowercase hex) and returns a verification result matching the
125
+ * canonical shape.
126
+ *
127
+ * `@motebit/crypto` stays permissive-floor-pure and dep-thin — it never imports a
128
+ * platform adapter. Consumers (CLI, mobile, desktop, relay) wire the
129
+ * leaf packages (`@motebit/crypto-appattest` for device_check;
130
+ * future `@motebit/crypto-tpm`, `@motebit/crypto-play-integrity`) into
131
+ * this object so that dispatch remains explicit, auditable, and
132
+ * tree-shakable: a verifier that doesn't care about App Attest ships
133
+ * zero App Attest code.
134
+ *
135
+ * `deviceCheck` takes an optional third `context` argument carrying
136
+ * the VC-subject fields that participate in the JCS body the Swift
137
+ * mint path signs over (motebit_id / device_id / attested_at). The
138
+ * dispatcher populates this from the credential subject; direct
139
+ * callers threading their own context can too. Older injected
140
+ * verifiers that ignore the third argument still satisfy the type.
141
+ */
142
+ export interface HardwareAttestationVerifiers {
143
+ readonly deviceCheck?: (claim: HardwareAttestationClaim, expectedIdentityPublicKeyHex: string, context?: DeviceCheckVerifierContext) => HardwareAttestationVerifyResult | PromiseLike<HardwareAttestationVerifyResult> | {
144
+ readonly valid: boolean;
145
+ readonly errors: ReadonlyArray<{
146
+ readonly message: string;
147
+ }>;
148
+ } | PromiseLike<{
149
+ readonly valid: boolean;
150
+ readonly errors: ReadonlyArray<{
151
+ readonly message: string;
152
+ }>;
153
+ }>;
154
+ readonly tpm?: (claim: HardwareAttestationClaim, expectedIdentityPublicKeyHex: string, context?: DeviceCheckVerifierContext) => HardwareAttestationVerifyResult | PromiseLike<HardwareAttestationVerifyResult> | {
155
+ readonly valid: boolean;
156
+ readonly errors: ReadonlyArray<{
157
+ readonly message: string;
158
+ }>;
159
+ } | PromiseLike<{
160
+ readonly valid: boolean;
161
+ readonly errors: ReadonlyArray<{
162
+ readonly message: string;
163
+ }>;
164
+ }>;
165
+ readonly playIntegrity?: (claim: HardwareAttestationClaim, expectedIdentityPublicKeyHex: string, context?: DeviceCheckVerifierContext) => HardwareAttestationVerifyResult | PromiseLike<HardwareAttestationVerifyResult> | {
166
+ readonly valid: boolean;
167
+ readonly errors: ReadonlyArray<{
168
+ readonly message: string;
169
+ }>;
170
+ } | PromiseLike<{
171
+ readonly valid: boolean;
172
+ readonly errors: ReadonlyArray<{
173
+ readonly message: string;
174
+ }>;
175
+ }>;
176
+ readonly webauthn?: (claim: HardwareAttestationClaim, expectedIdentityPublicKeyHex: string, context?: DeviceCheckVerifierContext) => HardwareAttestationVerifyResult | PromiseLike<HardwareAttestationVerifyResult> | {
177
+ readonly valid: boolean;
178
+ readonly errors: ReadonlyArray<{
179
+ readonly message: string;
180
+ }>;
181
+ } | PromiseLike<{
182
+ readonly valid: boolean;
183
+ readonly errors: ReadonlyArray<{
184
+ readonly message: string;
185
+ }>;
186
+ }>;
187
+ }
188
+ /**
189
+ * Verify a hardware-attestation claim.
190
+ *
191
+ * - `claim` — the `HardwareAttestationClaim` taken from a credential's
192
+ * `credentialSubject.hardware_attestation`.
193
+ * - `expectedIdentityPublicKeyHex` — the Ed25519 public key (hex) the
194
+ * verifier believes owns the credential. Comes from the credential
195
+ * issuance path (typically the subject's DID pubkey).
196
+ * - `verifiers` — optional injection of platform-specific verifiers for
197
+ * claims other than `secure_enclave`. Consumers pass
198
+ * `{ deviceCheck: deviceCheckVerifier(...) }` from
199
+ * `@motebit/crypto-appattest` to enable App Attest verification. When
200
+ * a claim's platform has no verifier wired, the dispatcher returns a
201
+ * stub `valid: false, errors: [{message:"adapter not yet shipped"}]`
202
+ * so verification remains fail-closed by default.
203
+ * - `deviceCheckContext` — VC-subject fields (motebit_id / device_id /
204
+ * attested_at) lifted from the credential subject; threaded to the
205
+ * injected `deviceCheck` verifier so it can re-derive the JCS body
206
+ * Apple signed over. Ignored for every other platform.
207
+ *
208
+ * Zero throws — every failure lands as `valid: false` with a structured
209
+ * reason so callers can render consistent audit output. The
210
+ * secure_enclave path remains synchronous; device_check (and any other
211
+ * injected adapter) may return a Promise, so callers that dispatch
212
+ * through the `verify()` entrypoint get `await`ed results.
213
+ */
214
+ export declare function verifyHardwareAttestationClaim(claim: HardwareAttestationClaim, expectedIdentityPublicKeyHex: string, verifiers?: HardwareAttestationVerifiers, deviceCheckContext?: DeviceCheckVerifierContext): HardwareAttestationVerifyResult | Promise<HardwareAttestationVerifyResult>;
215
+ /**
216
+ * Test-only helper — encode a canonical body + signature into the
217
+ * receipt format. Tests that have a P-256 private key (via
218
+ * `@noble/curves/p256`) can call `signBytes` themselves, then hand the
219
+ * resulting body + signature to this helper to produce a well-formed
220
+ * receipt that `verifyHardwareAttestationClaim` will accept. Production
221
+ * callers MUST mint receipts via the Rust Secure Enclave bridge —
222
+ * never through this function.
223
+ */
224
+ export declare function encodeSecureEnclaveReceiptForTest(bodyBytes: Uint8Array, sigBytes: Uint8Array): string;
225
+ /**
226
+ * Test-only helper — build a canonical body JSON's bytes. Use with
227
+ * `encodeSecureEnclaveReceiptForTest` to produce a full receipt for
228
+ * verification tests. Canonicalization matches what production would
229
+ * emit.
230
+ */
231
+ export declare function canonicalSecureEnclaveBodyForTest(body: {
232
+ readonly motebit_id: string;
233
+ readonly device_id: string;
234
+ readonly identity_public_key: string;
235
+ readonly se_public_key: string;
236
+ readonly attested_at: number;
237
+ }): Uint8Array;
238
+ //# sourceMappingURL=hardware-attestation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hardware-attestation.d.ts","sourceRoot":"","sources":["../src/hardware-attestation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Hardware attestation — verify that a motebit's Ed25519 identity key
3
+ * is bound to a hardware-backed ECDSA P-256 key held in a platform
4
+ * trust anchor (Apple Secure Enclave today; TPM / Play Integrity /
5
+ * DeviceCheck as future additive adapters).
6
+ *
7
+ * ## Why this exists
8
+ *
9
+ * Motebit's identity key is Ed25519, stored in the OS keyring on
10
+ * desktop and in equivalent app-sandboxed stores on mobile/web. That
11
+ * key is *software-custody*: the private bytes are readable by any
12
+ * process running as the user. The moat thesis — "accumulated trust
13
+ * that a third party can verify" — is categorically weaker without a
14
+ * hardware root. Hardware attestation bridges the gap without forcing
15
+ * a cryptosuite migration: a separate hardware-native keypair (Apple
16
+ * Secure Enclave generates ECDSA P-256) signs a canonical claim that
17
+ * binds itself to the Ed25519 identity. The identity stays where it
18
+ * is; the hardware signature is *additional* evidence a verifier can
19
+ * rank against.
20
+ *
21
+ * Same shape as FIDO / WebAuthn attestation — the platform root key
22
+ * is distinct from the user-facing identity, and one attests the
23
+ * other.
24
+ *
25
+ * ## Receipt format (`platform: "secure_enclave"`)
26
+ *
27
+ * attestation_receipt = base64url(canonical_body_json) + "." +
28
+ * base64url(ecdsa_p256_signature_der)
29
+ *
30
+ * canonical_body_json = JCS-canonicalized JSON of:
31
+ * {
32
+ * version: "1",
33
+ * algorithm: "ecdsa-p256-sha256",
34
+ * motebit_id: string,
35
+ * device_id: string,
36
+ * identity_public_key: Ed25519 hex lowercase,
37
+ * se_public_key: P-256 compressed-point hex lowercase,
38
+ * attested_at: unix ms,
39
+ * }
40
+ *
41
+ * The P-256 signature is over `SHA-256(canonical_body_json)` — standard
42
+ * ECDSA-on-SHA256. The verifier recovers the SE public key from
43
+ * `body.se_public_key` (self-contained; zero relay contact), verifies
44
+ * the signature, then checks that `body.identity_public_key` equals the
45
+ * Ed25519 key the credential subject is claimed for.
46
+ *
47
+ * ## Non-goals in v1
48
+ *
49
+ * - Other platforms (TPM / DeviceCheck / Play Integrity) — each
50
+ * returns `valid: false` + a named-missing-adapter error. Additive
51
+ * platform adapters plug in behind the same result shape.
52
+ * - Revocation — claims expire with their parent credential's
53
+ * expiry. No separate revocation channel.
54
+ * - Chain-of-trust verification — the SE public key is the
55
+ * self-asserted root in v1. Future platform adapters verify the
56
+ * platform's own attestation chain (Apple's root CA, Google's
57
+ * verified-boot chain, etc.) as glucose per the metabolic
58
+ * principle.
59
+ *
60
+ * Permissive floor (Apache-2.0), no I/O, deterministic. Safe to run in any
61
+ * environment that can parse UTF-8 JSON.
62
+ */
63
+ import { canonicalJson, fromBase64Url } from "./signing.js";
64
+ import { verifyP256EcdsaSha256 } from "./suite-dispatch.js";
65
+ /**
66
+ * Verify a hardware-attestation claim.
67
+ *
68
+ * - `claim` — the `HardwareAttestationClaim` taken from a credential's
69
+ * `credentialSubject.hardware_attestation`.
70
+ * - `expectedIdentityPublicKeyHex` — the Ed25519 public key (hex) the
71
+ * verifier believes owns the credential. Comes from the credential
72
+ * issuance path (typically the subject's DID pubkey).
73
+ * - `verifiers` — optional injection of platform-specific verifiers for
74
+ * claims other than `secure_enclave`. Consumers pass
75
+ * `{ deviceCheck: deviceCheckVerifier(...) }` from
76
+ * `@motebit/crypto-appattest` to enable App Attest verification. When
77
+ * a claim's platform has no verifier wired, the dispatcher returns a
78
+ * stub `valid: false, errors: [{message:"adapter not yet shipped"}]`
79
+ * so verification remains fail-closed by default.
80
+ * - `deviceCheckContext` — VC-subject fields (motebit_id / device_id /
81
+ * attested_at) lifted from the credential subject; threaded to the
82
+ * injected `deviceCheck` verifier so it can re-derive the JCS body
83
+ * Apple signed over. Ignored for every other platform.
84
+ *
85
+ * Zero throws — every failure lands as `valid: false` with a structured
86
+ * reason so callers can render consistent audit output. The
87
+ * secure_enclave path remains synchronous; device_check (and any other
88
+ * injected adapter) may return a Promise, so callers that dispatch
89
+ * through the `verify()` entrypoint get `await`ed results.
90
+ */
91
+ export function verifyHardwareAttestationClaim(claim, expectedIdentityPublicKeyHex, verifiers, deviceCheckContext) {
92
+ const platform = claim.platform;
93
+ const errors = [];
94
+ switch (platform) {
95
+ case "secure_enclave":
96
+ return verifySecureEnclaveClaim(claim, expectedIdentityPublicKeyHex);
97
+ case "software":
98
+ // Truthful "this key is not hardware-backed" claim — valid in
99
+ // the sense of "no deception," but offers no hardware signal. The
100
+ // semiring scores it as `0.1` (see `@motebit/semiring`). Report
101
+ // as `valid: false` for the hardware-verification channel: the
102
+ // claim doesn't prove hardware, and the caller should score it
103
+ // via the semiring, not treat it as attested.
104
+ errors.push({
105
+ message: "platform `software` is a no-hardware sentinel; no verification channel",
106
+ });
107
+ return { valid: false, platform: "software", errors };
108
+ case "device_check":
109
+ if (verifiers?.deviceCheck) {
110
+ return dispatchInjected(platform, verifiers.deviceCheck(claim, expectedIdentityPublicKeyHex, deviceCheckContext));
111
+ }
112
+ errors.push({
113
+ message: `platform \`${platform}\` verifier not wired — pass { deviceCheck: deviceCheckVerifier(...) } from @motebit/crypto-appattest to enable`,
114
+ });
115
+ return { valid: false, platform, errors };
116
+ case "tpm":
117
+ if (verifiers?.tpm) {
118
+ return dispatchInjected(platform, verifiers.tpm(claim, expectedIdentityPublicKeyHex, deviceCheckContext));
119
+ }
120
+ errors.push({
121
+ message: `platform \`${platform}\` verifier not wired — pass { tpm: ... } via the verifiers parameter to enable`,
122
+ });
123
+ return { valid: false, platform, errors };
124
+ case "play_integrity":
125
+ if (verifiers?.playIntegrity) {
126
+ return dispatchInjected(platform, verifiers.playIntegrity(claim, expectedIdentityPublicKeyHex, deviceCheckContext));
127
+ }
128
+ errors.push({
129
+ message: `platform \`${platform}\` verifier not wired — pass { playIntegrity: ... } via the verifiers parameter to enable`,
130
+ });
131
+ return { valid: false, platform, errors };
132
+ case "webauthn":
133
+ if (verifiers?.webauthn) {
134
+ return dispatchInjected(platform, verifiers.webauthn(claim, expectedIdentityPublicKeyHex, deviceCheckContext));
135
+ }
136
+ errors.push({
137
+ message: `platform \`${platform}\` verifier not wired — pass { webauthn: webauthnVerifier(...) } from @motebit/crypto-webauthn to enable`,
138
+ });
139
+ return { valid: false, platform, errors };
140
+ default:
141
+ errors.push({
142
+ message: `unknown platform \`${claim.platform}\` — not in the declared enum`,
143
+ });
144
+ return { valid: false, platform: null, errors };
145
+ }
146
+ }
147
+ /**
148
+ * Normalize the injected verifier's return shape into the canonical
149
+ * `HardwareAttestationVerifyResult`. Injected leaves may return a
150
+ * richer shape (e.g. `@motebit/crypto-appattest`'s
151
+ * `DeviceCheckVerifyResult` carries `attestation_detail`); the
152
+ * canonical fields are lifted out here so the outer VC verify path
153
+ * always sees the same shape.
154
+ */
155
+ async function dispatchInjected(platform, result) {
156
+ const awaited = await Promise.resolve(result);
157
+ return {
158
+ valid: awaited.valid,
159
+ platform,
160
+ errors: awaited.errors ?? [],
161
+ };
162
+ }
163
+ // ── Secure Enclave path ──────────────────────────────────────────────
164
+ function verifySecureEnclaveClaim(claim, expectedIdentityPublicKeyHex) {
165
+ const errors = [];
166
+ const platform = "secure_enclave";
167
+ if (claim.key_exported === true) {
168
+ // An exported hardware key no longer uniquely holds the material;
169
+ // the signed receipt is historical evidence only. We still verify
170
+ // the signature — it's still meaningful — but the result's caller
171
+ // should score it with the lower semiring value (see
172
+ // `scoreAttestation` in @motebit/semiring: exported → 0.5). We do
173
+ // not short-circuit as invalid — that would conflate "key was
174
+ // exported" with "attestation fraudulent."
175
+ }
176
+ if (!claim.attestation_receipt) {
177
+ errors.push({
178
+ message: "secure_enclave claim missing `attestation_receipt`",
179
+ });
180
+ return { valid: false, platform, errors };
181
+ }
182
+ const parts = claim.attestation_receipt.split(".");
183
+ if (parts.length !== 2) {
184
+ errors.push({
185
+ message: `attestation_receipt must be 2 base64url parts separated by '.'; got ${parts.length}`,
186
+ });
187
+ return { valid: false, platform, errors };
188
+ }
189
+ const [bodyB64, sigB64] = parts;
190
+ let bodyBytes;
191
+ let sigBytes;
192
+ try {
193
+ bodyBytes = fromBase64Url(bodyB64);
194
+ sigBytes = fromBase64Url(sigB64);
195
+ }
196
+ catch (err) {
197
+ errors.push({
198
+ message: `base64url decode failed: ${err instanceof Error ? err.message : String(err)}`,
199
+ });
200
+ return { valid: false, platform, errors };
201
+ }
202
+ let bodyJson;
203
+ try {
204
+ bodyJson = JSON.parse(new TextDecoder().decode(bodyBytes));
205
+ }
206
+ catch (err) {
207
+ errors.push({
208
+ message: `body JSON parse failed: ${err instanceof Error ? err.message : String(err)}`,
209
+ });
210
+ return { valid: false, platform, errors };
211
+ }
212
+ const bodyCheck = parseSecureEnclaveBody(bodyJson);
213
+ if (bodyCheck.kind === "error") {
214
+ errors.push({ message: bodyCheck.reason });
215
+ return { valid: false, platform, errors };
216
+ }
217
+ const body = bodyCheck.body;
218
+ if (body.version !== "1") {
219
+ errors.push({
220
+ message: `unsupported body version '${body.version}' (expected '1')`,
221
+ });
222
+ return { valid: false, platform, errors };
223
+ }
224
+ if (body.algorithm !== "ecdsa-p256-sha256") {
225
+ errors.push({
226
+ message: `unsupported body algorithm '${body.algorithm}' (expected 'ecdsa-p256-sha256')`,
227
+ });
228
+ return { valid: false, platform, errors };
229
+ }
230
+ // Re-canonicalize the body and hash — the signed bytes are what the
231
+ // signer actually signed. The body we parsed is the source of truth
232
+ // for *fields*, but the signed bytes must match the on-wire body
233
+ // bytes exactly (JCS is deterministic, but the signer might have
234
+ // produced the body with trailing whitespace etc. — we verify
235
+ // against the as-received bytes, not our re-canonicalization).
236
+ let sigValid;
237
+ try {
238
+ sigValid = verifyP256EcdsaSha256(body.se_public_key, bodyBytes, sigBytes);
239
+ }
240
+ catch (err) {
241
+ errors.push({
242
+ message: `p-256 verification crashed: ${err instanceof Error ? err.message : String(err)}`,
243
+ });
244
+ return { valid: false, platform, errors };
245
+ }
246
+ if (!sigValid) {
247
+ errors.push({
248
+ message: "p-256 signature does not verify against body + se_public_key",
249
+ });
250
+ return { valid: false, platform, errors };
251
+ }
252
+ // Identity-key binding check — the attestation body names which
253
+ // Ed25519 key this hardware claim is for. The verifier has the
254
+ // expected key from the credential's issuance context.
255
+ if (body.identity_public_key.toLowerCase() !== expectedIdentityPublicKeyHex.toLowerCase()) {
256
+ errors.push({
257
+ message: `identity_public_key mismatch: body names ${body.identity_public_key.slice(0, 16)}…, expected ${expectedIdentityPublicKeyHex.slice(0, 16)}…`,
258
+ });
259
+ return { valid: false, platform, errors };
260
+ }
261
+ return {
262
+ valid: true,
263
+ platform,
264
+ se_public_key: body.se_public_key,
265
+ attested_at: body.attested_at,
266
+ errors: [],
267
+ };
268
+ }
269
+ function parseSecureEnclaveBody(raw) {
270
+ if (raw === null || typeof raw !== "object") {
271
+ return { kind: "error", reason: "body is not a JSON object" };
272
+ }
273
+ const r = raw;
274
+ const fields = [
275
+ "version",
276
+ "algorithm",
277
+ "motebit_id",
278
+ "device_id",
279
+ "identity_public_key",
280
+ "se_public_key",
281
+ "attested_at",
282
+ ];
283
+ for (const f of fields) {
284
+ if (!(f in r)) {
285
+ return { kind: "error", reason: `body missing required field '${f}'` };
286
+ }
287
+ }
288
+ if (typeof r.version !== "string" ||
289
+ typeof r.algorithm !== "string" ||
290
+ typeof r.motebit_id !== "string" ||
291
+ typeof r.device_id !== "string" ||
292
+ typeof r.identity_public_key !== "string" ||
293
+ typeof r.se_public_key !== "string" ||
294
+ typeof r.attested_at !== "number") {
295
+ return { kind: "error", reason: "body field types invalid" };
296
+ }
297
+ return {
298
+ kind: "ok",
299
+ body: {
300
+ version: r.version,
301
+ algorithm: r.algorithm,
302
+ motebit_id: r.motebit_id,
303
+ device_id: r.device_id,
304
+ identity_public_key: r.identity_public_key,
305
+ se_public_key: r.se_public_key,
306
+ attested_at: r.attested_at,
307
+ },
308
+ };
309
+ }
310
+ // ── Test helper — mint a valid SE receipt from an in-process P-256 key ─
311
+ /**
312
+ * Test-only helper — encode a canonical body + signature into the
313
+ * receipt format. Tests that have a P-256 private key (via
314
+ * `@noble/curves/p256`) can call `signBytes` themselves, then hand the
315
+ * resulting body + signature to this helper to produce a well-formed
316
+ * receipt that `verifyHardwareAttestationClaim` will accept. Production
317
+ * callers MUST mint receipts via the Rust Secure Enclave bridge —
318
+ * never through this function.
319
+ */
320
+ export function encodeSecureEnclaveReceiptForTest(bodyBytes, sigBytes) {
321
+ return `${toBase64Url(bodyBytes)}.${toBase64Url(sigBytes)}`;
322
+ }
323
+ /**
324
+ * Test-only helper — build a canonical body JSON's bytes. Use with
325
+ * `encodeSecureEnclaveReceiptForTest` to produce a full receipt for
326
+ * verification tests. Canonicalization matches what production would
327
+ * emit.
328
+ */
329
+ export function canonicalSecureEnclaveBodyForTest(body) {
330
+ const full = {
331
+ version: "1",
332
+ algorithm: "ecdsa-p256-sha256",
333
+ ...body,
334
+ };
335
+ return new TextEncoder().encode(canonicalJson(full));
336
+ }
337
+ function toBase64Url(bytes) {
338
+ // Match the conventions in signing.ts — base64url, no padding.
339
+ let binary = "";
340
+ for (let i = 0; i < bytes.length; i++) {
341
+ binary += String.fromCharCode(bytes[i]);
342
+ }
343
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
344
+ }
345
+ //# sourceMappingURL=hardware-attestation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hardware-attestation.js","sourceRoot":"","sources":["../src/hardware-attestation.ts"],"names":[],"mappings":"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"}