nid-webauthn-emulator 0.1.3

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/dist/spec/authenticator/authenticator-emulator.spec.d.ts +1 -0
  4. package/dist/spec/authenticator/authenticator-emulator.spec.js +18 -0
  5. package/dist/spec/authenticator/ctap-model.spec.d.ts +1 -0
  6. package/dist/spec/authenticator/ctap-model.spec.js +95 -0
  7. package/dist/spec/integration/integration.spec.d.ts +1 -0
  8. package/dist/spec/integration/integration.spec.js +32 -0
  9. package/dist/spec/integration/passkeys-api-client.d.ts +11 -0
  10. package/dist/spec/integration/passkeys-api-client.js +2 -0
  11. package/dist/spec/integration/webauthn-io.d.ts +32 -0
  12. package/dist/spec/integration/webauthn-io.js +100 -0
  13. package/dist/spec/libs/browser-injection.spec.d.ts +1 -0
  14. package/dist/spec/libs/browser-injection.spec.js +15 -0
  15. package/dist/spec/libs/cose-key.spec.d.ts +1 -0
  16. package/dist/spec/libs/cose-key.spec.js +47 -0
  17. package/dist/spec/repository/credential-repository.spec.d.ts +1 -0
  18. package/dist/spec/repository/credential-repository.spec.js +96 -0
  19. package/dist/spec/webauthn/cose-key.spec.d.ts +1 -0
  20. package/dist/spec/webauthn/cose-key.spec.js +74 -0
  21. package/dist/spec/webauthn/webauthn-emulator.spec.d.ts +1 -0
  22. package/dist/spec/webauthn/webauthn-emulator.spec.js +222 -0
  23. package/dist/spec/webauthn/webauthn-model-json.spec.d.ts +1 -0
  24. package/dist/spec/webauthn/webauthn-model-json.spec.js +103 -0
  25. package/dist/spec/webauthn/webauthn-model.spec.d.ts +1 -0
  26. package/dist/spec/webauthn/webauthn-model.spec.js +97 -0
  27. package/dist/spec/webauthn/webauthn-test-server.d.ts +15 -0
  28. package/dist/spec/webauthn/webauthn-test-server.js +87 -0
  29. package/dist/src/authenticator/authenticator-emulator.d.ts +68 -0
  30. package/dist/src/authenticator/authenticator-emulator.js +304 -0
  31. package/dist/src/authenticator/ctap-model.d.ts +107 -0
  32. package/dist/src/authenticator/ctap-model.js +202 -0
  33. package/dist/src/index.d.ts +16 -0
  34. package/dist/src/index.js +34 -0
  35. package/dist/src/libs/encode-utils.d.ts +15 -0
  36. package/dist/src/libs/encode-utils.js +92 -0
  37. package/dist/src/repository/credentials-file-repository.d.ts +8 -0
  38. package/dist/src/repository/credentials-file-repository.js +62 -0
  39. package/dist/src/repository/credentials-memory-repository.d.ts +7 -0
  40. package/dist/src/repository/credentials-memory-repository.js +20 -0
  41. package/dist/src/repository/credentials-repository.d.ts +39 -0
  42. package/dist/src/repository/credentials-repository.js +46 -0
  43. package/dist/src/test-utils/browser-injection.d.ts +3 -0
  44. package/dist/src/test-utils/browser-injection.js +52 -0
  45. package/dist/src/webauthn/cose-key.d.ts +16 -0
  46. package/dist/src/webauthn/cose-key.js +151 -0
  47. package/dist/src/webauthn/webauthn-emulator.d.ts +30 -0
  48. package/dist/src/webauthn/webauthn-emulator.js +142 -0
  49. package/dist/src/webauthn/webauthn-model-json.d.ts +101 -0
  50. package/dist/src/webauthn/webauthn-model-json.js +187 -0
  51. package/dist/src/webauthn/webauthn-model.d.ts +68 -0
  52. package/dist/src/webauthn/webauthn-model.js +186 -0
  53. package/package.json +37 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (C) 2024 Nikkei Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # NID WebAuthn Emulator
