@revibase/ctap2-js 0.1.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.
Files changed (69) hide show
  1. package/README.md +65 -0
  2. package/lib/apdu.d.ts +56 -0
  3. package/lib/apdu.d.ts.map +1 -0
  4. package/lib/apdu.js +225 -0
  5. package/lib/buildRequests.d.ts +11 -0
  6. package/lib/buildRequests.d.ts.map +1 -0
  7. package/lib/buildRequests.js +24 -0
  8. package/lib/constants.d.ts +11 -0
  9. package/lib/constants.d.ts.map +1 -0
  10. package/lib/constants.js +15 -0
  11. package/lib/ctap2/cborUtils.d.ts +14 -0
  12. package/lib/ctap2/cborUtils.d.ts.map +1 -0
  13. package/lib/ctap2/cborUtils.js +141 -0
  14. package/lib/ctap2/getAssertion.d.ts +32 -0
  15. package/lib/ctap2/getAssertion.d.ts.map +1 -0
  16. package/lib/ctap2/getAssertion.js +104 -0
  17. package/lib/ctap2/makeCredential.d.ts +37 -0
  18. package/lib/ctap2/makeCredential.d.ts.map +1 -0
  19. package/lib/ctap2/makeCredential.js +118 -0
  20. package/lib/errors.d.ts +7 -0
  21. package/lib/errors.d.ts.map +1 -0
  22. package/lib/errors.js +13 -0
  23. package/lib/helpers/authDataExtensions.d.ts +9 -0
  24. package/lib/helpers/authDataExtensions.d.ts.map +1 -0
  25. package/lib/helpers/authDataExtensions.js +192 -0
  26. package/lib/helpers/base64url.d.ts +3 -0
  27. package/lib/helpers/base64url.d.ts.map +1 -0
  28. package/lib/helpers/base64url.js +29 -0
  29. package/lib/helpers/clientData.d.ts +9 -0
  30. package/lib/helpers/clientData.d.ts.map +1 -0
  31. package/lib/helpers/clientData.js +18 -0
  32. package/lib/helpers/fromWebAuthnJson.d.ts +6 -0
  33. package/lib/helpers/fromWebAuthnJson.d.ts.map +1 -0
  34. package/lib/helpers/fromWebAuthnJson.js +125 -0
  35. package/lib/helpers/webauthnResponses.d.ts +21 -0
  36. package/lib/helpers/webauthnResponses.d.ts.map +1 -0
  37. package/lib/helpers/webauthnResponses.js +51 -0
  38. package/lib/helpers/webauthnTypes.d.ts +6 -0
  39. package/lib/helpers/webauthnTypes.d.ts.map +1 -0
  40. package/lib/helpers/webauthnTypes.js +2 -0
  41. package/lib/index.d.ts +4 -0
  42. package/lib/index.d.ts.map +1 -0
  43. package/lib/index.js +8 -0
  44. package/lib/parseResponses.d.ts +19 -0
  45. package/lib/parseResponses.d.ts.map +1 -0
  46. package/lib/parseResponses.js +37 -0
  47. package/lib/publicApi.d.ts +15 -0
  48. package/lib/publicApi.d.ts.map +1 -0
  49. package/lib/publicApi.js +83 -0
  50. package/lib/types.d.ts +10 -0
  51. package/lib/types.d.ts.map +1 -0
  52. package/lib/types.js +2 -0
  53. package/package.json +35 -0
  54. package/src/apdu.ts +285 -0
  55. package/src/constants.ts +16 -0
  56. package/src/ctap2/cborUtils.ts +155 -0
  57. package/src/ctap2/getAssertion.ts +154 -0
  58. package/src/ctap2/makeCredential.ts +173 -0
  59. package/src/errors.ts +11 -0
  60. package/src/helpers/authDataExtensions.ts +214 -0
  61. package/src/helpers/base64url.ts +24 -0
  62. package/src/helpers/clientData.ts +24 -0
  63. package/src/helpers/fromWebAuthnJson.ts +175 -0
  64. package/src/helpers/webauthnResponses.ts +113 -0
  65. package/src/index.ts +6 -0
  66. package/src/parseResponses.ts +67 -0
  67. package/src/publicApi.ts +152 -0
  68. package/src/types.ts +16 -0
  69. package/tsconfig.json +16 -0
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @revibase/ctap2-js
2
+
3
+ This library is for apps that want to talk to a **physical FIDO2/passkey authenticator over NFC** by sending **APDUs** directly.
4
+
5
+ **License:** MIT
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @revibase/ctap2-js
11
+ ```
12
+
13
+ Optional peer (for types): [`@simplewebauthn/browser`](https://simplewebauthn.dev/docs/packages/browser)
14
+
15
+ ```bash
16
+ pnpm add @simplewebauthn/browser
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ## React Native
22
+
23
+ Use a library that can send NFC commands like [`react-native-nfc-manager`](https://github.com/revtel/react-native-nfc-manager) with `NfcTech.IsoDep`.
24
+
25
+ ```ts
26
+ import { authenticateWithNfc } from "@revibase/ctap2-js";
27
+ import NfcManager, { NfcTech } from "react-native-nfc-manager";
28
+
29
+ async function withIsoDep<T>(
30
+ fn: (tx: (apdu: Uint8Array) => Promise<Uint8Array>) => Promise<T>,
31
+ ): Promise<T> {
32
+ const store = useNfcAuthPromptStore.getState();
33
+ await NfcManager.start();
34
+ if (Platform.OS === "android") {
35
+ store.setVisible(true);
36
+ store.setCancelSession(() => {
37
+ NfcManager.cancelTechnologyRequest().catch(() => {});
38
+ });
39
+ }
40
+ await NfcManager.requestTechnology(NfcTech.IsoDep, {
41
+ alertMessage: "Hold your security key near the phone",
42
+ });
43
+ const tx = async (apdu: Uint8Array) => {
44
+ return new Uint8Array(await NfcManager.isoDepHandler.transceive([...apdu]));
45
+ };
46
+
47
+ try {
48
+ return await fn(tx);
49
+ } finally {
50
+ if (Platform.OS === "android") {
51
+ store.setCancelSession(null);
52
+ }
53
+ await NfcManager.cancelTechnologyRequest().catch(() => {});
54
+ }
55
+ }
56
+
57
+ const request = {
58
+ origin: "your_origin",
59
+ crossOrigin: false,
60
+ rpId: "your_rp_id",
61
+ challenge: "random_challenge_in_base64_url",
62
+ };
63
+
64
+ await withIsoDep((tx) => authenticateWithNfc(request, tx));
65
+ ```
package/lib/apdu.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ interface ParsedIso7816Response {
2
+ data: Uint8Array;
3
+ sw1: number;
4
+ sw2: number;
5
+ statusWord: number;
6
+ }
7
+ interface NfcShortApduSegment {
8
+ bytes: Uint8Array;
9
+ chained: boolean;
10
+ }
11
+ export declare function encodeExtendedCase3e(cla: number, ins: number, p1: number, p2: number, data: Uint8Array): Uint8Array;
12
+ export declare function encodeShortApdu(cla: number, ins: number, p1: number, p2: number, payload: Uint8Array, options: {
13
+ chained: boolean;
14
+ }): Uint8Array;
15
+ /**
16
+ * Send CTAP CBOR command APDUs
17
+ * Extended APDU in memory is split into short chained frames;
18
+ * Intermediate responses must be SW 0x9000, the last response carries CTAP payload + SW.
19
+ */
20
+ export declare function transmitCborApduSegments(segments: NfcShortApduSegment[], transceive: (apdu: Uint8Array) => Promise<Uint8Array>): Promise<Uint8Array>;
21
+ /**
22
+ * Follow ISO 7816 "more data" responses for CBOR:
23
+ * while SW1 == 0x61, send GET RESPONSE (CLA 0x80, INS 0xc0, Le = SW2) and
24
+ * concatenate response bodies; final buffer is payload + SW1 + SW2.
25
+ */
26
+ export declare function collectCborApduResponse(firstResponse: Uint8Array, transceive: (apdu: Uint8Array) => Promise<Uint8Array>): Promise<Uint8Array>;
27
+ /**
28
+ * Full NFC CTAP2 command exchange for Java Card–style FIDO applets: send short
29
+ * chained command frames ({@link transmitCborApduSegments}), then resolve
30
+ * `61 xx` / GET RESPONSE until `90 00` (same as libfido2 `nfc_do_tx` +
31
+ * `rx_msg`). Use after SELECT.
32
+ */
33
+ export declare function transceiveNfcCtap2Command(segments: NfcShortApduSegment[], transceive: (apdu: Uint8Array) => Promise<Uint8Array>): Promise<Uint8Array>;
34
+ export declare function splitExtendedApduForNfcTransmit(extended: Uint8Array, chunkSize?: number): NfcShortApduSegment[];
35
+ export declare function buildSelectFidoApdu(): Uint8Array;
36
+ /** Short APDU(s) for SELECT — preferred over {@link buildSelectFidoApdu} on NFC. */
37
+ export declare function buildSelectFidoApduSegments(): NfcShortApduSegment[];
38
+ export declare function buildCborCommandApdu(cborPayload: Uint8Array): Uint8Array;
39
+ export declare function buildGetResponseApdu(le: number, cbor: boolean): Uint8Array;
40
+ export declare function parseIso7816Response(buf: Uint8Array): ParsedIso7816Response;
41
+ type ParseCtaphidCborBodyResult = {
42
+ ok: true;
43
+ cborMapBytes: Uint8Array;
44
+ } | {
45
+ ok: false;
46
+ layer: "iso7816";
47
+ statusWord: number;
48
+ } | {
49
+ ok: false;
50
+ layer: "ctap";
51
+ errorCode: number;
52
+ };
53
+ export declare function parseCtaphidCborFromApduResponse(apduResponseIncludingSw: Uint8Array): ParseCtaphidCborBodyResult;
54
+ export declare function unwrapCtaphidCborBody(parsed: ParseCtaphidCborBodyResult): Uint8Array;
55
+ export {};
56
+ //# sourceMappingURL=apdu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apdu.d.ts","sourceRoot":"","sources":["../src/apdu.ts"],"names":[],"mappings":"AA2BA,UAAU,qBAAqB;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,mBAAmB;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,UAAU,GACf,UAAU,CAiBZ;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAC5B,UAAU,CAiBZ;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,GACpD,OAAO,CAAC,UAAU,CAAC,CAwBrB;AAKD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,UAAU,EACzB,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,GACpD,OAAO,CAAC,UAAU,CAAC,CAyBrB;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,GACpD,OAAO,CAAC,UAAU,CAAC,CAKrB;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,UAAU,EACpB,SAAS,GAAE,MAA0B,GACpC,mBAAmB,EAAE,CAyBvB;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAEhD;AAED,oFAAoF;AACpF,wBAAgB,2BAA2B,IAAI,mBAAmB,EAAE,CAEnE;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,CAExE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,CAQ1E;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,UAAU,GAAG,qBAAqB,CAa3E;AAMD,KAAK,0BAA0B,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,UAAU,CAAA;CAAE,GACtC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,wBAAgB,gCAAgC,CAC9C,uBAAuB,EAAE,UAAU,GAClC,0BAA0B,CAa5B;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,0BAA0B,GACjC,UAAU,CAUZ"}
package/lib/apdu.js ADDED
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unwrapCtaphidCborBody = exports.parseCtaphidCborFromApduResponse = exports.parseIso7816Response = exports.buildGetResponseApdu = exports.buildCborCommandApdu = exports.buildSelectFidoApduSegments = exports.buildSelectFidoApdu = exports.splitExtendedApduForNfcTransmit = exports.transceiveNfcCtap2Command = exports.collectCborApduResponse = exports.transmitCborApduSegments = exports.encodeShortApdu = exports.encodeExtendedCase3e = void 0;
4
+ const constants_1 = require("./constants");
5
+ const errors_1 = require("./errors");
6
+ const CLA_CHAIN = 0x10;
7
+ /** CTAP2 error codes */
8
+ function formatCtapErrorMessage(code) {
9
+ const hex = `0x${code.toString(16).padStart(2, "0")}`;
10
+ const names = {
11
+ 0x01: "INVALID_COMMAND",
12
+ 0x02: "INVALID_PARAMETER",
13
+ 0x03: "INVALID_LENGTH",
14
+ 0x11: "CBOR_UNEXPECTED_TYPE",
15
+ 0x12: "INVALID_CBOR",
16
+ 0x14: "MISSING_PARAMETER",
17
+ 0x2e: "NO_CREDENTIALS",
18
+ 0x30: "NOT_ALLOWED",
19
+ };
20
+ const name = names[code];
21
+ return name ? `CTAP error ${hex} (${name})` : `CTAP error code ${hex}`;
22
+ }
23
+ function encodeExtendedCase3e(cla, ins, p1, p2, data) {
24
+ const n = data.length;
25
+ if (n > 65535) {
26
+ throw new errors_1.ApduError("APDU data field exceeds 65535 bytes");
27
+ }
28
+ const out = new Uint8Array(7 + n + 2);
29
+ out[0] = cla & 0xff;
30
+ out[1] = ins & 0xff;
31
+ out[2] = p1 & 0xff;
32
+ out[3] = p2 & 0xff;
33
+ out[4] = 0x00;
34
+ out[5] = (n >> 8) & 0xff;
35
+ out[6] = n & 0xff;
36
+ out.set(data, 7);
37
+ out[7 + n] = 0x00;
38
+ out[7 + n + 1] = 0x00;
39
+ return out;
40
+ }
41
+ exports.encodeExtendedCase3e = encodeExtendedCase3e;
42
+ function encodeShortApdu(cla, ins, p1, p2, payload, options) {
43
+ const lc = payload.length;
44
+ if (lc > 255) {
45
+ throw new errors_1.ApduError("short APDU payload must be ≤ 255 bytes");
46
+ }
47
+ const withLe = !options.chained;
48
+ const out = new Uint8Array(5 + lc + (withLe ? 1 : 0));
49
+ out[0] = (cla & 0xff) | (options.chained ? CLA_CHAIN : 0);
50
+ out[1] = ins & 0xff;
51
+ out[2] = p1 & 0xff;
52
+ out[3] = p2 & 0xff;
53
+ out[4] = lc & 0xff;
54
+ out.set(payload, 5);
55
+ if (withLe) {
56
+ out[5 + lc] = 0x00;
57
+ }
58
+ return out;
59
+ }
60
+ exports.encodeShortApdu = encodeShortApdu;
61
+ /**
62
+ * Send CTAP CBOR command APDUs
63
+ * Extended APDU in memory is split into short chained frames;
64
+ * Intermediate responses must be SW 0x9000, the last response carries CTAP payload + SW.
65
+ */
66
+ async function transmitCborApduSegments(segments, transceive) {
67
+ let last;
68
+ for (const seg of segments) {
69
+ const resp = await transceive(seg.bytes);
70
+ if (resp.length < 2) {
71
+ throw new errors_1.ApduError(`APDU response too short for status word (${resp.length} byte(s))`);
72
+ }
73
+ const sw = (resp[resp.length - 2] << 8) | resp[resp.length - 1];
74
+ if (seg.chained) {
75
+ if (sw !== constants_1.SW_NO_ERROR) {
76
+ throw new errors_1.ApduError(`ISO7816 status 0x${sw.toString(16).padStart(4, "0")}`);
77
+ }
78
+ }
79
+ else {
80
+ last = resp;
81
+ }
82
+ }
83
+ if (!last) {
84
+ throw new errors_1.ApduError("no final APDU response");
85
+ }
86
+ return last;
87
+ }
88
+ exports.transmitCborApduSegments = transmitCborApduSegments;
89
+ /** Max GET RESPONSE iterations (matches practical NFC response sizes). */
90
+ const GET_RESPONSE_MAX_CHAIN = 256;
91
+ /**
92
+ * Follow ISO 7816 "more data" responses for CBOR:
93
+ * while SW1 == 0x61, send GET RESPONSE (CLA 0x80, INS 0xc0, Le = SW2) and
94
+ * concatenate response bodies; final buffer is payload + SW1 + SW2.
95
+ */
96
+ async function collectCborApduResponse(firstResponse, transceive) {
97
+ const chunks = [];
98
+ let cur = firstResponse;
99
+ for (let i = 0; i < GET_RESPONSE_MAX_CHAIN; i++) {
100
+ if (cur.length < 2) {
101
+ throw new errors_1.ApduError("APDU response too short");
102
+ }
103
+ const sw1 = cur[cur.length - 2];
104
+ const sw2 = cur[cur.length - 1];
105
+ chunks.push(cur.slice(0, -2));
106
+ if (sw1 !== constants_1.SW1_MORE_DATA) {
107
+ const totalLen = chunks.reduce((a, c) => a + c.length, 0) + 2;
108
+ const out = new Uint8Array(totalLen);
109
+ let o = 0;
110
+ for (const c of chunks) {
111
+ out.set(c, o);
112
+ o += c.length;
113
+ }
114
+ out[o] = sw1;
115
+ out[o + 1] = sw2;
116
+ return out;
117
+ }
118
+ cur = await transceive(buildGetResponseApdu(sw2, true));
119
+ }
120
+ throw new errors_1.ApduError("GET RESPONSE chain exceeded limit");
121
+ }
122
+ exports.collectCborApduResponse = collectCborApduResponse;
123
+ /**
124
+ * Full NFC CTAP2 command exchange for Java Card–style FIDO applets: send short
125
+ * chained command frames ({@link transmitCborApduSegments}), then resolve
126
+ * `61 xx` / GET RESPONSE until `90 00` (same as libfido2 `nfc_do_tx` +
127
+ * `rx_msg`). Use after SELECT.
128
+ */
129
+ async function transceiveNfcCtap2Command(segments, transceive) {
130
+ return collectCborApduResponse(await transmitCborApduSegments(segments, transceive), transceive);
131
+ }
132
+ exports.transceiveNfcCtap2Command = transceiveNfcCtap2Command;
133
+ function splitExtendedApduForNfcTransmit(extended, chunkSize = constants_1.NFC_TX_CHUNK_SIZE) {
134
+ if (extended.length < 9) {
135
+ throw new errors_1.ApduError("extended APDU too short");
136
+ }
137
+ const cla = extended[0];
138
+ const ins = extended[1];
139
+ const p1 = extended[2];
140
+ const p2 = extended[3];
141
+ const data = extended.slice(7, extended.length - 2);
142
+ const segments = [];
143
+ let off = 0;
144
+ while (data.length - off > chunkSize) {
145
+ const chunk = data.subarray(off, off + chunkSize);
146
+ segments.push({
147
+ bytes: encodeShortApdu(cla, ins, p1, p2, chunk, { chained: true }),
148
+ chained: true,
149
+ });
150
+ off += chunkSize;
151
+ }
152
+ const rest = data.subarray(off);
153
+ segments.push({
154
+ bytes: encodeShortApdu(cla, ins, p1, p2, rest, { chained: false }),
155
+ chained: false,
156
+ });
157
+ return segments;
158
+ }
159
+ exports.splitExtendedApduForNfcTransmit = splitExtendedApduForNfcTransmit;
160
+ function buildSelectFidoApdu() {
161
+ return encodeExtendedCase3e(0x00, 0xa4, 0x04, 0x00, constants_1.FIDO_AID);
162
+ }
163
+ exports.buildSelectFidoApdu = buildSelectFidoApdu;
164
+ /** Short APDU(s) for SELECT — preferred over {@link buildSelectFidoApdu} on NFC. */
165
+ function buildSelectFidoApduSegments() {
166
+ return splitExtendedApduForNfcTransmit(buildSelectFidoApdu());
167
+ }
168
+ exports.buildSelectFidoApduSegments = buildSelectFidoApduSegments;
169
+ function buildCborCommandApdu(cborPayload) {
170
+ return encodeExtendedCase3e(0x80, 0x10, 0x00, 0x00, cborPayload);
171
+ }
172
+ exports.buildCborCommandApdu = buildCborCommandApdu;
173
+ function buildGetResponseApdu(le, cbor) {
174
+ const apdu = new Uint8Array(5);
175
+ apdu[0] = cbor ? 0x80 : 0x00;
176
+ apdu[1] = 0xc0;
177
+ apdu[2] = 0x00;
178
+ apdu[3] = 0x00;
179
+ apdu[4] = le & 0xff;
180
+ return apdu;
181
+ }
182
+ exports.buildGetResponseApdu = buildGetResponseApdu;
183
+ function parseIso7816Response(buf) {
184
+ if (buf.length < 2) {
185
+ throw new errors_1.ApduError("response must include SW1 and SW2");
186
+ }
187
+ const sw1 = buf[buf.length - 2];
188
+ const sw2 = buf[buf.length - 1];
189
+ const statusWord = (sw1 << 8) | sw2;
190
+ return {
191
+ data: buf.slice(0, -2),
192
+ sw1,
193
+ sw2,
194
+ statusWord,
195
+ };
196
+ }
197
+ exports.parseIso7816Response = parseIso7816Response;
198
+ function isSuccessStatus(statusWord) {
199
+ return statusWord === constants_1.SW_NO_ERROR;
200
+ }
201
+ function parseCtaphidCborFromApduResponse(apduResponseIncludingSw) {
202
+ const { data, statusWord } = parseIso7816Response(apduResponseIncludingSw);
203
+ if (!isSuccessStatus(statusWord)) {
204
+ return { ok: false, layer: "iso7816", statusWord };
205
+ }
206
+ if (data.length < 1) {
207
+ throw new errors_1.ApduError("empty CTAP payload after ISO7816 success");
208
+ }
209
+ const status = data[0];
210
+ if (status !== 0) {
211
+ return { ok: false, layer: "ctap", errorCode: status };
212
+ }
213
+ return { ok: true, cborMapBytes: data.subarray(1) };
214
+ }
215
+ exports.parseCtaphidCborFromApduResponse = parseCtaphidCborFromApduResponse;
216
+ function unwrapCtaphidCborBody(parsed) {
217
+ if (!parsed.ok) {
218
+ if (parsed.layer === "iso7816") {
219
+ throw new errors_1.ApduError(`ISO7816 status 0x${parsed.statusWord.toString(16).padStart(4, "0")}`);
220
+ }
221
+ throw new errors_1.ApduError(formatCtapErrorMessage(parsed.errorCode));
222
+ }
223
+ return parsed.cborMapBytes;
224
+ }
225
+ exports.unwrapCtaphidCborBody = unwrapCtaphidCborBody;
@@ -0,0 +1,11 @@
1
+ import type { NfcShortApduSegment } from "./apdu";
2
+ import type { PublicKeyCredentialCreationOptionsJSONWithNfc, PublicKeyCredentialRequestOptionsJSONWithNfc } from "./types";
3
+ /** @internal Used by tests and advanced integrations. */
4
+ export declare function startAuthenticationToApdu(args: PublicKeyCredentialRequestOptionsJSONWithNfc): Uint8Array;
5
+ /** @internal */
6
+ export declare function startAuthenticationToApduSegments(args: PublicKeyCredentialRequestOptionsJSONWithNfc): NfcShortApduSegment[];
7
+ /** @internal */
8
+ export declare function startRegistrationToApdu(args: PublicKeyCredentialCreationOptionsJSONWithNfc): Uint8Array;
9
+ /** @internal */
10
+ export declare function startRegistrationToApduSegments(args: PublicKeyCredentialCreationOptionsJSONWithNfc): NfcShortApduSegment[];
11
+ //# sourceMappingURL=buildRequests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildRequests.d.ts","sourceRoot":"","sources":["../src/buildRequests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAOlD,OAAO,KAAK,EACX,6CAA6C,EAC7C,4CAA4C,EAC5C,MAAM,SAAS,CAAC;AAEjB,yDAAyD;AACzD,wBAAgB,yBAAyB,CACxC,IAAI,EAAE,4CAA4C,GAChD,UAAU,CAIZ;AAED,gBAAgB;AAChB,wBAAgB,iCAAiC,CAChD,IAAI,EAAE,4CAA4C,GAChD,mBAAmB,EAAE,CAIvB;AAED,gBAAgB;AAChB,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,6CAA6C,GACjD,UAAU,CAIZ;AAED,gBAAgB;AAChB,wBAAgB,+BAA+B,CAC9C,IAAI,EAAE,6CAA6C,GACjD,mBAAmB,EAAE,CAIvB"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startRegistrationToApduSegments = exports.startRegistrationToApdu = exports.startAuthenticationToApduSegments = exports.startAuthenticationToApdu = void 0;
4
+ const fromWebAuthnJson_1 = require("./helpers/fromWebAuthnJson");
5
+ /** @internal Used by tests and advanced integrations. */
6
+ function startAuthenticationToApdu(args) {
7
+ return (0, fromWebAuthnJson_1.buildAuthenticatorGetAssertionApduFromPublicKeyCredentialRequestOptionsJSON)(args);
8
+ }
9
+ exports.startAuthenticationToApdu = startAuthenticationToApdu;
10
+ /** @internal */
11
+ function startAuthenticationToApduSegments(args) {
12
+ return (0, fromWebAuthnJson_1.buildAuthenticatorGetAssertionApduSegmentsFromPublicKeyCredentialRequestOptionsJSON)(args);
13
+ }
14
+ exports.startAuthenticationToApduSegments = startAuthenticationToApduSegments;
15
+ /** @internal */
16
+ function startRegistrationToApdu(args) {
17
+ return (0, fromWebAuthnJson_1.buildAuthenticatorMakeCredentialApduFromPublicKeyCredentialCreationOptionsJSON)(args);
18
+ }
19
+ exports.startRegistrationToApdu = startRegistrationToApdu;
20
+ /** @internal */
21
+ function startRegistrationToApduSegments(args) {
22
+ return (0, fromWebAuthnJson_1.buildAuthenticatorMakeCredentialApduSegmentsFromPublicKeyCredentialCreationOptionsJSON)(args);
23
+ }
24
+ exports.startRegistrationToApduSegments = startRegistrationToApduSegments;
@@ -0,0 +1,11 @@
1
+ export declare const NFC_TX_CHUNK_SIZE = 240;
2
+ export declare const FIDO_AID: Uint8Array;
3
+ export declare const CTAP_CMD_MSG = 3;
4
+ export declare const CTAP_CMD_INIT = 6;
5
+ export declare const CTAP_CMD_CBOR = 16;
6
+ export declare const CTAP_CBOR_MAKE_CRED = 1;
7
+ export declare const CTAP_CBOR_ASSERT = 2;
8
+ export declare const CTAP_CBOR_GETINFO = 4;
9
+ export declare const SW_NO_ERROR = 36864;
10
+ export declare const SW1_MORE_DATA = 97;
11
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,eAAO,MAAM,QAAQ,YAEnB,CAAC;AAEH,eAAO,MAAM,YAAY,IAAO,CAAC;AACjC,eAAO,MAAM,aAAa,IAAO,CAAC;AAClC,eAAO,MAAM,aAAa,KAAO,CAAC;AAElC,eAAO,MAAM,mBAAmB,IAAO,CAAC;AACxC,eAAO,MAAM,gBAAgB,IAAO,CAAC;AACrC,eAAO,MAAM,iBAAiB,IAAO,CAAC;AAEtC,eAAO,MAAM,WAAW,QAAS,CAAC;AAClC,eAAO,MAAM,aAAa,KAAO,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SW1_MORE_DATA = exports.SW_NO_ERROR = exports.CTAP_CBOR_GETINFO = exports.CTAP_CBOR_ASSERT = exports.CTAP_CBOR_MAKE_CRED = exports.CTAP_CMD_CBOR = exports.CTAP_CMD_INIT = exports.CTAP_CMD_MSG = exports.FIDO_AID = exports.NFC_TX_CHUNK_SIZE = void 0;
4
+ exports.NFC_TX_CHUNK_SIZE = 240;
5
+ exports.FIDO_AID = Uint8Array.from([
6
+ 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01,
7
+ ]);
8
+ exports.CTAP_CMD_MSG = 0x03;
9
+ exports.CTAP_CMD_INIT = 0x06;
10
+ exports.CTAP_CMD_CBOR = 0x10;
11
+ exports.CTAP_CBOR_MAKE_CRED = 0x01;
12
+ exports.CTAP_CBOR_ASSERT = 0x02;
13
+ exports.CTAP_CBOR_GETINFO = 0x04;
14
+ exports.SW_NO_ERROR = 0x9000;
15
+ exports.SW1_MORE_DATA = 0x61;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CTAP2 canonical CBOR key order for text-string map keys (FIDO CTAP §8 Message Encoding):
3
+ * shorter UTF-8 length sorts first; same length → lower byte-wise lexical order.
4
+ */
5
+ export declare function compareCtapTextMapKeys(a: string, b: string): number;
6
+ /**
7
+ * Recursively reorders every CBOR-serialized map to CTAP2 canonical form (sorted keys).
8
+ * Integer-key maps (command parameters, COSE, etc.) are sorted numerically.
9
+ */
10
+ export declare function canonicalizeCtapValue(value: unknown): unknown;
11
+ export declare function encodeCtapCbor(value: unknown): Uint8Array;
12
+ export declare function bytesView(v: unknown): Uint8Array | undefined;
13
+ export declare function cborMapGet(map: Map<number, unknown> | object, key: number): unknown;
14
+ //# sourceMappingURL=cborUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cborUtils.d.ts","sourceRoot":"","sources":["../../src/ctap2/cborUtils.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAYnE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAoE7D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAUzD;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAa5D;AAED,wBAAgB,UAAU,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAClC,GAAG,EAAE,MAAM,GACT,OAAO,CAeT"}
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cborMapGet = exports.bytesView = exports.encodeCtapCbor = exports.canonicalizeCtapValue = exports.compareCtapTextMapKeys = void 0;
4
+ const cbor_x_1 = require("cbor-x");
5
+ /**
6
+ * cbor-x encodes JS `Map` with CBOR tag 259 by default. CTAP2/FIDO2 expects a
7
+ * plain CBOR map (no tag), or authenticators return FIDO_ERR_CBOR_UNEXPECTED_TYPE (0x11).
8
+ */
9
+ const ctapCborEncoder = new cbor_x_1.Encoder({
10
+ useTag259ForMaps: false,
11
+ useRecords: false,
12
+ mapsAsObjects: true,
13
+ structuredClone: false,
14
+ tagUint8Array: false,
15
+ bundleStrings: false,
16
+ });
17
+ const textEncoder = new TextEncoder();
18
+ /**
19
+ * CTAP2 canonical CBOR key order for text-string map keys (FIDO CTAP §8 Message Encoding):
20
+ * shorter UTF-8 length sorts first; same length → lower byte-wise lexical order.
21
+ */
22
+ function compareCtapTextMapKeys(a, b) {
23
+ const ab = textEncoder.encode(a);
24
+ const bb = textEncoder.encode(b);
25
+ if (ab.length !== bb.length) {
26
+ return ab.length - bb.length;
27
+ }
28
+ for (let i = 0; i < ab.length; i++) {
29
+ if (ab[i] !== bb[i]) {
30
+ return ab[i] - bb[i];
31
+ }
32
+ }
33
+ return 0;
34
+ }
35
+ exports.compareCtapTextMapKeys = compareCtapTextMapKeys;
36
+ /**
37
+ * Recursively reorders every CBOR-serialized map to CTAP2 canonical form (sorted keys).
38
+ * Integer-key maps (command parameters, COSE, etc.) are sorted numerically.
39
+ */
40
+ function canonicalizeCtapValue(value) {
41
+ if (value === null || value === undefined) {
42
+ return value;
43
+ }
44
+ const t = typeof value;
45
+ if (t === "boolean" ||
46
+ t === "number" ||
47
+ t === "string" ||
48
+ t === "bigint") {
49
+ return value;
50
+ }
51
+ if (value instanceof Uint8Array) {
52
+ return value;
53
+ }
54
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
55
+ const view = value;
56
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return value.map((v) => canonicalizeCtapValue(v));
60
+ }
61
+ if (value instanceof Map) {
62
+ const keys = [...value.keys()];
63
+ if (keys.length === 0) {
64
+ return new Map();
65
+ }
66
+ const allNum = keys.every((k) => typeof k === "number");
67
+ if (allNum) {
68
+ const sorted = keys
69
+ .slice()
70
+ .sort((a, b) => a - b);
71
+ const m = new Map();
72
+ for (const k of sorted) {
73
+ m.set(k, canonicalizeCtapValue(value.get(k)));
74
+ }
75
+ return m;
76
+ }
77
+ const allStr = keys.every((k) => typeof k === "string");
78
+ if (allStr) {
79
+ const sorted = keys
80
+ .slice()
81
+ .sort(compareCtapTextMapKeys);
82
+ const o = {};
83
+ for (const k of sorted) {
84
+ o[k] = canonicalizeCtapValue(value.get(k));
85
+ }
86
+ return o;
87
+ }
88
+ throw new Error("CTAP CBOR: map keys must be all numbers or all strings for canonical encoding");
89
+ }
90
+ if (t === "object") {
91
+ const o = value;
92
+ const keys = Object.keys(o).sort(compareCtapTextMapKeys);
93
+ const out = {};
94
+ for (const k of keys) {
95
+ out[k] = canonicalizeCtapValue(o[k]);
96
+ }
97
+ return out;
98
+ }
99
+ return value;
100
+ }
101
+ exports.canonicalizeCtapValue = canonicalizeCtapValue;
102
+ function encodeCtapCbor(value) {
103
+ const encoded = ctapCborEncoder.encode(canonicalizeCtapValue(value));
104
+ if (encoded instanceof Uint8Array) {
105
+ return new Uint8Array(encoded);
106
+ }
107
+ if (ArrayBuffer.isView(encoded)) {
108
+ const v = encoded;
109
+ return new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
110
+ }
111
+ return Uint8Array.from(encoded);
112
+ }
113
+ exports.encodeCtapCbor = encodeCtapCbor;
114
+ function bytesView(v) {
115
+ if (v instanceof Uint8Array) {
116
+ return new Uint8Array(v);
117
+ }
118
+ if (ArrayBuffer.isView(v) && !(v instanceof DataView)) {
119
+ const view = v;
120
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
121
+ }
122
+ return undefined;
123
+ }
124
+ exports.bytesView = bytesView;
125
+ function cborMapGet(map, key) {
126
+ if (map instanceof Map) {
127
+ return map.get(key);
128
+ }
129
+ if (map !== null && typeof map === "object") {
130
+ const o = map;
131
+ if (key in o) {
132
+ return o[key];
133
+ }
134
+ const s = String(key);
135
+ if (s in o) {
136
+ return o[s];
137
+ }
138
+ }
139
+ return undefined;
140
+ }
141
+ exports.cborMapGet = cborMapGet;
@@ -0,0 +1,32 @@
1
+ export interface AuthenticatorGetAssertionRequest {
2
+ rpId: string;
3
+ clientDataHash: Uint8Array;
4
+ allowCredentials?: Array<{
5
+ id: Uint8Array;
6
+ type?: string;
7
+ }>;
8
+ extensions?: Record<string, unknown>;
9
+ options?: {
10
+ up?: boolean;
11
+ uv?: boolean;
12
+ };
13
+ }
14
+ export interface AuthenticatorGetAssertionResponse {
15
+ credential?: {
16
+ id: Uint8Array;
17
+ type?: string;
18
+ };
19
+ authData: Uint8Array;
20
+ signature: Uint8Array;
21
+ user?: {
22
+ id: Uint8Array;
23
+ name?: string;
24
+ displayName?: string;
25
+ icon?: string;
26
+ };
27
+ numberOfCredentials?: number;
28
+ largeBlobKey?: Uint8Array;
29
+ }
30
+ export declare function encodeAuthenticatorGetAssertionRequest(req: AuthenticatorGetAssertionRequest): Uint8Array;
31
+ export declare function parseAuthenticatorGetAssertionResponse(apduResponseIncludingSw: Uint8Array): AuthenticatorGetAssertionResponse;
32
+ //# sourceMappingURL=getAssertion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAssertion.d.ts","sourceRoot":"","sources":["../../src/ctap2/getAssertion.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,gCAAgC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,UAAU,CAAC;IAC3B,gBAAgB,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,UAAU,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACzC;AAED,MAAM,WAAW,iCAAiC;IACjD,UAAU,CAAC,EAAE;QACZ,EAAE,EAAE,UAAU,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,QAAQ,EAAE,UAAU,CAAC;IACrB,SAAS,EAAE,UAAU,CAAC;IACtB,IAAI,CAAC,EAAE;QACN,EAAE,EAAE,UAAU,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,UAAU,CAAC;CAC1B;AAkGD,wBAAgB,sCAAsC,CACrD,GAAG,EAAE,gCAAgC,GACnC,UAAU,CAWZ;AAED,wBAAgB,sCAAsC,CACrD,uBAAuB,EAAE,UAAU,GACjC,iCAAiC,CAMnC"}