nid-webauthn-emulator 0.2.6 → 0.2.7
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/dist/spec/authenticator/authenticator-emulator.spec.js +67 -0
- package/dist/spec/authenticator/ctap-model.spec.js +84 -0
- package/dist/spec/libs/encode-utils.spec.d.ts +1 -0
- package/dist/spec/libs/encode-utils.spec.js +38 -0
- package/dist/spec/webauthn/webauthn-emulator.spec.js +337 -33
- package/dist/src/authenticator/authenticator-emulator.d.ts +1 -1
- package/dist/src/authenticator/authenticator-emulator.js +1 -1
- package/dist/src/authenticator/ctap-model.d.ts +16 -16
- package/dist/src/libs/encode-utils.d.ts +5 -5
- package/dist/src/libs/encode-utils.js +6 -3
- package/dist/src/webauthn/cose-key.d.ts +4 -4
- package/dist/src/webauthn/cose-key.js +1 -1
- package/dist/src/webauthn/webauthn-emulator.d.ts +2 -4
- package/dist/src/webauthn/webauthn-emulator.js +65 -36
- package/dist/src/webauthn/webauthn-model.d.ts +11 -11
- package/package.json +10 -10
|
@@ -241,4 +241,71 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
241
241
|
}).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
242
242
|
});
|
|
243
243
|
});
|
|
244
|
+
(0, globals_1.describe)("Authenticator Credential Management additional error coverage", () => {
|
|
245
|
+
(0, globals_1.test)("dispatch default branch throws invalid command", () => {
|
|
246
|
+
const authenticator = new src_1.AuthenticatorEmulator();
|
|
247
|
+
// Build a CTAP request with an unknown subCommand to hit default branch
|
|
248
|
+
const data = encode_utils_1.default.encodeCbor({ "1": -1 });
|
|
249
|
+
(0, globals_1.expect)(() => authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement, data })).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND));
|
|
250
|
+
});
|
|
251
|
+
(0, globals_1.test)("enumerateCredentialsBegin without repository throws not allowed", () => {
|
|
252
|
+
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
253
|
+
const req = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
254
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
255
|
+
subCommandParams: { rpId: "example.com" },
|
|
256
|
+
});
|
|
257
|
+
(0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
258
|
+
});
|
|
259
|
+
(0, globals_1.test)("updateUserInformation without repository throws not allowed", () => {
|
|
260
|
+
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
261
|
+
const req = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
262
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
263
|
+
subCommandParams: { rpId: "example.com", user: { id: new Uint8Array([1]), name: "n", displayName: "d" } },
|
|
264
|
+
});
|
|
265
|
+
(0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
266
|
+
});
|
|
267
|
+
(0, globals_1.test)("deleteCredential without repository throws not allowed", () => {
|
|
268
|
+
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
269
|
+
const req = (0, ctap_model_1.packCredentialManagementRequest)({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
|
|
270
|
+
(0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
271
|
+
});
|
|
272
|
+
(0, globals_1.test)("deleteCredential without credentialId throws invalid parameter", () => {
|
|
273
|
+
const authenticator = new src_1.AuthenticatorEmulator();
|
|
274
|
+
const req = (0, ctap_model_1.packCredentialManagementRequest)({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
|
|
275
|
+
(0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
276
|
+
});
|
|
277
|
+
(0, globals_1.test)("deleteCredential for non-existent id throws no credentials", () => {
|
|
278
|
+
const authenticator = new src_1.AuthenticatorEmulator();
|
|
279
|
+
const credentialId = encode_utils_1.default.strToUint8Array("non-existent-id");
|
|
280
|
+
const req = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
281
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
|
|
282
|
+
subCommandParams: { credentialId },
|
|
283
|
+
});
|
|
284
|
+
(0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
285
|
+
});
|
|
286
|
+
(0, globals_1.test)("private enumerateCredentialsBegin guard throws not allowed when repository missing", () => {
|
|
287
|
+
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
288
|
+
const target = authenticator.authenticatorEnumerateCredentialsBegin.bind(authenticator);
|
|
289
|
+
const call = () => target({
|
|
290
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
291
|
+
subCommandParams: { rpId: "x" },
|
|
292
|
+
});
|
|
293
|
+
(0, globals_1.expect)(call).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
294
|
+
});
|
|
295
|
+
(0, globals_1.test)("private updateUserInformation guard throws not allowed when repository missing", () => {
|
|
296
|
+
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
297
|
+
const target = authenticator.authenticatorUpdateUserInformation.bind(authenticator);
|
|
298
|
+
const call = () => target({
|
|
299
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
300
|
+
subCommandParams: { rpId: "x", user: { id: new Uint8Array([1]), name: "n", displayName: "d" } },
|
|
301
|
+
});
|
|
302
|
+
(0, globals_1.expect)(call).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
303
|
+
});
|
|
304
|
+
(0, globals_1.test)("private deleteCredential guard throws not allowed when repository missing", () => {
|
|
305
|
+
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
306
|
+
const target = authenticator.authenticatorDeleteCredential.bind(authenticator);
|
|
307
|
+
const call = () => target({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
|
|
308
|
+
(0, globals_1.expect)(call).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
309
|
+
});
|
|
310
|
+
});
|
|
244
311
|
});
|
|
@@ -91,4 +91,88 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
91
91
|
};
|
|
92
92
|
(0, globals_1.expect)(() => (0, ctap_model_1.unpackRequest)(request)).toThrow("CTAP error: CTAP2_ERR_INVALID_CBOR (18)");
|
|
93
93
|
});
|
|
94
|
+
(0, globals_1.test)("unpackRequest default branch returns undefined request for unhandled command", () => {
|
|
95
|
+
const req = { command: ctap_model_1.CTAP_COMMAND.authenticatorClientPIN }; // not explicitly handled in switch
|
|
96
|
+
const unpacked = (0, ctap_model_1.unpackRequest)(req);
|
|
97
|
+
(0, globals_1.expect)(unpacked.command).toBe(ctap_model_1.CTAP_COMMAND.authenticatorClientPIN);
|
|
98
|
+
(0, globals_1.expect)(unpacked.request).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
(0, globals_1.test)("credential management request with undefined optional params round-trips", () => {
|
|
101
|
+
const packed = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
102
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
103
|
+
// subCommandParams and pinUvAuth* omitted intentionally
|
|
104
|
+
});
|
|
105
|
+
const unpacked = (0, ctap_model_1.unpackCredentialManagementRequest)(packed);
|
|
106
|
+
(0, globals_1.expect)(unpacked.subCommand).toBe(ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin);
|
|
107
|
+
(0, globals_1.expect)(unpacked.subCommandParams).toBeUndefined();
|
|
108
|
+
(0, globals_1.expect)(unpacked.pinUvAuthParam).toBeUndefined();
|
|
109
|
+
(0, globals_1.expect)(unpacked.pinUvAuthProtocol).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
(0, globals_1.test)("credential management response optional binary fields handled when absent/present", () => {
|
|
112
|
+
// Absent case
|
|
113
|
+
const packedAbsent = (0, ctap_model_1.packCredentialManagementResponse)({});
|
|
114
|
+
const absent = (0, ctap_model_1.unpackCredentialManagementResponse)(packedAbsent);
|
|
115
|
+
(0, globals_1.expect)(absent.credentialID).toBeUndefined();
|
|
116
|
+
(0, globals_1.expect)(absent.publicKey).toBeUndefined();
|
|
117
|
+
(0, globals_1.expect)(absent.rpIDHash).toBeUndefined();
|
|
118
|
+
(0, globals_1.expect)(absent.largeBlobKey).toBeUndefined();
|
|
119
|
+
// Present case
|
|
120
|
+
const bin = new Uint8Array([1, 2, 3]);
|
|
121
|
+
const packedPresent = (0, ctap_model_1.packCredentialManagementResponse)({
|
|
122
|
+
credentialID: bin,
|
|
123
|
+
publicKey: bin,
|
|
124
|
+
rpIDHash: bin,
|
|
125
|
+
largeBlobKey: bin,
|
|
126
|
+
});
|
|
127
|
+
const present = (0, ctap_model_1.unpackCredentialManagementResponse)(packedPresent);
|
|
128
|
+
(0, globals_1.expect)(present.credentialID).toBeDefined();
|
|
129
|
+
(0, globals_1.expect)(present.publicKey).toBeDefined();
|
|
130
|
+
(0, globals_1.expect)(present.rpIDHash).toBeDefined();
|
|
131
|
+
(0, globals_1.expect)(present.largeBlobKey).toBeDefined();
|
|
132
|
+
if (present.credentialID && present.publicKey && present.rpIDHash && present.largeBlobKey) {
|
|
133
|
+
(0, globals_1.expect)(Array.from(present.credentialID)).toEqual([1, 2, 3]);
|
|
134
|
+
(0, globals_1.expect)(Array.from(present.publicKey)).toEqual([1, 2, 3]);
|
|
135
|
+
(0, globals_1.expect)(Array.from(present.rpIDHash)).toEqual([1, 2, 3]);
|
|
136
|
+
(0, globals_1.expect)(Array.from(present.largeBlobKey)).toEqual([1, 2, 3]);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
(0, globals_1.test)("unpackCredentialManagementRequest throws on invalid CBOR", () => {
|
|
140
|
+
const bad = {
|
|
141
|
+
command: ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement,
|
|
142
|
+
data: new Uint8Array([1, 2, 3]),
|
|
143
|
+
};
|
|
144
|
+
(0, globals_1.expect)(() => (0, ctap_model_1.unpackCredentialManagementRequest)(bad)).toThrow(/CTAP2_ERR_INVALID_CBOR/);
|
|
145
|
+
});
|
|
146
|
+
(0, globals_1.test)("unpackCredentialManagementResponse throws on invalid CBOR", () => {
|
|
147
|
+
const bad = {
|
|
148
|
+
status: ctap_model_1.CTAP_STATUS_CODE.CTAP2_OK,
|
|
149
|
+
data: new Uint8Array([1, 2, 3]),
|
|
150
|
+
};
|
|
151
|
+
(0, globals_1.expect)(() => (0, ctap_model_1.unpackCredentialManagementResponse)(bad)).toThrow(/CTAP2_ERR_INVALID_CBOR/);
|
|
152
|
+
});
|
|
153
|
+
(0, globals_1.test)("unpackRequest CM includes pinUvAuthParam when present", () => {
|
|
154
|
+
const pin = new Uint8Array([7, 7]);
|
|
155
|
+
const packed = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
156
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
157
|
+
subCommandParams: { rpId: "example.com" },
|
|
158
|
+
pinUvAuthParam: pin,
|
|
159
|
+
});
|
|
160
|
+
const unpacked = (0, ctap_model_1.unpackRequest)(packed);
|
|
161
|
+
const req = unpacked.request;
|
|
162
|
+
const isMgmt = (x) => typeof x === "object" && x !== null && "subCommand" in x;
|
|
163
|
+
(0, globals_1.expect)(isMgmt(req)).toBe(true);
|
|
164
|
+
if (isMgmt(req)) {
|
|
165
|
+
(0, globals_1.expect)(Array.from(req.pinUvAuthParam ?? new Uint8Array())).toEqual([7, 7]);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
(0, globals_1.test)("unpackCredentialManagementRequest includes pinUvAuthParam when present", () => {
|
|
169
|
+
const pin = new Uint8Array([9, 9]);
|
|
170
|
+
const packed = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
171
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
172
|
+
subCommandParams: { rpId: "example.com" },
|
|
173
|
+
pinUvAuthParam: pin,
|
|
174
|
+
});
|
|
175
|
+
const req = (0, ctap_model_1.unpackCredentialManagementRequest)(packed);
|
|
176
|
+
(0, globals_1.expect)(Array.from(req.pinUvAuthParam ?? new Uint8Array())).toEqual([9, 9]);
|
|
177
|
+
});
|
|
94
178
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
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 encode_utils_1 = __importDefault(require("../../src/libs/encode-utils"));
|
|
8
|
+
(0, globals_1.describe)("EncodeUtils encodeCbor/decodeCbor edge cases", () => {
|
|
9
|
+
(0, globals_1.test)("handles ArrayBuffer values within objects and arrays", () => {
|
|
10
|
+
const ab = new Uint8Array([1, 2, 3]).buffer; // ArrayBuffer branch
|
|
11
|
+
const ua = new Uint8Array([4, 5]);
|
|
12
|
+
const obj = {
|
|
13
|
+
v1: ab,
|
|
14
|
+
v2: ua,
|
|
15
|
+
arr: [ab, ua],
|
|
16
|
+
};
|
|
17
|
+
const encoded = encode_utils_1.default.encodeCbor(obj);
|
|
18
|
+
const decoded = encode_utils_1.default.decodeCbor(encoded);
|
|
19
|
+
// ArrayBuffer is decoded back to Uint8Array
|
|
20
|
+
(0, globals_1.expect)(decoded.v1).toBeInstanceOf(Uint8Array);
|
|
21
|
+
(0, globals_1.expect)(Array.from(decoded.v1)).toEqual([1, 2, 3]);
|
|
22
|
+
// Existing Uint8Array round-trips
|
|
23
|
+
(0, globals_1.expect)(Array.from(decoded.v2)).toEqual([4, 5]);
|
|
24
|
+
// Mixed array elements preserved as Uint8Array
|
|
25
|
+
(0, globals_1.expect)(Array.isArray(decoded.arr)).toBe(true);
|
|
26
|
+
const [a0, a1] = decoded.arr;
|
|
27
|
+
(0, globals_1.expect)(Array.from(a0)).toEqual([1, 2, 3]);
|
|
28
|
+
(0, globals_1.expect)(Array.from(a1)).toEqual([4, 5]);
|
|
29
|
+
});
|
|
30
|
+
(0, globals_1.test)("handles Buffer values without conversion in encoder and to Uint8Array on decode", () => {
|
|
31
|
+
const buf = Buffer.from([9, 8]);
|
|
32
|
+
const obj = { b: buf };
|
|
33
|
+
const encoded = encode_utils_1.default.encodeCbor(obj);
|
|
34
|
+
const decoded = encode_utils_1.default.decodeCbor(encoded);
|
|
35
|
+
(0, globals_1.expect)(decoded.b).toBeInstanceOf(Uint8Array);
|
|
36
|
+
(0, globals_1.expect)(Array.from(decoded.b)).toEqual([9, 8]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -61,15 +61,21 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
61
61
|
const emulator = new src_1.default();
|
|
62
62
|
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
63
63
|
const options = await testServer.getRegistrationOptions(user);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
const call = () => emulator.createJSON(illegalOrigin, options);
|
|
65
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
66
|
+
try {
|
|
67
|
+
call();
|
|
68
|
+
throw new Error("Expected DOMException");
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
72
|
+
(0, globals_1.expect)(e.name).toBe("SecurityError");
|
|
73
|
+
}
|
|
67
74
|
});
|
|
68
75
|
(0, globals_1.test)("Create Passkeys with no public key credential parameters _ failed to response", async () => {
|
|
69
76
|
const emulator = new src_1.default();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}).rejects.toThrow(src_1.NoPublicKeyError);
|
|
77
|
+
const call = () => emulator.create(webauthn_test_server_1.TEST_RP_ORIGIN, {});
|
|
78
|
+
(0, globals_1.expect)(call).toThrow(TypeError);
|
|
73
79
|
});
|
|
74
80
|
(0, globals_1.test)("Found in the exclude List _ CTAP Error", async () => {
|
|
75
81
|
const emulator = new src_1.default();
|
|
@@ -78,18 +84,32 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
78
84
|
const credential = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
79
85
|
await testServer.getRegistrationVerification(user, credential);
|
|
80
86
|
const options2 = await testServer.getRegistrationOptions(user);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options2);
|
|
88
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
89
|
+
try {
|
|
90
|
+
call();
|
|
91
|
+
throw new Error("Expected DOMException");
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
95
|
+
(0, globals_1.expect)(e.name).toBe("NotAllowedError");
|
|
96
|
+
}
|
|
84
97
|
});
|
|
85
98
|
(0, globals_1.test)("Invalid algorithm _ CTAP Error", async () => {
|
|
86
99
|
const emulator = new src_1.default();
|
|
87
100
|
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
88
101
|
const options = await testServer.getRegistrationOptions(user);
|
|
89
102
|
const illegalOptions = { ...options, pubKeyCredParams: [{ type: "public-key", alg: 99 }] };
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, illegalOptions);
|
|
104
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
105
|
+
try {
|
|
106
|
+
call();
|
|
107
|
+
throw new Error("Expected DOMException");
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
111
|
+
(0, globals_1.expect)(e.name).toBe("NotSupportedError");
|
|
112
|
+
}
|
|
93
113
|
});
|
|
94
114
|
(0, globals_1.test)("User registration operation failed _ CTAP Error", async () => {
|
|
95
115
|
const authenticator = new src_1.AuthenticatorEmulator({
|
|
@@ -98,9 +118,16 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
98
118
|
const emulator = new src_1.default(authenticator);
|
|
99
119
|
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
100
120
|
const options = await testServer.getRegistrationOptions(user);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
121
|
+
const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
122
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
123
|
+
try {
|
|
124
|
+
call();
|
|
125
|
+
throw new Error("Expected DOMException");
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
129
|
+
(0, globals_1.expect)(e.name).toBe("NotAllowedError");
|
|
130
|
+
}
|
|
104
131
|
});
|
|
105
132
|
(0, globals_1.test)("Create Passkeys without RP ID _ OK", async () => {
|
|
106
133
|
const emulator = new src_1.default();
|
|
@@ -168,25 +195,38 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
168
195
|
const [emulator, testServer] = await createCredential();
|
|
169
196
|
const illegalOrigin = `${webauthn_test_server_1.TEST_RP_ORIGIN}_illegal`;
|
|
170
197
|
const options = await testServer.getAuthenticationOptions();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
198
|
+
const call = () => emulator.getJSON(illegalOrigin, options);
|
|
199
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
200
|
+
try {
|
|
201
|
+
call();
|
|
202
|
+
throw new Error("Expected DOMException");
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
206
|
+
(0, globals_1.expect)(e.name).toBe("SecurityError");
|
|
207
|
+
}
|
|
174
208
|
});
|
|
175
209
|
(0, globals_1.test)("Request Passkeys with no public key credential parameters _ failed to response", async () => {
|
|
176
210
|
const emulator = new src_1.default();
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}).rejects.toThrow(src_1.NoPublicKeyError);
|
|
211
|
+
const call = () => emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, {});
|
|
212
|
+
(0, globals_1.expect)(call).toThrow(TypeError);
|
|
180
213
|
});
|
|
181
214
|
(0, globals_1.test)("Not found in allow list _ CTAP Error", async () => {
|
|
182
215
|
const [emulator, testServer] = await createCredential();
|
|
183
216
|
const options = await testServer.getAuthenticationOptions();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
217
|
+
const call = () => emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, {
|
|
218
|
+
...options,
|
|
219
|
+
allowCredentials: [{ id: "AAAAAA", type: "public-key" }],
|
|
220
|
+
});
|
|
221
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
222
|
+
try {
|
|
223
|
+
call();
|
|
224
|
+
throw new Error("Expected DOMException");
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
228
|
+
(0, globals_1.expect)(e.name).toBe("NotAllowedError");
|
|
229
|
+
}
|
|
190
230
|
});
|
|
191
231
|
(0, globals_1.test)("User authentication operation failed _ CTAP Error", async () => {
|
|
192
232
|
const authenticator = new src_1.AuthenticatorEmulator({
|
|
@@ -194,9 +234,16 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
194
234
|
});
|
|
195
235
|
const [emulator, testServer] = await createCredential(authenticator);
|
|
196
236
|
const options = await testServer.getAuthenticationOptions();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
const call = () => emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
238
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
239
|
+
try {
|
|
240
|
+
call();
|
|
241
|
+
throw new Error("Expected DOMException");
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
245
|
+
(0, globals_1.expect)(e.name).toBe("NotAllowedError");
|
|
246
|
+
}
|
|
200
247
|
});
|
|
201
248
|
(0, globals_1.test)("Only one credential is allowed and RP ID is undefined _ credential is undefined and OK", async () => {
|
|
202
249
|
const [emulator, testServer] = await createCredential();
|
|
@@ -351,9 +398,16 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
351
398
|
// Create authentication options
|
|
352
399
|
const authOptions = await testServer.getAuthenticationOptions();
|
|
353
400
|
// Standard RP ID validation only
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
401
|
+
const invalidCall = () => emulator.getJSON(testServer.origin, authOptions);
|
|
402
|
+
(0, globals_1.expect)(invalidCall).toThrow(DOMException);
|
|
403
|
+
try {
|
|
404
|
+
invalidCall();
|
|
405
|
+
throw new Error("Expected DOMException");
|
|
406
|
+
}
|
|
407
|
+
catch (e) {
|
|
408
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
409
|
+
(0, globals_1.expect)(e.name).toBe("SecurityError");
|
|
410
|
+
}
|
|
357
411
|
// Related origins validation
|
|
358
412
|
const authResponse = emulator.getJSON(testServer.origin, authOptions, relatedOrigins);
|
|
359
413
|
(0, globals_1.expect)(authResponse).toBeDefined();
|
|
@@ -419,3 +473,253 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
419
473
|
}).toThrow(); // Should throw CTAP2_ERR_NO_CREDENTIALS
|
|
420
474
|
});
|
|
421
475
|
});
|
|
476
|
+
(0, globals_1.describe)("WebAuthnEmulator getClientExtensionResults.rk coverage", () => {
|
|
477
|
+
(0, globals_1.test)("rk=true when discoverable credential (user present)", async () => {
|
|
478
|
+
const emulator = new src_1.default(new src_1.AuthenticatorEmulator());
|
|
479
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
480
|
+
const user = { username: "ext-user", id: "ext-user" };
|
|
481
|
+
const regOptions = await testServer.getRegistrationOptions(user);
|
|
482
|
+
const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, regOptions);
|
|
483
|
+
await testServer.getRegistrationVerification(user, regResp);
|
|
484
|
+
const reqOptions = await testServer.getAuthenticationOptions();
|
|
485
|
+
const parsed = (0, src_1.parseRequestOptionsFromJSON)(reqOptions);
|
|
486
|
+
const cred = emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, { publicKey: parsed });
|
|
487
|
+
const ext = cred.getClientExtensionResults();
|
|
488
|
+
(0, globals_1.expect)("credProps" in ext && ext.credProps).toBeTruthy();
|
|
489
|
+
if ("credProps" in ext && ext.credProps) {
|
|
490
|
+
(0, globals_1.expect)(ext.credProps.rk).toBe(true);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
(0, globals_1.test)("rk=false with stateless + single allowCredential (no user)", async () => {
|
|
494
|
+
const emulator = new src_1.default(new src_1.AuthenticatorEmulator({ stateless: true }));
|
|
495
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
496
|
+
const user = { username: "ext-user2", id: "ext-user2" };
|
|
497
|
+
const regOptions = await testServer.getRegistrationOptions(user);
|
|
498
|
+
const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, regOptions);
|
|
499
|
+
await testServer.getRegistrationVerification(user, regResp);
|
|
500
|
+
const reqOptions = await testServer.getAuthenticationOptions();
|
|
501
|
+
const singleAllow = {
|
|
502
|
+
...reqOptions,
|
|
503
|
+
allowCredentials: [{ type: "public-key", id: regResp.id }],
|
|
504
|
+
};
|
|
505
|
+
const parsed = (0, src_1.parseRequestOptionsFromJSON)(singleAllow);
|
|
506
|
+
const cred = emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, { publicKey: parsed });
|
|
507
|
+
const ext = cred.getClientExtensionResults();
|
|
508
|
+
(0, globals_1.expect)("credProps" in ext && ext.credProps).toBeTruthy();
|
|
509
|
+
if ("credProps" in ext && ext.credProps) {
|
|
510
|
+
(0, globals_1.expect)(ext.credProps.rk).toBe(false);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
(0, globals_1.test)("signalAllAcceptedCredentials skips credentials when userId mismatches", async () => {
|
|
514
|
+
// repository-backed authenticator with isolated repository
|
|
515
|
+
const emulator = new src_1.default(new src_1.AuthenticatorEmulator({ credentialsRepository: new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository() }));
|
|
516
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
517
|
+
const user = { username: "ext-user3", id: "ext-user3" };
|
|
518
|
+
const regOptions = await testServer.getRegistrationOptions(user);
|
|
519
|
+
const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, regOptions);
|
|
520
|
+
const repository = emulator.authenticator.params.credentialsRepository;
|
|
521
|
+
await testServer.getRegistrationVerification(user, regResp);
|
|
522
|
+
// credentials exist
|
|
523
|
+
const before = repository.loadCredentials();
|
|
524
|
+
(0, globals_1.expect)(before.length).toBe(1);
|
|
525
|
+
const existingId = before.map((c) => encode_utils_1.default.encodeBase64Url(c.publicKeyCredentialSource.id))[0];
|
|
526
|
+
const actualUserId = encode_utils_1.default.encodeBase64Url(before[0].user.id);
|
|
527
|
+
// ensure mismatch by altering actualUserId
|
|
528
|
+
const wrongUserId = `${actualUserId}-mismatch`;
|
|
529
|
+
emulator.signalAllAcceptedCredentials({
|
|
530
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
531
|
+
userId: wrongUserId,
|
|
532
|
+
allAcceptedCredentialIds: [], // even though empty, mismatch should skip deletion
|
|
533
|
+
});
|
|
534
|
+
const after = repository.loadCredentials();
|
|
535
|
+
const remainingIds = after.map((c) => encode_utils_1.default.encodeBase64Url(c.publicKeyCredentialSource.id));
|
|
536
|
+
(0, globals_1.expect)(remainingIds).toContain(existingId); // original credential remains
|
|
537
|
+
(0, globals_1.expect)(after.length).toBe(1); // unchanged
|
|
538
|
+
});
|
|
539
|
+
(0, globals_1.test)("signalAllAcceptedCredentials handles undefined totalCredentials (defaults to 0)", async () => {
|
|
540
|
+
class BeginNoTotalAuthenticator extends src_1.AuthenticatorEmulator {
|
|
541
|
+
command(request) {
|
|
542
|
+
if (request.command === src_1.CTAP_COMMAND.authenticatorCredentialManagement) {
|
|
543
|
+
return (0, src_1.packCredentialManagementResponse)({});
|
|
544
|
+
}
|
|
545
|
+
return super.command(request);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const emulator = new src_1.default(new BeginNoTotalAuthenticator());
|
|
549
|
+
(0, globals_1.expect)(() => emulator.signalAllAcceptedCredentials({
|
|
550
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
551
|
+
userId: "any-user",
|
|
552
|
+
allAcceptedCredentialIds: [],
|
|
553
|
+
})).not.toThrow();
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
(0, globals_1.describe)("WebAuthnEmulator DOMException mapping coverage", () => {
|
|
557
|
+
(0, globals_1.test)("signalCurrentUserDetails with invalid parameter → DataError", () => {
|
|
558
|
+
const emulator = new src_1.default(new src_1.AuthenticatorEmulator());
|
|
559
|
+
const userId = encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array("uid"));
|
|
560
|
+
const call = () => emulator.signalCurrentUserDetails({
|
|
561
|
+
// Force rpId to be undefined to trigger CTAP1_ERR_INVALID_PARAMETER inside authenticator
|
|
562
|
+
rpId: undefined,
|
|
563
|
+
userId,
|
|
564
|
+
name: "n",
|
|
565
|
+
displayName: "d",
|
|
566
|
+
});
|
|
567
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
568
|
+
try {
|
|
569
|
+
call();
|
|
570
|
+
throw new Error("Expected DOMException");
|
|
571
|
+
}
|
|
572
|
+
catch (e) {
|
|
573
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
574
|
+
(0, globals_1.expect)(e.name).toBe("DataError");
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
(0, globals_1.test)("getAuthenticatorInfo with invalid CBOR → DataError", () => {
|
|
578
|
+
class BadCborAuthenticator extends src_1.AuthenticatorEmulator {
|
|
579
|
+
command() {
|
|
580
|
+
return { status: src_1.CTAP_STATUS_CODE.CTAP2_OK, data: new Uint8Array([0xff]) };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const emulator = new src_1.default(new BadCborAuthenticator());
|
|
584
|
+
const call = () => emulator.getAuthenticatorInfo();
|
|
585
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
586
|
+
try {
|
|
587
|
+
call();
|
|
588
|
+
throw new Error("Expected DOMException");
|
|
589
|
+
}
|
|
590
|
+
catch (e) {
|
|
591
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
592
|
+
(0, globals_1.expect)(e.name).toBe("DataError");
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
(0, globals_1.test)("getAuthenticatorInfo with invalid command → UnknownError", () => {
|
|
596
|
+
class InvalidCommandAuthenticator extends src_1.AuthenticatorEmulator {
|
|
597
|
+
command() {
|
|
598
|
+
throw new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const emulator = new src_1.default(new InvalidCommandAuthenticator());
|
|
602
|
+
const call = () => emulator.getAuthenticatorInfo();
|
|
603
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
604
|
+
try {
|
|
605
|
+
call();
|
|
606
|
+
throw new Error("Expected DOMException");
|
|
607
|
+
}
|
|
608
|
+
catch (e) {
|
|
609
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
610
|
+
(0, globals_1.expect)(e.name).toBe("UnknownError");
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
(0, globals_1.describe)("WebAuthnEmulator credential management error mapping", () => {
|
|
615
|
+
(0, globals_1.test)("signalUnknownCredential on stateless authenticator → NotAllowedError", () => {
|
|
616
|
+
const emulator = new src_1.default(new src_1.AuthenticatorEmulator({ stateless: true }));
|
|
617
|
+
const call = () => emulator.signalUnknownCredential({
|
|
618
|
+
rpId: "example.com",
|
|
619
|
+
credentialId: encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array("nope")),
|
|
620
|
+
});
|
|
621
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
622
|
+
try {
|
|
623
|
+
call();
|
|
624
|
+
throw new Error("Expected DOMException");
|
|
625
|
+
}
|
|
626
|
+
catch (e) {
|
|
627
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
628
|
+
(0, globals_1.expect)(e.name).toBe("NotAllowedError");
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
(0, globals_1.test)("signalUnknownCredential for non-existent credential → NotAllowedError", async () => {
|
|
632
|
+
const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
633
|
+
const authenticator = new src_1.AuthenticatorEmulator({ credentialsRepository: repository });
|
|
634
|
+
const emulator = new src_1.default(authenticator);
|
|
635
|
+
const call = () => emulator.signalUnknownCredential({
|
|
636
|
+
rpId: "example.com",
|
|
637
|
+
credentialId: encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array("missing")),
|
|
638
|
+
});
|
|
639
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
640
|
+
try {
|
|
641
|
+
call();
|
|
642
|
+
throw new Error("Expected DOMException");
|
|
643
|
+
}
|
|
644
|
+
catch (e) {
|
|
645
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
646
|
+
(0, globals_1.expect)(e.name).toBe("NotAllowedError");
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
(0, globals_1.describe)("WebAuthnEmulator invalid CBOR mapping on create/get", () => {
|
|
651
|
+
(0, globals_1.test)("create path invalid CBOR → DataError", () => {
|
|
652
|
+
class BadCreateCborAuthenticator extends src_1.AuthenticatorEmulator {
|
|
653
|
+
command(request) {
|
|
654
|
+
if (request.command === src_1.CTAP_COMMAND.authenticatorMakeCredential) {
|
|
655
|
+
return { status: src_1.CTAP_STATUS_CODE.CTAP2_OK, data: new Uint8Array([0xff]) };
|
|
656
|
+
}
|
|
657
|
+
return super.command(request);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const emulator = new src_1.default(new BadCreateCborAuthenticator());
|
|
661
|
+
const user = { username: "bad-cbor", id: "bad-cbor" };
|
|
662
|
+
const server = new webauthn_test_server_1.WebAuthnTestServer();
|
|
663
|
+
return server.getRegistrationOptions(user).then((opts) => {
|
|
664
|
+
const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, opts);
|
|
665
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
666
|
+
try {
|
|
667
|
+
call();
|
|
668
|
+
throw new Error("Expected DOMException");
|
|
669
|
+
}
|
|
670
|
+
catch (e) {
|
|
671
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
672
|
+
(0, globals_1.expect)(e.name).toBe("DataError");
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
(0, globals_1.test)("get path invalid CBOR → DataError", () => {
|
|
677
|
+
class BadGetCborAuthenticator extends src_1.AuthenticatorEmulator {
|
|
678
|
+
command(request) {
|
|
679
|
+
if (request.command === src_1.CTAP_COMMAND.authenticatorGetAssertion) {
|
|
680
|
+
return { status: src_1.CTAP_STATUS_CODE.CTAP2_OK, data: new Uint8Array([0xff]) };
|
|
681
|
+
}
|
|
682
|
+
return super.command(request);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const emulator = new src_1.default(new BadGetCborAuthenticator());
|
|
686
|
+
const server = new webauthn_test_server_1.WebAuthnTestServer();
|
|
687
|
+
const user = { username: "bad-cbor2", id: "bad-cbor2" };
|
|
688
|
+
return server.getRegistrationOptions(user).then((reg) => {
|
|
689
|
+
const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, reg);
|
|
690
|
+
return server.getRegistrationVerification(user, regResp).then(async () => {
|
|
691
|
+
const req = await server.getAuthenticationOptions();
|
|
692
|
+
const call = () => emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, req);
|
|
693
|
+
(0, globals_1.expect)(call).toThrow(DOMException);
|
|
694
|
+
try {
|
|
695
|
+
call();
|
|
696
|
+
throw new Error("Expected DOMException");
|
|
697
|
+
}
|
|
698
|
+
catch (e) {
|
|
699
|
+
(0, globals_1.expect)(e).toBeInstanceOf(DOMException);
|
|
700
|
+
(0, globals_1.expect)(e.name).toBe("DataError");
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
(0, globals_1.describe)("handleAuthenticatorCall other error path", () => {
|
|
707
|
+
(0, globals_1.test)("getAuthenticatorInfo rethrows non-CTAP errors as-is", () => {
|
|
708
|
+
class ThrowingAuthenticator extends src_1.AuthenticatorEmulator {
|
|
709
|
+
command() {
|
|
710
|
+
throw new Error("Non-CTAP failure");
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const emulator = new src_1.default(new ThrowingAuthenticator());
|
|
714
|
+
try {
|
|
715
|
+
emulator.getAuthenticatorInfo();
|
|
716
|
+
throw new Error("Expected Error to be thrown");
|
|
717
|
+
}
|
|
718
|
+
catch (e) {
|
|
719
|
+
// Should not be mapped to DOMException; should be the original error
|
|
720
|
+
(0, globals_1.expect)(e).not.toBeInstanceOf(DOMException);
|
|
721
|
+
(0, globals_1.expect)(e).toBeInstanceOf(Error);
|
|
722
|
+
(0, globals_1.expect)(e.message).toBe("Non-CTAP failure");
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
});
|
|
@@ -19,7 +19,7 @@ export type PasskeyCredential = {
|
|
|
19
19
|
readonly user: PublicKeyCredentialUserEntity | undefined;
|
|
20
20
|
};
|
|
21
21
|
export type AuthenticatorParameters = {
|
|
22
|
-
readonly aaguid: Uint8Array
|
|
22
|
+
readonly aaguid: Uint8Array<ArrayBuffer>;
|
|
23
23
|
readonly transports: AuthenticatorTransport[];
|
|
24
24
|
readonly algorithmIdentifiers: readonly (keyof typeof COSEAlgorithmIdentifier)[];
|
|
25
25
|
readonly signCounterIncrement: number;
|
|
@@ -353,7 +353,7 @@ function getAssertion(rpIdHash, clientDataHash, newSignCounter, credential, inte
|
|
|
353
353
|
type: "pkcs8",
|
|
354
354
|
key: credential.privateKey,
|
|
355
355
|
});
|
|
356
|
-
const signature = encode_utils_1.default.bufferSourceToUint8Array((0, node_crypto_1.sign)(null, new Uint8Array(payload), privateKey));
|
|
356
|
+
const signature = encode_utils_1.default.bufferSourceToUint8Array(new Uint8Array((0, node_crypto_1.sign)(null, new Uint8Array(payload), privateKey)));
|
|
357
357
|
return { authData: (0, webauthn_model_1.packAuthenticatorData)(authenticatorData), signature };
|
|
358
358
|
}
|
|
359
359
|
function makeCredential(aaguid, rpId, alg, transports, interactionResponse, user, statelessKey) {
|
|
@@ -9,7 +9,7 @@ export type AuthenticatorInteractionOptions = {
|
|
|
9
9
|
};
|
|
10
10
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential */
|
|
11
11
|
export interface AuthenticatorMakeCredentialRequest {
|
|
12
|
-
clientDataHash: Uint8Array
|
|
12
|
+
clientDataHash: Uint8Array<ArrayBuffer>;
|
|
13
13
|
rp: PublicKeyCredentialRpEntity & {
|
|
14
14
|
id: string;
|
|
15
15
|
};
|
|
@@ -18,30 +18,30 @@ export interface AuthenticatorMakeCredentialRequest {
|
|
|
18
18
|
excludeList?: PublicKeyCredentialDescriptor[];
|
|
19
19
|
extensions?: object;
|
|
20
20
|
options?: Partial<AuthenticatorOptions>;
|
|
21
|
-
pinAuth?: Uint8Array
|
|
21
|
+
pinAuth?: Uint8Array<ArrayBuffer>;
|
|
22
22
|
pinProtocol?: number;
|
|
23
23
|
}
|
|
24
24
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential */
|
|
25
25
|
export interface AuthenticatorMakeCredentialResponse {
|
|
26
26
|
fmt: string;
|
|
27
|
-
authData: Uint8Array
|
|
27
|
+
authData: Uint8Array<ArrayBuffer>;
|
|
28
28
|
attStmt: object;
|
|
29
29
|
}
|
|
30
30
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion */
|
|
31
31
|
export interface AuthenticatorGetAssertionRequest {
|
|
32
32
|
rpId: string;
|
|
33
|
-
clientDataHash: Uint8Array
|
|
33
|
+
clientDataHash: Uint8Array<ArrayBuffer>;
|
|
34
34
|
allowList?: PublicKeyCredentialDescriptor[];
|
|
35
35
|
extensions?: object;
|
|
36
36
|
options?: Partial<AuthenticatorOptions>;
|
|
37
|
-
pinAuth?: Uint8Array
|
|
37
|
+
pinAuth?: Uint8Array<ArrayBuffer>;
|
|
38
38
|
pinProtocol?: number;
|
|
39
39
|
}
|
|
40
40
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion */
|
|
41
41
|
export interface AuthenticatorGetAssertionResponse {
|
|
42
42
|
credential?: PublicKeyCredentialDescriptor;
|
|
43
|
-
authData: Uint8Array
|
|
44
|
-
signature: Uint8Array
|
|
43
|
+
authData: Uint8Array<ArrayBuffer>;
|
|
44
|
+
signature: Uint8Array<ArrayBuffer>;
|
|
45
45
|
user?: PublicKeyCredentialUserEntity;
|
|
46
46
|
numberOfCredentials: number;
|
|
47
47
|
}
|
|
@@ -49,7 +49,7 @@ export interface AuthenticatorGetAssertionResponse {
|
|
|
49
49
|
export type AuthenticatorGetInfoResponse = {
|
|
50
50
|
versions: string[];
|
|
51
51
|
extensions?: string[];
|
|
52
|
-
aaguid: Uint8Array
|
|
52
|
+
aaguid: Uint8Array<ArrayBuffer>;
|
|
53
53
|
options?: Partial<AuthenticatorOptions> & {
|
|
54
54
|
plat?: boolean;
|
|
55
55
|
clientPin?: boolean;
|
|
@@ -91,35 +91,35 @@ export declare enum CREDENTIAL_MANAGEMENT_SUBCOMMAND {
|
|
|
91
91
|
export interface AuthenticatorCredentialManagementRequest {
|
|
92
92
|
subCommand: CREDENTIAL_MANAGEMENT_SUBCOMMAND;
|
|
93
93
|
subCommandParams?: {
|
|
94
|
-
credentialId?: Uint8Array
|
|
94
|
+
credentialId?: Uint8Array<ArrayBuffer>;
|
|
95
95
|
rpId?: string;
|
|
96
96
|
user?: PublicKeyCredentialUserEntity;
|
|
97
97
|
};
|
|
98
98
|
pinUvAuthProtocol?: number;
|
|
99
|
-
pinUvAuthParam?: Uint8Array
|
|
99
|
+
pinUvAuthParam?: Uint8Array<ArrayBuffer>;
|
|
100
100
|
}
|
|
101
101
|
export interface AuthenticatorCredentialManagementResponse {
|
|
102
102
|
existingResidentCredentialsCount?: number;
|
|
103
103
|
maxPossibleRemainingResidentCredentialsCount?: number;
|
|
104
104
|
rp?: PublicKeyCredentialRpEntity;
|
|
105
|
-
rpIDHash?: Uint8Array
|
|
105
|
+
rpIDHash?: Uint8Array<ArrayBuffer>;
|
|
106
106
|
totalRPs?: number;
|
|
107
107
|
user?: PublicKeyCredentialUserEntity;
|
|
108
|
-
credentialID?: Uint8Array
|
|
109
|
-
publicKey?: Uint8Array
|
|
108
|
+
credentialID?: Uint8Array<ArrayBuffer>;
|
|
109
|
+
publicKey?: Uint8Array<ArrayBuffer>;
|
|
110
110
|
totalCredentials?: number;
|
|
111
111
|
credProtect?: number;
|
|
112
|
-
largeBlobKey?: Uint8Array
|
|
112
|
+
largeBlobKey?: Uint8Array<ArrayBuffer>;
|
|
113
113
|
}
|
|
114
114
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#responses */
|
|
115
115
|
export interface CTAPAuthenticatorResponse {
|
|
116
116
|
status: CTAP_STATUS_CODE;
|
|
117
|
-
data?: Uint8Array
|
|
117
|
+
data?: Uint8Array<ArrayBuffer>;
|
|
118
118
|
}
|
|
119
119
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands */
|
|
120
120
|
export interface CTAPAuthenticatorRequest {
|
|
121
121
|
command: CTAP_COMMAND;
|
|
122
|
-
data?: Uint8Array
|
|
122
|
+
data?: Uint8Array<ArrayBuffer>;
|
|
123
123
|
}
|
|
124
124
|
export declare class AuthenticationEmulatorError extends Error {
|
|
125
125
|
status: CTAP_STATUS_CODE;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
declare function encodeBase64Url(data: BufferSource): string;
|
|
2
|
-
declare function decodeBase64Url(base64Url: string): Uint8Array
|
|
3
|
-
declare function bufferSourceToUint8Array(data: BufferSource): Uint8Array
|
|
4
|
-
declare function strToUint8Array(data: string): Uint8Array
|
|
5
|
-
declare function encodeCbor(data: object): Uint8Array
|
|
6
|
-
declare function decodeCbor<T>(data: Uint8Array): T;
|
|
2
|
+
declare function decodeBase64Url(base64Url: string): Uint8Array<ArrayBuffer>;
|
|
3
|
+
declare function bufferSourceToUint8Array(data: BufferSource): Uint8Array<ArrayBuffer>;
|
|
4
|
+
declare function strToUint8Array(data: string): Uint8Array<ArrayBuffer>;
|
|
5
|
+
declare function encodeCbor(data: object): Uint8Array<ArrayBuffer>;
|
|
6
|
+
declare function decodeCbor<T>(data: Uint8Array<ArrayBuffer>): T;
|
|
7
7
|
declare const EncodeUtils: {
|
|
8
8
|
strToUint8Array: typeof strToUint8Array;
|
|
9
9
|
bufferSourceToUint8Array: typeof bufferSourceToUint8Array;
|
|
@@ -31,12 +31,15 @@ function strToUint8Array(data) {
|
|
|
31
31
|
}
|
|
32
32
|
function encodeCbor(data) {
|
|
33
33
|
function encoder(value) {
|
|
34
|
-
if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
35
|
-
return Buffer.from(value);
|
|
36
|
-
}
|
|
37
34
|
if (value instanceof Buffer) {
|
|
38
35
|
return value;
|
|
39
36
|
}
|
|
37
|
+
if (value instanceof Uint8Array) {
|
|
38
|
+
return Buffer.from(value);
|
|
39
|
+
}
|
|
40
|
+
if (value instanceof ArrayBuffer) {
|
|
41
|
+
return Buffer.from(value);
|
|
42
|
+
}
|
|
40
43
|
if (Array.isArray(value)) {
|
|
41
44
|
return value.map((v) => encoder(v));
|
|
42
45
|
}
|
|
@@ -4,13 +4,13 @@ export declare abstract class CoseKey {
|
|
|
4
4
|
kty: number;
|
|
5
5
|
alg: number;
|
|
6
6
|
constructor(kty: number, alg: number);
|
|
7
|
-
abstract toBytes(): Uint8Array
|
|
7
|
+
abstract toBytes(): Uint8Array<ArrayBuffer>;
|
|
8
8
|
abstract toJwk(): JsonWebKey;
|
|
9
9
|
toKeyObject(): KeyObject;
|
|
10
10
|
static fromKeyObject(keyObject: KeyObject): CoseKey;
|
|
11
|
-
toDer(): Uint8Array
|
|
12
|
-
static fromDer(der: Uint8Array): CoseKey;
|
|
11
|
+
toDer(): Uint8Array<ArrayBuffer>;
|
|
12
|
+
static fromDer(der: Uint8Array<ArrayBuffer>): CoseKey;
|
|
13
13
|
equals(other: CoseKey): boolean;
|
|
14
|
-
static fromBytes(bytes: Uint8Array): CoseKey;
|
|
14
|
+
static fromBytes(bytes: Uint8Array<ArrayBuffer>): CoseKey;
|
|
15
15
|
static fromJwk(jwk: JsonWebKey): CoseKey;
|
|
16
16
|
}
|
|
@@ -21,7 +21,7 @@ class CoseKey {
|
|
|
21
21
|
return CoseKey.fromJwk(keyObject.export({ format: "jwk" }));
|
|
22
22
|
}
|
|
23
23
|
toDer() {
|
|
24
|
-
return this.toKeyObject().export({ format: "der", type: "spki" });
|
|
24
|
+
return new Uint8Array(this.toKeyObject().export({ format: "der", type: "spki" }));
|
|
25
25
|
}
|
|
26
26
|
static fromDer(der) {
|
|
27
27
|
return CoseKey.fromKeyObject(node_crypto_1.default.createPublicKey({ format: "der", type: "spki", key: Buffer.from(der) }));
|
|
@@ -10,16 +10,14 @@ export type AuthenticatorInfo = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare class WebAuthnEmulatorError extends Error {
|
|
12
12
|
}
|
|
13
|
-
export declare class NoPublicKeyError extends WebAuthnEmulatorError {
|
|
14
|
-
}
|
|
15
|
-
export declare class InvalidRpIdError extends WebAuthnEmulatorError {
|
|
16
|
-
}
|
|
17
13
|
/**
|
|
18
14
|
* WebAuthn emulator
|
|
19
15
|
*/
|
|
20
16
|
export declare class WebAuthnEmulator {
|
|
21
17
|
authenticator: AuthenticatorEmulator;
|
|
22
18
|
constructor(authenticator?: AuthenticatorEmulator);
|
|
19
|
+
private mapAuthenticatorErrorToDOMException;
|
|
20
|
+
private handleAuthenticatorCall;
|
|
23
21
|
getJSON(origin: string, optionsJSON: PublicKeyCredentialRequestOptionsJSON, relatedOrigins?: string[]): AuthenticationResponseJSON;
|
|
24
22
|
createJSON(origin: string, optionsJSON: PublicKeyCredentialCreationOptionsJSON, relatedOrigins?: string[]): RegistrationResponseJSON;
|
|
25
23
|
getAuthenticatorInfo(): AuthenticatorInfo;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.WebAuthnEmulator = exports.
|
|
6
|
+
exports.WebAuthnEmulator = exports.WebAuthnEmulatorError = void 0;
|
|
7
7
|
const node_crypto_1 = require("node:crypto");
|
|
8
8
|
const authenticator_emulator_1 = require("../authenticator/authenticator-emulator");
|
|
9
9
|
const ctap_model_1 = require("../authenticator/ctap-model");
|
|
@@ -13,12 +13,6 @@ const webauthn_model_json_1 = require("./webauthn-model-json");
|
|
|
13
13
|
class WebAuthnEmulatorError extends Error {
|
|
14
14
|
}
|
|
15
15
|
exports.WebAuthnEmulatorError = WebAuthnEmulatorError;
|
|
16
|
-
class NoPublicKeyError extends WebAuthnEmulatorError {
|
|
17
|
-
}
|
|
18
|
-
exports.NoPublicKeyError = NoPublicKeyError;
|
|
19
|
-
class InvalidRpIdError extends WebAuthnEmulatorError {
|
|
20
|
-
}
|
|
21
|
-
exports.InvalidRpIdError = InvalidRpIdError;
|
|
22
16
|
/**
|
|
23
17
|
* WebAuthn emulator
|
|
24
18
|
*/
|
|
@@ -27,6 +21,41 @@ class WebAuthnEmulator {
|
|
|
27
21
|
constructor(authenticator = new authenticator_emulator_1.AuthenticatorEmulator()) {
|
|
28
22
|
this.authenticator = authenticator;
|
|
29
23
|
}
|
|
24
|
+
mapAuthenticatorErrorToDOMException(error) {
|
|
25
|
+
let name;
|
|
26
|
+
switch (error.status) {
|
|
27
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_CREDENTIAL_EXCLUDED:
|
|
28
|
+
name = "NotAllowedError";
|
|
29
|
+
break;
|
|
30
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_UNSUPPORTED_ALGORITHM:
|
|
31
|
+
name = "NotSupportedError";
|
|
32
|
+
break;
|
|
33
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_OPERATION_DENIED:
|
|
34
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED:
|
|
35
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS:
|
|
36
|
+
name = "NotAllowedError";
|
|
37
|
+
break;
|
|
38
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER:
|
|
39
|
+
case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_INVALID_CBOR:
|
|
40
|
+
name = "DataError";
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
name = "UnknownError";
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
return new DOMException(error.message, name);
|
|
47
|
+
}
|
|
48
|
+
handleAuthenticatorCall(fn) {
|
|
49
|
+
try {
|
|
50
|
+
return fn();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof ctap_model_1.AuthenticationEmulatorError) {
|
|
54
|
+
throw this.mapAuthenticatorErrorToDOMException(error);
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
30
59
|
getJSON(origin, optionsJSON, relatedOrigins = []) {
|
|
31
60
|
const options = (0, webauthn_model_json_1.parseRequestOptionsFromJSON)(optionsJSON);
|
|
32
61
|
const response = this.get(origin, { publicKey: options }, relatedOrigins);
|
|
@@ -38,7 +67,7 @@ class WebAuthnEmulator {
|
|
|
38
67
|
return response.toJSON();
|
|
39
68
|
}
|
|
40
69
|
getAuthenticatorInfo() {
|
|
41
|
-
const authenticatorInfo = (0, ctap_model_1.unpackGetInfoResponse)(this.authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorGetInfo }));
|
|
70
|
+
const authenticatorInfo = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackGetInfoResponse)(this.authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorGetInfo })));
|
|
42
71
|
return {
|
|
43
72
|
version: authenticatorInfo.versions.join(", "),
|
|
44
73
|
aaguid: encode_utils_1.default.encodeBase64Url(authenticatorInfo.aaguid),
|
|
@@ -50,13 +79,13 @@ class WebAuthnEmulator {
|
|
|
50
79
|
}
|
|
51
80
|
signalUnknownCredential(options) {
|
|
52
81
|
const credentialId = (0, webauthn_model_json_1.decodeBase64Url)(options.credentialId);
|
|
53
|
-
this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
82
|
+
this.handleAuthenticatorCall(() => this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
54
83
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
|
|
55
84
|
subCommandParams: {
|
|
56
85
|
credentialId: encode_utils_1.default.bufferSourceToUint8Array(credentialId),
|
|
57
86
|
rpId: options.rpId,
|
|
58
87
|
},
|
|
59
|
-
}));
|
|
88
|
+
})));
|
|
60
89
|
}
|
|
61
90
|
/**
|
|
62
91
|
* Signal all accepted credentials for a user
|
|
@@ -66,12 +95,12 @@ class WebAuthnEmulator {
|
|
|
66
95
|
// Create a set of accepted credential IDs for quick lookup
|
|
67
96
|
const acceptedCredentialIds = new Set(options.allAcceptedCredentialIds);
|
|
68
97
|
// Start enumerating credentials for the specified RP
|
|
69
|
-
const beginResponse = (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
98
|
+
const beginResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
70
99
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
71
100
|
subCommandParams: {
|
|
72
101
|
rpId: options.rpId,
|
|
73
102
|
},
|
|
74
|
-
})));
|
|
103
|
+
}))));
|
|
75
104
|
// Get the total number of credentials
|
|
76
105
|
const totalCredentials = beginResponse.totalCredentials ?? 0;
|
|
77
106
|
// Get all credentials for the RP and delete those not in the accepted list
|
|
@@ -84,9 +113,9 @@ class WebAuthnEmulator {
|
|
|
84
113
|
// Process each credential
|
|
85
114
|
for (let i = 0; i < totalCredentials; i++) {
|
|
86
115
|
// Get next credential and unpack the response
|
|
87
|
-
const credResponse = (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
116
|
+
const credResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
88
117
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
|
|
89
|
-
})));
|
|
118
|
+
}))));
|
|
90
119
|
// Skip if this is not for the user we're looking for
|
|
91
120
|
if (credResponse.user && encode_utils_1.default.encodeBase64Url(credResponse.user.id) !== userId) {
|
|
92
121
|
continue;
|
|
@@ -96,13 +125,13 @@ class WebAuthnEmulator {
|
|
|
96
125
|
const credentialId = encode_utils_1.default.encodeBase64Url(credResponse.credentialID);
|
|
97
126
|
if (!acceptedCredentialIds.has(credentialId)) {
|
|
98
127
|
// Delete the credential directly
|
|
99
|
-
this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
128
|
+
this.handleAuthenticatorCall(() => this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
100
129
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
|
|
101
130
|
subCommandParams: {
|
|
102
131
|
credentialId: credResponse.credentialID,
|
|
103
132
|
rpId: rpId,
|
|
104
133
|
},
|
|
105
|
-
}));
|
|
134
|
+
})));
|
|
106
135
|
}
|
|
107
136
|
}
|
|
108
137
|
}
|
|
@@ -120,21 +149,21 @@ class WebAuthnEmulator {
|
|
|
120
149
|
displayName: options.displayName,
|
|
121
150
|
};
|
|
122
151
|
// Update user information using the credential management API
|
|
123
|
-
this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
152
|
+
this.handleAuthenticatorCall(() => this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
124
153
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
125
154
|
subCommandParams: {
|
|
126
155
|
rpId: options.rpId,
|
|
127
156
|
user: user,
|
|
128
157
|
},
|
|
129
|
-
}));
|
|
158
|
+
})));
|
|
130
159
|
}
|
|
131
160
|
/** @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get */
|
|
132
161
|
get(origin, options, relatedOrigins = []) {
|
|
133
162
|
if (!options.publicKey)
|
|
134
|
-
throw new
|
|
163
|
+
throw new TypeError("PublicKeyCredentialRequestOptions are required");
|
|
135
164
|
const rpId = new webauthn_model_1.RpId(options.publicKey.rpId ?? new URL(origin).hostname);
|
|
136
165
|
if (!rpId.validate(origin, relatedOrigins))
|
|
137
|
-
throw new
|
|
166
|
+
throw new DOMException(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`, "SecurityError");
|
|
138
167
|
const clientData = {
|
|
139
168
|
type: "webauthn.get",
|
|
140
169
|
challenge: encode_utils_1.default.encodeBase64Url(options.publicKey.challenge),
|
|
@@ -143,24 +172,24 @@ class WebAuthnEmulator {
|
|
|
143
172
|
};
|
|
144
173
|
const authenticatorRequest = (0, ctap_model_1.packGetAssertionRequest)({
|
|
145
174
|
rpId: rpId.value,
|
|
146
|
-
clientDataHash: encode_utils_1.default.bufferSourceToUint8Array((0, node_crypto_1.createHash)("sha256").update(JSON.stringify(clientData)).digest()),
|
|
175
|
+
clientDataHash: encode_utils_1.default.bufferSourceToUint8Array(new Uint8Array((0, node_crypto_1.createHash)("sha256").update(JSON.stringify(clientData)).digest())),
|
|
147
176
|
allowList: options.publicKey.allowCredentials,
|
|
148
177
|
options: (0, webauthn_model_1.toFido2RequestOptions)(options.publicKey.userVerification),
|
|
149
178
|
});
|
|
150
|
-
const authenticatorResponse = (0, ctap_model_1.unpackGetAssertionResponse)(this.authenticator.command(authenticatorRequest));
|
|
179
|
+
const authenticatorResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackGetAssertionResponse)(this.authenticator.command(authenticatorRequest)));
|
|
151
180
|
const responseId = authenticatorResponse.credential?.id ?? options.publicKey.allowCredentials?.[0]?.id;
|
|
152
181
|
const authData = (0, webauthn_model_1.unpackAuthenticatorData)(authenticatorResponse.authData);
|
|
153
182
|
const rawId = encode_utils_1.default.bufferSourceToUint8Array(responseId);
|
|
154
183
|
const publicKeyCredential = {
|
|
155
184
|
id: encode_utils_1.default.encodeBase64Url(rawId),
|
|
156
185
|
type: "public-key",
|
|
157
|
-
rawId,
|
|
186
|
+
rawId: rawId.buffer,
|
|
158
187
|
response: {
|
|
159
|
-
clientDataJSON: encode_utils_1.default.strToUint8Array(JSON.stringify(clientData)),
|
|
160
|
-
authenticatorData: (0, webauthn_model_1.packAuthenticatorData)(authData),
|
|
161
|
-
signature: authenticatorResponse.signature,
|
|
188
|
+
clientDataJSON: encode_utils_1.default.strToUint8Array(JSON.stringify(clientData)).buffer,
|
|
189
|
+
authenticatorData: (0, webauthn_model_1.packAuthenticatorData)(authData).buffer,
|
|
190
|
+
signature: authenticatorResponse.signature.buffer,
|
|
162
191
|
userHandle: authenticatorResponse.user
|
|
163
|
-
? encode_utils_1.default.bufferSourceToUint8Array(authenticatorResponse.user.id)
|
|
192
|
+
? encode_utils_1.default.bufferSourceToUint8Array(authenticatorResponse.user.id).buffer
|
|
164
193
|
: null,
|
|
165
194
|
},
|
|
166
195
|
authenticatorAttachment: null,
|
|
@@ -172,10 +201,10 @@ class WebAuthnEmulator {
|
|
|
172
201
|
/** @see https://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create */
|
|
173
202
|
create(origin, options, relatedOrigins = []) {
|
|
174
203
|
if (!options.publicKey)
|
|
175
|
-
throw new
|
|
204
|
+
throw new TypeError("PublicKeyCredentialCreationOptions are required");
|
|
176
205
|
const rpId = new webauthn_model_1.RpId(options.publicKey.rp.id ?? new URL(origin).hostname);
|
|
177
206
|
if (!rpId.validate(origin, relatedOrigins))
|
|
178
|
-
throw new
|
|
207
|
+
throw new DOMException(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`, "SecurityError");
|
|
179
208
|
const clientData = {
|
|
180
209
|
challenge: encode_utils_1.default.encodeBase64Url(options.publicKey.challenge),
|
|
181
210
|
origin,
|
|
@@ -184,14 +213,14 @@ class WebAuthnEmulator {
|
|
|
184
213
|
};
|
|
185
214
|
const clientDataJSON = JSON.stringify(clientData);
|
|
186
215
|
const authenticatorRequest = (0, ctap_model_1.packMakeCredentialRequest)({
|
|
187
|
-
clientDataHash: encode_utils_1.default.bufferSourceToUint8Array((0, node_crypto_1.createHash)("sha256").update(clientDataJSON).digest()),
|
|
216
|
+
clientDataHash: encode_utils_1.default.bufferSourceToUint8Array(new Uint8Array((0, node_crypto_1.createHash)("sha256").update(clientDataJSON).digest())),
|
|
188
217
|
rp: { name: options.publicKey.rp.name, id: rpId.value },
|
|
189
218
|
user: options.publicKey.user,
|
|
190
219
|
pubKeyCredParams: options.publicKey.pubKeyCredParams,
|
|
191
220
|
excludeList: options.publicKey.excludeCredentials,
|
|
192
221
|
options: (0, webauthn_model_1.toFido2CreateOptions)(options.publicKey.authenticatorSelection),
|
|
193
222
|
});
|
|
194
|
-
const authenticatorResponse = (0, ctap_model_1.unpackMakeCredentialResponse)(this.authenticator.command(authenticatorRequest));
|
|
223
|
+
const authenticatorResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackMakeCredentialResponse)(this.authenticator.command(authenticatorRequest)));
|
|
195
224
|
const authData = (0, webauthn_model_1.unpackAuthenticatorData)(authenticatorResponse.authData);
|
|
196
225
|
const attestedCredentialData = authData.attestedCredentialData;
|
|
197
226
|
const attestationObject = {
|
|
@@ -200,17 +229,17 @@ class WebAuthnEmulator {
|
|
|
200
229
|
authData,
|
|
201
230
|
};
|
|
202
231
|
const response = {
|
|
203
|
-
attestationObject: (0, webauthn_model_1.packAttestationObject)(attestationObject),
|
|
204
|
-
clientDataJSON: encode_utils_1.default.strToUint8Array(clientDataJSON),
|
|
205
|
-
getAuthenticatorData: () => (0, webauthn_model_1.packAuthenticatorData)(authData),
|
|
206
|
-
getPublicKey: () => attestedCredentialData.credentialPublicKey.toDer(),
|
|
232
|
+
attestationObject: (0, webauthn_model_1.packAttestationObject)(attestationObject).buffer,
|
|
233
|
+
clientDataJSON: encode_utils_1.default.strToUint8Array(clientDataJSON).buffer,
|
|
234
|
+
getAuthenticatorData: () => (0, webauthn_model_1.packAuthenticatorData)(authData).buffer,
|
|
235
|
+
getPublicKey: () => attestedCredentialData.credentialPublicKey.toDer().buffer,
|
|
207
236
|
getPublicKeyAlgorithm: () => attestedCredentialData.credentialPublicKey.alg,
|
|
208
237
|
getTransports: () => this.authenticator.params.transports,
|
|
209
238
|
};
|
|
210
239
|
const publicKeyCredential = {
|
|
211
240
|
id: encode_utils_1.default.encodeBase64Url(attestedCredentialData.credentialId),
|
|
212
241
|
type: "public-key",
|
|
213
|
-
rawId: attestedCredentialData.credentialId,
|
|
242
|
+
rawId: attestedCredentialData.credentialId.buffer,
|
|
214
243
|
response,
|
|
215
244
|
authenticatorAttachment: null,
|
|
216
245
|
getClientExtensionResults: () => ({ credProps: { rk: true } }),
|
|
@@ -3,27 +3,27 @@ import { CoseKey } from "./cose-key";
|
|
|
3
3
|
export declare class RpId {
|
|
4
4
|
readonly value: string;
|
|
5
5
|
constructor(value: string);
|
|
6
|
-
get hash(): Uint8Array
|
|
6
|
+
get hash(): Uint8Array<ArrayBuffer>;
|
|
7
7
|
/** @see https://www.w3.org/TR/webauthn-3/#sctn-validating-origin */
|
|
8
8
|
validate(origin: string, relatedOrigins?: string[]): boolean;
|
|
9
9
|
}
|
|
10
10
|
/** @see https://www.w3.org/TR/webauthn-3/#public-key-credential-source */
|
|
11
11
|
export type PublicKeyCredentialSource = {
|
|
12
12
|
type: "public-key";
|
|
13
|
-
id: Uint8Array
|
|
14
|
-
privateKey: Uint8Array
|
|
13
|
+
id: Uint8Array<ArrayBuffer>;
|
|
14
|
+
privateKey: Uint8Array<ArrayBuffer>;
|
|
15
15
|
rpId: RpId;
|
|
16
|
-
userHandle?: Uint8Array
|
|
16
|
+
userHandle?: Uint8Array<ArrayBuffer>;
|
|
17
17
|
};
|
|
18
18
|
/** @see https://www.w3.org/TR/webauthn-3/#sctn-attested-credential-data */
|
|
19
19
|
export type AttestedCredentialData = {
|
|
20
|
-
aaguid: Uint8Array
|
|
21
|
-
credentialId: Uint8Array
|
|
20
|
+
aaguid: Uint8Array<ArrayBuffer>;
|
|
21
|
+
credentialId: Uint8Array<ArrayBuffer>;
|
|
22
22
|
credentialPublicKey: CoseKey;
|
|
23
23
|
};
|
|
24
24
|
/** @see https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data */
|
|
25
25
|
export type AuthenticatorData = {
|
|
26
|
-
rpIdHash: Uint8Array
|
|
26
|
+
rpIdHash: Uint8Array<ArrayBuffer>;
|
|
27
27
|
flags: {
|
|
28
28
|
userPresent?: boolean;
|
|
29
29
|
userVerified?: boolean;
|
|
@@ -49,10 +49,10 @@ export type CollectedClientData = {
|
|
|
49
49
|
origin: string;
|
|
50
50
|
crossOrigin: boolean;
|
|
51
51
|
};
|
|
52
|
-
export declare function packAttestationObject(attestationObject: AttestationObject): Uint8Array
|
|
53
|
-
export declare function packAuthenticatorData(authData: AuthenticatorData): Uint8Array
|
|
54
|
-
export declare function unpackAttestationObject(attestationObject: Uint8Array): AttestationObject;
|
|
55
|
-
export declare function unpackAuthenticatorData(authData: Uint8Array): AuthenticatorData;
|
|
52
|
+
export declare function packAttestationObject(attestationObject: AttestationObject): Uint8Array<ArrayBuffer>;
|
|
53
|
+
export declare function packAuthenticatorData(authData: AuthenticatorData): Uint8Array<ArrayBuffer>;
|
|
54
|
+
export declare function unpackAttestationObject(attestationObject: Uint8Array<ArrayBuffer>): AttestationObject;
|
|
55
|
+
export declare function unpackAuthenticatorData(authData: Uint8Array<ArrayBuffer>): AuthenticatorData;
|
|
56
56
|
export type PublicKeyCredentialSourceJSON = {
|
|
57
57
|
type: "public-key";
|
|
58
58
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nid-webauthn-emulator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"test",
|
|
6
6
|
"e2e",
|
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
"main": "dist/src/index.js",
|
|
16
16
|
"types": "dist/src/index.d.ts",
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@biomejs/biome": "^2.
|
|
19
|
-
"@jest/globals": "^30.0
|
|
20
|
-
"@simplewebauthn/server": "^13.1
|
|
18
|
+
"@biomejs/biome": "^2.2.4",
|
|
19
|
+
"@jest/globals": "^30.2.0",
|
|
20
|
+
"@simplewebauthn/server": "^13.2.1",
|
|
21
21
|
"@tsconfig/recommended": "^1.0.10",
|
|
22
|
-
"@types/node": "^24.2
|
|
23
|
-
"jest": "^30.0
|
|
24
|
-
"ts-jest": "^29.4.
|
|
22
|
+
"@types/node": "^24.5.2",
|
|
23
|
+
"jest": "^30.2.0",
|
|
24
|
+
"ts-jest": "^29.4.4",
|
|
25
25
|
"ts-node": "^10.9.2",
|
|
26
|
-
"typescript": "^5.
|
|
26
|
+
"typescript": "^5.9.2"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"cbor": "^10.0.
|
|
30
|
-
"tldts": "^7.0.
|
|
29
|
+
"cbor": "^10.0.11",
|
|
30
|
+
"tldts": "^7.0.16"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"check": "biome check .",
|