@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
@@ -0,0 +1,113 @@
1
+ import { utf8ToBytes } from "@noble/hashes/utils.js";
2
+ import {
3
+ AuthenticationResponseJSON,
4
+ RegistrationResponseJSON,
5
+ } from "@simplewebauthn/browser";
6
+ import type { AuthenticatorGetAssertionResponse } from "../ctap2/getAssertion";
7
+ import { ApduError } from "../errors";
8
+ import {
9
+ buildAttestationObjectBytes,
10
+ extractCredentialIdFromAuthData,
11
+ type AuthenticatorMakeCredentialResponse,
12
+ } from "../ctap2/makeCredential";
13
+ import { parseAuthenticatorDataExtensions } from "./authDataExtensions";
14
+ import { bufferToBase64URLString } from "./base64url";
15
+
16
+ type ToWebAuthnResponseJSONInput =
17
+ | {
18
+ kind: "authentication";
19
+ assertion: AuthenticatorGetAssertionResponse;
20
+ credentialId?: Uint8Array;
21
+ clientDataJSON: string;
22
+ }
23
+ | {
24
+ kind: "registration";
25
+ credential: AuthenticatorMakeCredentialResponse;
26
+ clientDataJSON: string;
27
+ };
28
+
29
+ export function toWebAuthnResponseJSON(
30
+ input: Extract<ToWebAuthnResponseJSONInput, { kind: "authentication" }>,
31
+ ): AuthenticationResponseJSON;
32
+ export function toWebAuthnResponseJSON(
33
+ input: Extract<ToWebAuthnResponseJSONInput, { kind: "registration" }>,
34
+ ): RegistrationResponseJSON;
35
+ export function toWebAuthnResponseJSON(
36
+ input: ToWebAuthnResponseJSONInput,
37
+ ): AuthenticationResponseJSON | RegistrationResponseJSON {
38
+ if (input.kind === "authentication") {
39
+ const {
40
+ assertion,
41
+ credentialId: credentialIdArg,
42
+ clientDataJSON,
43
+ } = input;
44
+
45
+ const clientExtensionResults = parseAuthenticatorDataExtensions(
46
+ assertion.authData,
47
+ ) as AuthenticationExtensionsClientOutputs;
48
+
49
+ const credBytes =
50
+ assertion.credential?.id ??
51
+ credentialIdArg ??
52
+ (() => {
53
+ throw new ApduError(
54
+ "Assertion response missing credential id; set credentialId (base64url) on the request object or use a token that returns it in CBOR",
55
+ );
56
+ })();
57
+
58
+ const idStr = bufferToBase64URLString(credBytes);
59
+
60
+ return {
61
+ id: idStr,
62
+ rawId: idStr,
63
+ type: "public-key",
64
+ clientExtensionResults,
65
+ response: {
66
+ clientDataJSON: bufferToBase64URLString(
67
+ utf8ToBytes(clientDataJSON),
68
+ ),
69
+ authenticatorData: bufferToBase64URLString(
70
+ assertion.authData,
71
+ ),
72
+ signature: bufferToBase64URLString(assertion.signature),
73
+ userHandle:
74
+ assertion.user?.id !== undefined
75
+ ? bufferToBase64URLString(
76
+ assertion.user.id,
77
+ )
78
+ : undefined,
79
+ },
80
+ };
81
+ }
82
+
83
+ const { credential, clientDataJSON } = input;
84
+ const ao = buildAttestationObjectBytes(
85
+ credential.fmt,
86
+ credential.authData,
87
+ credential.attStmt,
88
+ );
89
+ const credentialId = extractCredentialIdFromAuthData(
90
+ credential.authData,
91
+ );
92
+ const idStr = bufferToBase64URLString(credentialId);
93
+
94
+ const clientExtensionResults = parseAuthenticatorDataExtensions(
95
+ credential.authData,
96
+ ) as AuthenticationExtensionsClientOutputs;
97
+
98
+ return {
99
+ id: idStr,
100
+ rawId: idStr,
101
+ type: "public-key",
102
+ clientExtensionResults,
103
+ response: {
104
+ clientDataJSON: bufferToBase64URLString(
105
+ utf8ToBytes(clientDataJSON),
106
+ ),
107
+ attestationObject: bufferToBase64URLString(ao),
108
+ authenticatorData: bufferToBase64URLString(
109
+ credential.authData,
110
+ ),
111
+ },
112
+ };
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { ApduError } from "./errors";
2
+ export { authenticateWithNfc, registerWithNfc } from "./publicApi";
3
+ export type {
4
+ PublicKeyCredentialCreationOptionsJSONWithNfc,
5
+ PublicKeyCredentialRequestOptionsJSONWithNfc,
6
+ } from "./types";
@@ -0,0 +1,67 @@
1
+ import {
2
+ AuthenticationResponseJSON,
3
+ RegistrationResponseJSON,
4
+ } from "@simplewebauthn/browser";
5
+ import { parseAuthenticatorGetAssertionResponse } from "./ctap2/getAssertion";
6
+ import { parseAuthenticatorMakeCredentialResponse } from "./ctap2/makeCredential";
7
+ import { buildCollectedClientDataJSON } from "./helpers/clientData";
8
+ import { toWebAuthnResponseJSON } from "./helpers/webauthnResponses";
9
+ import {
10
+ PublicKeyCredentialCreationOptionsJSONWithNfc,
11
+ PublicKeyCredentialRequestOptionsJSONWithNfc,
12
+ } from "./types";
13
+
14
+ type ParseApduToWebAuthnInput =
15
+ | {
16
+ kind: "authentication";
17
+ apduResponse: Uint8Array;
18
+ request: PublicKeyCredentialRequestOptionsJSONWithNfc;
19
+ }
20
+ | {
21
+ kind: "registration";
22
+ apduResponse: Uint8Array;
23
+ request: PublicKeyCredentialCreationOptionsJSONWithNfc;
24
+ };
25
+
26
+ export function parseApduToWebAuthnResponse(
27
+ input: Extract<ParseApduToWebAuthnInput, { kind: "authentication" }>,
28
+ ): AuthenticationResponseJSON;
29
+ export function parseApduToWebAuthnResponse(
30
+ input: Extract<ParseApduToWebAuthnInput, { kind: "registration" }>,
31
+ ): RegistrationResponseJSON;
32
+ export function parseApduToWebAuthnResponse(
33
+ input: ParseApduToWebAuthnInput,
34
+ ): AuthenticationResponseJSON | RegistrationResponseJSON {
35
+ if (input.kind === "authentication") {
36
+ const clientDataJSON = buildCollectedClientDataJSON("webauthn.get", {
37
+ challenge: input.request.challenge,
38
+ origin: input.request.origin,
39
+ crossOrigin: input.request.crossOrigin ?? false,
40
+ topOrigin: input.request.topOrigin,
41
+ });
42
+ const assertion = parseAuthenticatorGetAssertionResponse(
43
+ input.apduResponse,
44
+ );
45
+ return toWebAuthnResponseJSON({
46
+ kind: "authentication",
47
+ assertion,
48
+ credentialId: assertion.credential?.id,
49
+ clientDataJSON,
50
+ });
51
+ }
52
+
53
+ const clientDataJSON = buildCollectedClientDataJSON("webauthn.create", {
54
+ challenge: input.request.challenge,
55
+ origin: input.request.origin,
56
+ crossOrigin: input.request.crossOrigin ?? false,
57
+ topOrigin: input.request.topOrigin,
58
+ });
59
+ const credential = parseAuthenticatorMakeCredentialResponse(
60
+ input.apduResponse,
61
+ );
62
+ return toWebAuthnResponseJSON({
63
+ kind: "registration",
64
+ credential,
65
+ clientDataJSON,
66
+ });
67
+ }
@@ -0,0 +1,152 @@
1
+ import {
2
+ AuthenticationResponseJSON,
3
+ RegistrationResponseJSON,
4
+ } from "@simplewebauthn/browser";
5
+ import {
6
+ buildCborCommandApdu,
7
+ buildSelectFidoApduSegments,
8
+ splitExtendedApduForNfcTransmit,
9
+ transceiveNfcCtap2Command,
10
+ } from "./apdu";
11
+
12
+ import { encodeAuthenticatorGetAssertionRequest } from "./ctap2/getAssertion";
13
+ import { encodeAuthenticatorMakeCredentialRequest } from "./ctap2/makeCredential";
14
+ import {
15
+ authenticatorGetAssertionRequestFromPublicKeyCredentialRequestOptionsJSON,
16
+ authenticatorMakeCredentialRequestFromPublicKeyCredentialCreationOptionsJSON,
17
+ } from "./helpers/fromWebAuthnJson";
18
+ import { parseApduToWebAuthnResponse } from "./parseResponses";
19
+ import { ApduError } from "./errors";
20
+ import {
21
+ PublicKeyCredentialCreationOptionsJSONWithNfc,
22
+ PublicKeyCredentialRequestOptionsJSONWithNfc,
23
+ } from "./types";
24
+
25
+ /**
26
+ * Wraps `transceive` so failures and short reads get a stable {@link ApduError}
27
+ * with API/phase in the message. Re-throws {@link ApduError} from lower layers unchanged.
28
+ */
29
+ function nfcTransceiveFor(
30
+ transceive: (apdu: Uint8Array) => Promise<Uint8Array>,
31
+ apiLabel: string,
32
+ phase: string,
33
+ ): (apdu: Uint8Array) => Promise<Uint8Array> {
34
+ const label = `${apiLabel} (${phase})`;
35
+ return async (apdu) => {
36
+ try {
37
+ const resp = await transceive(apdu);
38
+ if (resp.length < 2) {
39
+ throw new ApduError(
40
+ `${label}: response must include SW1/SW2 (got ${resp.length} byte(s))`,
41
+ );
42
+ }
43
+ return resp;
44
+ } catch (e) {
45
+ if (e instanceof ApduError) {
46
+ throw e;
47
+ }
48
+ throw new ApduError(
49
+ `${label}: ${e instanceof Error ? e.message : String(e)}`,
50
+ { cause: e },
51
+ );
52
+ }
53
+ };
54
+ }
55
+
56
+ async function runAuthenticateWithNfc(
57
+ args: PublicKeyCredentialRequestOptionsJSONWithNfc,
58
+ transceive: (apdu: Uint8Array) => Promise<Uint8Array>,
59
+ ): Promise<AuthenticationResponseJSON> {
60
+ await transceiveNfcCtap2Command(
61
+ buildSelectFidoApduSegments(),
62
+ nfcTransceiveFor(transceive, "authenticateWithNfc", "SELECT FIDO"),
63
+ );
64
+ const apduResponse = await transceiveNfcCtap2Command(
65
+ splitExtendedApduForNfcTransmit(
66
+ buildCborCommandApdu(
67
+ encodeAuthenticatorGetAssertionRequest(
68
+ authenticatorGetAssertionRequestFromPublicKeyCredentialRequestOptionsJSON(
69
+ args,
70
+ ),
71
+ ),
72
+ ),
73
+ ),
74
+ nfcTransceiveFor(transceive, "authenticateWithNfc", "getAssertion"),
75
+ );
76
+ return parseApduToWebAuthnResponse({
77
+ kind: "authentication",
78
+ apduResponse,
79
+ request: args,
80
+ });
81
+ }
82
+
83
+ /**
84
+ * NFC against a Java Card FIDO2 applet: SELECT FIDO AID, then getAssertion with
85
+ * short APDU chaining + GET RESPONSE.
86
+ *
87
+ */
88
+ export async function authenticateWithNfc(
89
+ args: PublicKeyCredentialRequestOptionsJSONWithNfc,
90
+ transceive: (apdu: Uint8Array) => Promise<Uint8Array>,
91
+ ): Promise<AuthenticationResponseJSON> {
92
+ try {
93
+ return await runAuthenticateWithNfc(args, transceive);
94
+ } catch (e) {
95
+ if (e instanceof ApduError) {
96
+ throw e;
97
+ }
98
+ throw new ApduError(
99
+ `authenticateWithNfc: ${e instanceof Error ? e.message : String(e)}`,
100
+ { cause: e },
101
+ );
102
+ }
103
+ }
104
+
105
+ async function runRegisterWithNfc(
106
+ args: PublicKeyCredentialCreationOptionsJSONWithNfc,
107
+ transceive: (apdu: Uint8Array) => Promise<Uint8Array>,
108
+ ): Promise<RegistrationResponseJSON> {
109
+ await transceiveNfcCtap2Command(
110
+ buildSelectFidoApduSegments(),
111
+ nfcTransceiveFor(transceive, "registerWithNfc", "SELECT FIDO"),
112
+ );
113
+ const apduResponse = await transceiveNfcCtap2Command(
114
+ splitExtendedApduForNfcTransmit(
115
+ buildCborCommandApdu(
116
+ encodeAuthenticatorMakeCredentialRequest(
117
+ authenticatorMakeCredentialRequestFromPublicKeyCredentialCreationOptionsJSON(
118
+ args,
119
+ ),
120
+ ),
121
+ ),
122
+ ),
123
+ nfcTransceiveFor(transceive, "registerWithNfc", "makeCredential"),
124
+ );
125
+ return parseApduToWebAuthnResponse({
126
+ kind: "registration",
127
+ apduResponse,
128
+ request: args,
129
+ });
130
+ }
131
+
132
+ /**
133
+ * NFC against a Java Card FIDO2 applet: SELECT FIDO AID, then makeCredential with
134
+ * short APDU chaining + GET RESPONSE.
135
+ *
136
+ */
137
+ export async function registerWithNfc(
138
+ args: PublicKeyCredentialCreationOptionsJSONWithNfc,
139
+ transceive: (apdu: Uint8Array) => Promise<Uint8Array>,
140
+ ): Promise<RegistrationResponseJSON> {
141
+ try {
142
+ return await runRegisterWithNfc(args, transceive);
143
+ } catch (e) {
144
+ if (e instanceof ApduError) {
145
+ throw e;
146
+ }
147
+ throw new ApduError(
148
+ `registerWithNfc: ${e instanceof Error ? e.message : String(e)}`,
149
+ { cause: e },
150
+ );
151
+ }
152
+ }
package/src/types.ts ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ PublicKeyCredentialCreationOptionsJSON,
3
+ PublicKeyCredentialRequestOptionsJSON,
4
+ } from "@simplewebauthn/browser";
5
+
6
+ type NfcWebAuthnClientFields = {
7
+ origin: string;
8
+ crossOrigin?: boolean;
9
+ topOrigin?: string;
10
+ };
11
+
12
+ export type PublicKeyCredentialRequestOptionsJSONWithNfc =
13
+ PublicKeyCredentialRequestOptionsJSON & NfcWebAuthnClientFields;
14
+
15
+ export type PublicKeyCredentialCreationOptionsJSONWithNfc =
16
+ PublicKeyCredentialCreationOptionsJSON & NfcWebAuthnClientFields;
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020", "DOM"],
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "outDir": "lib",
9
+ "rootDir": "src",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "moduleResolution": "node"
14
+ },
15
+ "include": ["src/**/*"]
16
+ }