2
+
3
+ [![CI Status](https://github.com/Nikkei/nid-webauthn-emulator/actions/workflows/ci.yml/badge.svg)](https://github.com/Nikkei/nid-webauthn-emulator/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ `NID WebAuthn Emulator` は、[FIDO2/CTAP Authenticator のエミュレータ](src/authenticator/authenticator-emulator.ts) およびそれを利用した [WebAuthn API のエミュレータ](src/webauthn/webauthn-emulator.ts) ライブラリです。それぞれ WebAuthn API および CTAP の仕様に基づいて実装されています。このモジュールは Node.js 上で動作し、ローカルでの Passkeys の統合テストを目的として設計されています。
7
+
8
+ それぞれのエミュレータの詳細な仕様は下記を参照してください。
9
+
10
+ - [FIDO2/CTAP Authenticator Emulator 開発者向け詳細仕様](docs/authenticator-emulator.md)
11
+ - [WebAuthn API Emulator 開発者向け詳細仕様](docs/webauthn-emulator.md)
12
+
13
+ ## 使用方法
14
+
15
+ ```bash
16
+ npm install nid-webauthn-emulator
17
+ ```
18
+
19
+ 基本的な使い方は `WebAuthnEmulator` クラスを作成し、`create` および `get` メソッドを利用することで、WebAuthn API における `navigator.credentials.create` および `navigator.credentials.get` をエミュレーションすることができます。
20
+
21
+ ```TypeScript
22
+ import WebAuthnEmulator from "nid-webauthn-emulator";
23
+
24
+ const emulator = new WebAuthnEmulator();
25
+ const origin = "https://example.com";
26
+
27
+ emulator.create(origin, creationOptions);
28
+ emulator.get(origin, requestOptions);
29
+ ```
30
+
31
+ また、`createJSON` および `getJSON` メソッドを利用することで、WebAuthn API で WebAuthn API 仕様に基づいた JSON データでのエミュレーションを行うこともできます。
32
+
33
+ ```TypeScript
34
+ emulator.createJSON(origin, creationOptionsJSON);
35
+ emulator.getJSON(origin, requestOptionsJSON);
36
+ ```
37
+
38
+ これらの JSON の仕様は、標準仕様の下記に定義されたデータです。
39
+
40
+ - <https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson>
41
+ - <https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson>
42
+
43
+ `WebAuthnEmulator` クラスは標準で下記の FIDO2/CTAP Authenticator をエミュレートします。
44
+
45
+ - 自動で User Verification を行います (`uv` フラグを立てます)
46
+ - ES256, RS256, EdDSA のアルゴリズムをサポートします
47
+ - USB 接続の CTAP2 デバイスをエミュレートします
48
+ - AAGUID は `NID-AUTH-3141592` です
49
+ - 認証時には Sign Counter を `1` インクリメントさせます
50
+
51
+ これらの設定は下記のように `AuthenticatorEmulator` クラスを作成し、`WebAuthnEmulator` クラスに渡すことで、Authenticator の挙動を変更することができます。
52
+
53
+ ```TypeScript
54
+ import WebAuthnEmulator, { AuthenticatorEmulator } from "nid-webauthn-emulator";
55
+
56
+ const authenticator = new AuthenticatorEmulator({
57
+ algorithmIdentifiers: ["ES256"],
58
+ verifications: {
59
+ userVerified: false,
60
+ userPresent: false,
61
+ },
62
+ signCounterIncrement: 0,
63
+ });
64
+
65
+ const webAuthnEmulator = new WebAuthnEmulator(authenticator);
66
+ ```
67
+
68
+ `AuthenticatorEmulator` は FIDO2/CTAP の仕様のうち下記のコマンドを実装しています。
69
+
70
+ - `authenticatorMakeCredential` (CTAP2) : 認証情報の作成
71
+ - `authenticatorGetAssertion` (CTAP2) : 認証情報の取得
72
+ - `authenticatorGetInfo` (CTAP2) : 認証情報の取得
73
+
74
+ これらは通常は直接利用することはありませんが、`WebAuthnEmulator` クラスの内部で CTAP のプロトコルに従って下記の通り呼び出されています。
75
+
76
+ ```TypeScript
77
+ const authenticatorRequest = packMakeCredentialRequest({
78
+ clientDataHash: createHash("sha256").update(clientDataJSON).digest(),
79
+ rp: options.publicKey.rp,
80
+ user: options.publicKey.user,
81
+ pubKeyCredParams: options.publicKey.pubKeyCredParams,
82
+ excludeList: options.publicKey.excludeCredentials,
83
+ options: {
84
+ rk: options.publicKey.authenticatorSelection?.requireResidentKey,
85
+ uv: options.publicKey.authenticatorSelection?.userVerification !== "discouraged",
86
+ },
87
+ });
88
+ const authenticatorResponse = unpackMakeCredentialResponse(this.authenticator.command(authenticatorRequest));
89
+ ```
90
+
91
+ ## [WebAuthn.io](https://webauthn.io/) での実行例
92
+
93
+ WebAuthn の著名なデモサイトである [webauthn.io](https://webauthn.io/) での使用例を示します。[統合テスト](spec/integration/integration.spec.ts) に実際にうごくテストコード例があります。
94
+
95
+ ```TypeScript
96
+ // Origin および WebAuthn API エミュレータを初期化します
97
+ // ここでは、https://webauthn.io を Origin として利用します
98
+ const origin = "https://webauthn.io";
99
+ const emulator = new WebAuthnEmulator();
100
+ const webauthnIO = await WebAuthnIO.create();
101
+ const user = webauthnIO.getUser();
102
+
103
+ // Authenticator の情報を表示します
104
+ console.log("Authenticator Information", emulator.getAuthenticatorInfo());
105
+
106
+ // WebAuthn API Emulator により パスキーを作成します
107
+ const creationOptions = await webauthnIO.getRegistrationOptions(user);
108
+ const creationCredential = emulator.createJSON(origin, creationOptions);
109
+ await webauthnIO.getRegistrationVerification(user, creationCredential);
110
+
111
+ // 認証を webauthn.io で検証します
112
+ const requestOptions = await webauthnIO.getAuthenticationOptions();
113
+ const requestCredential = emulator.getJSON(origin, requestOptions);
114
+ await webauthnIO.getAuthenticationVerification(requestCredential);
115
+ ```
116
+
117
+ ## Playwright による自動テスト
118
+
119
+ このライブラリは Passkeys の E2E テストでの利用を目的としており、特に Playwright での利用を想定しています。Playwright でのテスト用にユーティリティクラス `BrowserInjection` を利用して簡単に、WebAuthn API エミュレータを利用することができます。使い方は下記の通りです。
120
+
121
+ ```TypeScript
122
+ import WebAuthnEmulator, {
123
+ BrowserInjection,
124
+ type PublicKeyCredentialCreationOptionsJSON,
125
+ type PublicKeyCredentialRequestOptionsJSON,
126
+ } from "nid-webauthn-emulator";
127
+
128
+ async function startWebAuthnEmulator(page: Page, origin: string, debug = false) {
129
+ const emulator = new WebAuthnEmulator();
130
+
131
+ await page.exposeFunction(
132
+ BrowserInjection.WebAuthnEmulatorCreate,
133
+ async (optionsJSON: PublicKeyCredentialCreationOptionsJSON) => {
134
+ const response = emulator.createJSON(origin, optionsJSON);
135
+ return response;
136
+ },
137
+ );
138
+
139
+ await page.exposeFunction(
140
+ BrowserInjection.WebAuthnEmulatorGet,
141
+ async (optionsJSON: PublicKeyCredentialRequestOptionsJSON) => {
142
+ const response = emulator.getJSON(origin, optionsJSON);
143
+ return response;
144
+ },
145
+ );
146
+ }
147
+
148
+ test.describe("Passkeys Tests", { tag: ["@daily"] }, () => {
149
+ test("Passkeys login test", async ({ page }) => {
150
+ // Page内で最初に1回だけ定義する exposed functions
151
+ await startWebAuthnEmulator(page, env, true);
152
+ await page.goto("https://example.com/passkeys/login");
153
+
154
+ // Passkeys の WebAuthn API をフック開始
155
+ // ページ遷移後に実行する必要がある
156
+ await page.evaluate(BrowserInjection.HookWebAuthnApis);
157
+ });
158
+ });
159
+ ```
160
+
161
+ `startWebAuthnEmulator` では Playwright の `exposeFunction` を利用して、`WebAuthnEmulator` の `createJSON` および `getJSON` メソッドをブラウザのコンテキストに注入します。これにより、Playwright でのテストコンテキストにおいて `WebAuthnEmulator` クラスの `get` および `create` の各 API がそれぞれ `window` オブジェクトの下に定義されるようになります。
162
+
163
+ - `window.webAuthnEmulatorGet`: `WebAuthnEmulator.getJSON` の Exposed Function
164
+ - `window.webAuthnEmulatorCreate`: `WebAuthnEmulator.createJSON` の Exposed Function
165
+
166
+ これらは Page グローバルに定義されるため、Page インスタンスにつき 1 回だけ定義する必要があります。
167
+
168
+ 次に `navigator.credentials.get` 等の WebAuthn API をフックするために、`BrowserInjection.HookWebAuthnApis` をテストコンテキストにおいて評価します。
169
+
170
+ ```TypeScript
171
+ await page.evaluate(BrowserInjection.HookWebAuthnApis);
172
+ ```
173
+
174
+ `BrowserInjection.HookWebAuthnApis` は JavaScript 関数のシリアライズされた文字列であり、評価すると下記のような処理を行います。
175
+
176
+ - `navigator.credentials.get` の定義を上書きし、`window.webAuthnEmulatorGet` を呼び出す
177
+ - `navigator.credentials.create` の定義を上書きし、`window.webAuthnEmulatorCreate` を呼び出す
178
+
179
+ これにより先ほどの `exposeFunction` で定義した `WebAuthnEmulator` のメソッドが、`navigator.credentials.get` および `navigator.credentials.create` 呼び出し時に実行されるようになります。これらの処理中にはテストコンテキストと Playwright のコンテキスト間の通信のためにデータのシリアライズおよびデシリアライズが行われるため、そのための処理も含まれています。
180
+
181
+ ## ライセンス
182
+
183
+ MIT License
184
+
185
+ Copyright (C) 2024 Nikkei Inc.
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const globals_2 = require("@jest/globals");
5
+ const src_1 = require("../../src");
6
+ const ctap_model_1 = require("../../src/authenticator/ctap-model");
7
+ (0, globals_1.describe)("Authenticator Emulator Exceptional Test", () => {
8
+ // Success case has been tested in the webauthn-emulator.spec.ts
9
+ (0, globals_1.test)("Unknown command _ CTAP Error", async () => {
10
+ const testRequest = {
11
+ command: ctap_model_1.CTAP_COMMAND.authenticatorReset,
12
+ };
13
+ const authenticator = new src_1.AuthenticatorEmulator();
14
+ (0, globals_2.expect)(() => {
15
+ authenticator.command(testRequest);
16
+ }).toThrowError("CTAP error: CTAP1_ERR_INVALID_COMMAND (1)");
17
+ });
18
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const globals_2 = require("@jest/globals");
5
+ const ctap_model_1 = require("../../src/authenticator/ctap-model");
6
+ (0, globals_1.describe)("CTAP Model Test", () => {
7
+ (0, globals_1.test)("MakeCredentialRequest CTAP Object pack and unpack", async () => {
8
+ const testRequest = {
9
+ clientDataHash: new Uint8Array([99, 104, 97, 108, 108, 101, 110, 103, 101]),
10
+ rp: { name: "webauthn.io", id: "webauthn.io" },
11
+ user: { id: new Uint8Array([116, 101, 115, 116, 45, 117, 115, 101, 114]), name: "user", displayName: "user" },
12
+ pubKeyCredParams: [{ alg: -7, type: "public-key" }],
13
+ excludeList: [{ id: new Uint8Array([116, 101, 115, 116, 45, 99]), type: "public-key" }],
14
+ options: { rk: true, uv: true },
15
+ pinAuth: new Uint8Array([112, 105, 110, 65, 117, 116, 104]),
16
+ extensions: {},
17
+ pinProtocol: 9,
18
+ };
19
+ const packed = (0, ctap_model_1.packMakeCredentialRequest)(testRequest);
20
+ const unpacked = (0, ctap_model_1.unpackRequest)(packed);
21
+ const rePacked = (0, ctap_model_1.packMakeCredentialRequest)(unpacked.request);
22
+ (0, globals_2.expect)(packed).toEqual(rePacked);
23
+ (0, globals_2.expect)(unpacked.request).toEqual(testRequest);
24
+ });
25
+ (0, globals_1.test)("GetAssertionRequest CTAP Object pack and unpack", async () => {
26
+ const testRequest = {
27
+ rpId: "webauthn.io",
28
+ clientDataHash: new Uint8Array([99, 104, 97, 108, 108, 101, 110, 103, 101]),
29
+ allowList: [{ id: new Uint8Array([116, 101, 115, 116, 45, 99]), type: "public-key" }],
30
+ extensions: {},
31
+ options: { up: true, uv: true },
32
+ pinAuth: new Uint8Array([112, 105, 110, 65, 117, 116, 104]),
33
+ pinProtocol: 9,
34
+ };
35
+ const packed = (0, ctap_model_1.packGetAssertionRequest)(testRequest);
36
+ const unpacked = (0, ctap_model_1.unpackRequest)(packed);
37
+ const rePacked = (0, ctap_model_1.packGetAssertionRequest)(unpacked.request);
38
+ (0, globals_2.expect)(packed).toEqual(rePacked);
39
+ (0, globals_2.expect)(unpacked.request).toEqual(testRequest);
40
+ });
41
+ (0, globals_1.test)("MakeCredentialResponse CTAP Object pack and unpack", async () => {
42
+ const testResponse = {
43
+ fmt: "none",
44
+ attStmt: {},
45
+ authData: new Uint8Array([97, 117, 116, 104, 68, 97, 116, 97]),
46
+ };
47
+ const packed = (0, ctap_model_1.packMakeCredentialResponse)(testResponse);
48
+ const unpacked = (0, ctap_model_1.unpackMakeCredentialResponse)(packed);
49
+ const rePacked = (0, ctap_model_1.packMakeCredentialResponse)(unpacked);
50
+ (0, globals_2.expect)(packed).toEqual(rePacked);
51
+ (0, globals_2.expect)(unpacked).toEqual(testResponse);
52
+ });
53
+ (0, globals_1.test)("GetAssertionResponse CTAP Object pack and unpack", async () => {
54
+ const testResponse = {
55
+ authData: new Uint8Array([97, 117, 116, 104, 68, 97, 116, 97]),
56
+ signature: new Uint8Array([115, 105, 103]),
57
+ user: { id: new Uint8Array([116, 101, 115, 116, 45, 117, 115, 101, 114]), name: "user", displayName: "user" },
58
+ numberOfCredentials: 1,
59
+ };
60
+ const packed = (0, ctap_model_1.packGetAssertionResponse)(testResponse);
61
+ const unpacked = (0, ctap_model_1.unpackGetAssertionResponse)(packed);
62
+ const rePacked = (0, ctap_model_1.packGetAssertionResponse)(unpacked);
63
+ (0, globals_2.expect)(packed).toEqual(rePacked);
64
+ (0, globals_2.expect)(unpacked).toEqual(testResponse);
65
+ });
66
+ (0, globals_1.test)("GetInfoResponse CTAP Object pack and unpack", async () => {
67
+ const testResponse = {
68
+ versions: ["U2F_V2"],
69
+ extensions: ["credProtect"],
70
+ aaguid: new Uint8Array([97, 97, 103, 117, 105, 100]),
71
+ options: { plat: false, rk: true, clientPin: true, up: true, uv: true },
72
+ maxMsgSize: 1200,
73
+ pinProtocols: [1, 2, 3],
74
+ };
75
+ const packed = (0, ctap_model_1.packGetInfoResponse)(testResponse);
76
+ const unpacked = (0, ctap_model_1.unpackGetInfoResponse)(packed);
77
+ const rePacked = (0, ctap_model_1.packGetInfoResponse)(unpacked);
78
+ (0, globals_2.expect)(packed).toEqual(rePacked);
79
+ (0, globals_2.expect)(unpacked).toEqual(testResponse);
80
+ });
81
+ (0, globals_1.test)("Illegal data parse test _ failed to unpack", async () => {
82
+ const response = {};
83
+ const expected = "CTAP error: CTAP2_ERR_INVALID_CBOR (18)";
84
+ (0, globals_2.expect)(() => (0, ctap_model_1.unpackMakeCredentialResponse)(response)).toThrowError(expected);
85
+ (0, globals_2.expect)(() => (0, ctap_model_1.unpackGetAssertionResponse)(response)).toThrowError(expected);
86
+ (0, globals_2.expect)(() => (0, ctap_model_1.unpackGetInfoResponse)(response)).toThrowError(expected);
87
+ });
88
+ (0, globals_1.test)("Illegal cbor data parse test _ failed to unpack", async () => {
89
+ const request = {
90
+ command: ctap_model_1.CTAP_COMMAND.authenticatorMakeCredential,
91
+ data: new Uint8Array([1, 2, 3, 4, 5]),
92
+ };
93
+ (0, globals_2.expect)(() => (0, ctap_model_1.unpackRequest)(request)).toThrowError("CTAP error: CTAP2_ERR_INVALID_CBOR (18)");
94
+ });
95
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const globals_1 = require("@jest/globals");
7
+ const index_1 = __importDefault(require("../../src/index"));
8
+ const webauthn_io_1 = require("./webauthn-io");
9
+ (0, globals_1.describe)("Passkeys Integration Test by webauthn.io", () => {
10
+ globals_1.test.skip("Registration Ceremony and Authentication Ceremony", async () => {
11
+ const origin = "https://webauthn.io";
12
+ const emulator = new index_1.default();
13
+ const webauthnIO = await webauthn_io_1.WebAuthnIO.create();
14
+ const user = webauthnIO.getUser();
15
+ // Authenticator Information
16
+ console.log("Authenticator Information", emulator.getAuthenticatorInfo());
17
+ // Create passkey.
18
+ const creationOptions = await webauthnIO.getRegistrationOptions(user);
19
+ console.log("Registration options", creationOptions);
20
+ const creationCredential = emulator.createJSON(origin, creationOptions);
21
+ console.log("Registration credential", creationCredential);
22
+ await webauthnIO.getRegistrationVerification(user, creationCredential);
23
+ console.log("Registration verification completed");
24
+ // Authenticate passkey.
25
+ const requestOptions = await webauthnIO.getAuthenticationOptions();
26
+ console.log("Authentication options", requestOptions);
27
+ const requestCredential = emulator.getJSON(origin, requestOptions);
28
+ console.log("Authentication credential", requestCredential);
29
+ await webauthnIO.getAuthenticationVerification(requestCredential);
30
+ console.log("Authentication verification completed");
31
+ }, 60000);
32
+ });
@@ -0,0 +1,11 @@
1
+ import type { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from "../../src/webauthn/webauthn-model-json";
2
+ export interface PasskeysUser {
3
+ username: string;
4
+ id: string;
5
+ }
6
+ export interface PasskeysApiClient {
7
+ getRegistrationOptions(user: PasskeysUser): Promise<PublicKeyCredentialCreationOptionsJSON>;
8
+ getRegistrationVerification(user: PasskeysUser, response: RegistrationResponseJSON): Promise<void>;
9
+ getAuthenticationOptions(): Promise<PublicKeyCredentialRequestOptionsJSON>;
10
+ getAuthenticationVerification(response: AuthenticationResponseJSON): Promise<void>;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,32 @@
1
+ import type { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from "../../src/webauthn/webauthn-model-json";
2
+ import type { PasskeysApiClient, PasskeysUser } from "./passkeys-api-client";
3
+ /**
4
+ * Passkeys Api Client implementation for webauthn.io
5
+ */
6
+ export declare class WebAuthnIO implements PasskeysApiClient {
7
+ private sessionId;
8
+ private constructor();
9
+ static create(): Promise<WebAuthnIO>;
10
+ getUser(): PasskeysUser;
11
+ /**
12
+ * Get a passkey registration options by https://webauthn.io/registration/options
13
+ * @returns PublicKeyCredentialCreationOptionsJSON
14
+ */
15
+ getRegistrationOptions(user: PasskeysUser): Promise<PublicKeyCredentialCreationOptionsJSON>;
16
+ /**
17
+ * Register your passkey account by https://webauthn.io/registration/verification
18
+ * @param response RegistrationResponseJSON
19
+ */
20
+ getRegistrationVerification(user: PasskeysUser, response: RegistrationResponseJSON): Promise<void>;
21
+ /**
22
+ * Get a passkey authentication options by https://webauthn.io/authentication/options
23
+ * @returns PublicKeyCredentialRequestOptionsJSON
24
+ */
25
+ getAuthenticationOptions(): Promise<PublicKeyCredentialRequestOptionsJSON>;
26
+ /**
27
+ * Authenticate your passkey account by https://webauthn.io/authentication/verification
28
+ * @param response AuthenticationResponseJSON
29
+ */
30
+ getAuthenticationVerification(response: AuthenticationResponseJSON): Promise<void>;
31
+ private static getSessionId;
32
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebAuthnIO = void 0;
4
+ /**
5
+ * Passkeys Api Client implementation for webauthn.io
6
+ */
7
+ class WebAuthnIO {
8
+ sessionId;
9
+ constructor(sessionId) {
10
+ this.sessionId = sessionId;
11
+ }
12
+ static async create() {
13
+ const sessionId = await WebAuthnIO.getSessionId();
14
+ return new WebAuthnIO(sessionId);
15
+ }
16
+ getUser() {
17
+ return { username: `user-${this.sessionId}`, id: this.sessionId };
18
+ }
19
+ /**
20
+ * Get a passkey registration options by https://webauthn.io/registration/options
21
+ * @returns PublicKeyCredentialCreationOptionsJSON
22
+ */
23
+ async getRegistrationOptions(user) {
24
+ const optionsRequest = {
25
+ username: user.username,
26
+ user_verification: "preferred",
27
+ attestation: "none",
28
+ attachment: "all",
29
+ algorithms: ["es256", "rs256"],
30
+ discoverable_credential: "preferred",
31
+ hints: [],
32
+ };
33
+ const options = await fetch("https://webauthn.io/registration/options", {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify(optionsRequest),
37
+ });
38
+ const optionsJson = await options.json();
39
+ return optionsJson;
40
+ }
41
+ /**
42
+ * Register your passkey account by https://webauthn.io/registration/verification
43
+ * @param response RegistrationResponseJSON
44
+ */
45
+ async getRegistrationVerification(user, response) {
46
+ const verificationRequest = {
47
+ response: response,
48
+ username: user.username,
49
+ };
50
+ const verification = await fetch("https://webauthn.io/registration/verification", {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify(verificationRequest),
54
+ });
55
+ const verificationJson = await verification.json();
56
+ if (!verificationJson.verified) {
57
+ throw new Error(`Verification failed: ${JSON.stringify(verificationJson)}`);
58
+ }
59
+ }
60
+ /**
61
+ * Get a passkey authentication options by https://webauthn.io/authentication/options
62
+ * @returns PublicKeyCredentialRequestOptionsJSON
63
+ */
64
+ async getAuthenticationOptions() {
65
+ const optionsRequest = {
66
+ user_verification: "preferred",
67
+ };
68
+ const options = await fetch("https://webauthn.io/authentication/options", {
69
+ method: "POST",
70
+ headers: { "Content-Type": "application/json", Cookie: `sessionid=${this.sessionId}` },
71
+ body: JSON.stringify(optionsRequest),
72
+ });
73
+ const optionsJson = await options.json();
74
+ return optionsJson;
75
+ }
76
+ /**
77
+ * Authenticate your passkey account by https://webauthn.io/authentication/verification
78
+ * @param response AuthenticationResponseJSON
79
+ */
80
+ async getAuthenticationVerification(response) {
81
+ const verificationRequest = {
82
+ response: response,
83
+ username: "",
84
+ };
85
+ const verification = await fetch("https://webauthn.io/authentication/verification", {
86
+ method: "POST",
87
+ headers: { "Content-Type": "application/json", Cookie: `sessionid=${this.sessionId}` },
88
+ body: JSON.stringify(verificationRequest),
89
+ });
90
+ const verificationJson = await verification.json();
91
+ if (!verificationJson.verified) {
92
+ throw new Error(`Verification failed: ${JSON.stringify(verificationJson)}`);
93
+ }
94
+ }
95
+ static async getSessionId() {
96
+ const home = await fetch("https://webauthn.io/");
97
+ return home.headers.get("set-cookie")?.split(";")[0].split("=")[1] ?? "";
98
+ }
99
+ }
100
+ exports.WebAuthnIO = WebAuthnIO;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const browser_injection_1 = require("../../src/test-utils/browser-injection");
5
+ (0, globals_1.describe)("Browser Injection Test", () => {
6
+ (0, globals_1.test)("Browser Injection Test", async () => {
7
+ const window = { navigator: { credentials: { create: undefined, get: undefined } } };
8
+ const PublicKeyCredential = { isConditionalMediationAvailable: async () => false };
9
+ // biome-ignore lint/security/noGlobalEval: This is a test code.
10
+ eval(browser_injection_1.HookWebAuthnApis);
11
+ (0, globals_1.expect)(await PublicKeyCredential.isConditionalMediationAvailable()).toBeTruthy();
12
+ (0, globals_1.expect)(window.navigator.credentials.create).toBeDefined();
13
+ (0, globals_1.expect)(window.navigator.credentials.get).toBeDefined();
14
+ });
15
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const authenticator_emulator_1 = require("../../src/authenticator/authenticator-emulator");
5
+ const cose_key_1 = require("../../src/webauthn/cose-key");
6
+ const webauthn_model_1 = require("../../src/webauthn/webauthn-model");
7
+ globals_1.describe.each([-7, -8, -257])("CoseKey Test: %s", (alg) => {
8
+ const getKey = () => {
9
+ const authenticator = new authenticator_emulator_1.AuthenticatorEmulator();
10
+ const rpId = new webauthn_model_1.RpId("example.com");
11
+ const userHandle = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
12
+ const credential = authenticator.authenticatorMakeCredential({
13
+ rp: { id: rpId.value, name: "example" },
14
+ user: { id: userHandle, name: "example", displayName: "example" },
15
+ pubKeyCredParams: [{ alg, type: "public-key" }],
16
+ clientDataHash: new Uint8Array(32),
17
+ });
18
+ return (0, webauthn_model_1.unpackAuthenticatorData)(credential.authData).attestedCredentialData?.credentialPublicKey;
19
+ };
20
+ (0, globals_1.test)("Der format _ serialize and deserialize test", async () => {
21
+ const testCoseKey = getKey();
22
+ const serialized = testCoseKey.toDer();
23
+ const deserialized = cose_key_1.CoseKey.fromDer(testCoseKey.toDer());
24
+ const reSerialized = deserialized.toDer();
25
+ (0, globals_1.expect)(testCoseKey.alg).toEqual(alg);
26
+ (0, globals_1.expect)(testCoseKey).toEqual(deserialized);
27
+ (0, globals_1.expect)(serialized).toEqual(reSerialized);
28
+ });
29
+ (0, globals_1.test)("Jwk format _ serialize and deserialize test", async () => {
30
+ const testCoseKey = getKey();
31
+ const serialized = testCoseKey.toJwk();
32
+ const deserialized = cose_key_1.CoseKey.fromJwk(testCoseKey.toJwk()).toJwk();
33
+ const reSerialized = cose_key_1.CoseKey.fromJwk(deserialized).toJwk();
34
+ (0, globals_1.expect)(testCoseKey.alg).toEqual(alg);
35
+ (0, globals_1.expect)(serialized).toEqual(deserialized);
36
+ (0, globals_1.expect)(serialized).toEqual(reSerialized);
37
+ });
38
+ (0, globals_1.test)("KeyObject format _ serialize and deserialize test", async () => {
39
+ const testCoseKey = getKey();
40
+ const serialized = testCoseKey.toKeyObject();
41
+ const deserialized = cose_key_1.CoseKey.fromKeyObject(testCoseKey.toKeyObject()).toKeyObject();
42
+ const reSerialized = cose_key_1.CoseKey.fromKeyObject(deserialized).toKeyObject();
43
+ (0, globals_1.expect)(testCoseKey.alg).toEqual(alg);
44
+ (0, globals_1.expect)(serialized).toEqual(deserialized);
45
+ (0, globals_1.expect)(serialized).toEqual(reSerialized);
46
+ });
47
+ });