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.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/spec/authenticator/authenticator-emulator.spec.d.ts +1 -0
- package/dist/spec/authenticator/authenticator-emulator.spec.js +18 -0
- package/dist/spec/authenticator/ctap-model.spec.d.ts +1 -0
- package/dist/spec/authenticator/ctap-model.spec.js +95 -0
- package/dist/spec/integration/integration.spec.d.ts +1 -0
- package/dist/spec/integration/integration.spec.js +32 -0
- package/dist/spec/integration/passkeys-api-client.d.ts +11 -0
- package/dist/spec/integration/passkeys-api-client.js +2 -0
- package/dist/spec/integration/webauthn-io.d.ts +32 -0
- package/dist/spec/integration/webauthn-io.js +100 -0
- package/dist/spec/libs/browser-injection.spec.d.ts +1 -0
- package/dist/spec/libs/browser-injection.spec.js +15 -0
- package/dist/spec/libs/cose-key.spec.d.ts +1 -0
- package/dist/spec/libs/cose-key.spec.js +47 -0
- package/dist/spec/repository/credential-repository.spec.d.ts +1 -0
- package/dist/spec/repository/credential-repository.spec.js +96 -0
- package/dist/spec/webauthn/cose-key.spec.d.ts +1 -0
- package/dist/spec/webauthn/cose-key.spec.js +74 -0
- package/dist/spec/webauthn/webauthn-emulator.spec.d.ts +1 -0
- package/dist/spec/webauthn/webauthn-emulator.spec.js +222 -0
- package/dist/spec/webauthn/webauthn-model-json.spec.d.ts +1 -0
- package/dist/spec/webauthn/webauthn-model-json.spec.js +103 -0
- package/dist/spec/webauthn/webauthn-model.spec.d.ts +1 -0
- package/dist/spec/webauthn/webauthn-model.spec.js +97 -0
- package/dist/spec/webauthn/webauthn-test-server.d.ts +15 -0
- package/dist/spec/webauthn/webauthn-test-server.js +87 -0
- package/dist/src/authenticator/authenticator-emulator.d.ts +68 -0
- package/dist/src/authenticator/authenticator-emulator.js +304 -0
- package/dist/src/authenticator/ctap-model.d.ts +107 -0
- package/dist/src/authenticator/ctap-model.js +202 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +34 -0
- package/dist/src/libs/encode-utils.d.ts +15 -0
- package/dist/src/libs/encode-utils.js +92 -0
- package/dist/src/repository/credentials-file-repository.d.ts +8 -0
- package/dist/src/repository/credentials-file-repository.js +62 -0
- package/dist/src/repository/credentials-memory-repository.d.ts +7 -0
- package/dist/src/repository/credentials-memory-repository.js +20 -0
- package/dist/src/repository/credentials-repository.d.ts +39 -0
- package/dist/src/repository/credentials-repository.js +46 -0
- package/dist/src/test-utils/browser-injection.d.ts +3 -0
- package/dist/src/test-utils/browser-injection.js +52 -0
- package/dist/src/webauthn/cose-key.d.ts +16 -0
- package/dist/src/webauthn/cose-key.js +151 -0
- package/dist/src/webauthn/webauthn-emulator.d.ts +30 -0
- package/dist/src/webauthn/webauthn-emulator.js +142 -0
- package/dist/src/webauthn/webauthn-model-json.d.ts +101 -0
- package/dist/src/webauthn/webauthn-model-json.js +187 -0
- package/dist/src/webauthn/webauthn-model.d.ts +68 -0
- package/dist/src/webauthn/webauthn-model.js +186 -0
- 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
|
+
[](https://github.com/Nikkei/nid-webauthn-emulator/actions/workflows/ci.yml)
|
|
4
|
+
[](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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|