nid-webauthn-emulator 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/dist/spec/authenticator/authenticator-emulator.spec.d.ts +1 -0
  4. package/dist/spec/authenticator/authenticator-emulator.spec.js +18 -0
  5. package/dist/spec/authenticator/ctap-model.spec.d.ts +1 -0
  6. package/dist/spec/authenticator/ctap-model.spec.js +95 -0
  7. package/dist/spec/integration/integration.spec.d.ts +1 -0
  8. package/dist/spec/integration/integration.spec.js +32 -0
  9. package/dist/spec/integration/passkeys-api-client.d.ts +11 -0
  10. package/dist/spec/integration/passkeys-api-client.js +2 -0
  11. package/dist/spec/integration/webauthn-io.d.ts +32 -0
  12. package/dist/spec/integration/webauthn-io.js +100 -0
  13. package/dist/spec/libs/browser-injection.spec.d.ts +1 -0
  14. package/dist/spec/libs/browser-injection.spec.js +15 -0
  15. package/dist/spec/libs/cose-key.spec.d.ts +1 -0
  16. package/dist/spec/libs/cose-key.spec.js +47 -0
  17. package/dist/spec/repository/credential-repository.spec.d.ts +1 -0
  18. package/dist/spec/repository/credential-repository.spec.js +96 -0
  19. package/dist/spec/webauthn/cose-key.spec.d.ts +1 -0
  20. package/dist/spec/webauthn/cose-key.spec.js +74 -0
  21. package/dist/spec/webauthn/webauthn-emulator.spec.d.ts +1 -0
  22. package/dist/spec/webauthn/webauthn-emulator.spec.js +222 -0
  23. package/dist/spec/webauthn/webauthn-model-json.spec.d.ts +1 -0
  24. package/dist/spec/webauthn/webauthn-model-json.spec.js +103 -0
  25. package/dist/spec/webauthn/webauthn-model.spec.d.ts +1 -0
  26. package/dist/spec/webauthn/webauthn-model.spec.js +97 -0
  27. package/dist/spec/webauthn/webauthn-test-server.d.ts +15 -0
  28. package/dist/spec/webauthn/webauthn-test-server.js +87 -0
  29. package/dist/src/authenticator/authenticator-emulator.d.ts +68 -0
  30. package/dist/src/authenticator/authenticator-emulator.js +304 -0
  31. package/dist/src/authenticator/ctap-model.d.ts +107 -0
  32. package/dist/src/authenticator/ctap-model.js +202 -0
  33. package/dist/src/index.d.ts +16 -0
  34. package/dist/src/index.js +34 -0
  35. package/dist/src/libs/encode-utils.d.ts +15 -0
  36. package/dist/src/libs/encode-utils.js +92 -0
  37. package/dist/src/repository/credentials-file-repository.d.ts +8 -0
  38. package/dist/src/repository/credentials-file-repository.js +62 -0
  39. package/dist/src/repository/credentials-memory-repository.d.ts +7 -0
  40. package/dist/src/repository/credentials-memory-repository.js +20 -0
  41. package/dist/src/repository/credentials-repository.d.ts +39 -0
  42. package/dist/src/repository/credentials-repository.js +46 -0
  43. package/dist/src/test-utils/browser-injection.d.ts +3 -0
  44. package/dist/src/test-utils/browser-injection.js +52 -0
  45. package/dist/src/webauthn/cose-key.d.ts +16 -0
  46. package/dist/src/webauthn/cose-key.js +151 -0
  47. package/dist/src/webauthn/webauthn-emulator.d.ts +30 -0
  48. package/dist/src/webauthn/webauthn-emulator.js +142 -0
  49. package/dist/src/webauthn/webauthn-model-json.d.ts +101 -0
  50. package/dist/src/webauthn/webauthn-model-json.js +187 -0
  51. package/dist/src/webauthn/webauthn-model.d.ts +68 -0
  52. package/dist/src/webauthn/webauthn-model.js +186 -0
  53. package/package.json +37 -0
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebAuthnTestServer = exports.TEST_RP_ID = exports.TEST_RP_ORIGIN = void 0;
4
+ const server_1 = require("@simplewebauthn/server");
5
+ exports.TEST_RP_ORIGIN = "https://test-rp.com";
6
+ exports.TEST_RP_ID = "test-rp.com";
7
+ class WebAuthnTestServer {
8
+ origin;
9
+ rpID;
10
+ challenges = new Set();
11
+ credentials = [];
12
+ constructor(origin = exports.TEST_RP_ORIGIN, rpID = exports.TEST_RP_ID) {
13
+ this.origin = origin;
14
+ this.rpID = rpID;
15
+ }
16
+ async getRegistrationOptions(user) {
17
+ const options = await (0, server_1.generateRegistrationOptions)({
18
+ rpName: "Test RP",
19
+ rpID: this.rpID,
20
+ userName: user.username,
21
+ attestationType: "none",
22
+ excludeCredentials: this.credentials.map((cred) => ({
23
+ id: cred.id,
24
+ type: "public-key",
25
+ })),
26
+ authenticatorSelection: {
27
+ residentKey: "preferred",
28
+ userVerification: "preferred",
29
+ authenticatorAttachment: "platform",
30
+ },
31
+ });
32
+ options.user.id;
33
+ this.challenges.add(options.challenge);
34
+ return options;
35
+ }
36
+ async getRegistrationVerification(user, response) {
37
+ const verification = await (0, server_1.verifyRegistrationResponse)({
38
+ response,
39
+ expectedChallenge: (challenge) => this.challenges.has(challenge),
40
+ expectedOrigin: this.origin,
41
+ expectedRPID: this.rpID,
42
+ });
43
+ if (!verification.verified || !verification.registrationInfo)
44
+ throw new Error("Registration verification failed");
45
+ const registrationInfo = verification.registrationInfo;
46
+ this.credentials.push({
47
+ id: response.id,
48
+ publicKey: registrationInfo.credentialPublicKey,
49
+ user,
50
+ webauthnUserID: user.id,
51
+ counter: verification.registrationInfo.counter,
52
+ backedUp: false,
53
+ transports: undefined,
54
+ });
55
+ }
56
+ async getAuthenticationOptions() {
57
+ const options = await (0, server_1.generateAuthenticationOptions)({
58
+ rpID: this.rpID,
59
+ allowCredentials: this.credentials.map((cred) => ({
60
+ id: cred.id,
61
+ type: "public-key",
62
+ })),
63
+ });
64
+ this.challenges.add(options.challenge);
65
+ return options;
66
+ }
67
+ async getAuthenticationVerification(response) {
68
+ const credential = this.credentials.find((cred) => cred.id === response.id);
69
+ if (!credential)
70
+ throw new Error("Credential not found");
71
+ const verification = await (0, server_1.verifyAuthenticationResponse)({
72
+ response,
73
+ expectedChallenge: (challenge) => this.challenges.has(challenge),
74
+ expectedOrigin: this.origin,
75
+ expectedRPID: this.rpID,
76
+ authenticator: {
77
+ credentialID: credential.id,
78
+ credentialPublicKey: credential.publicKey,
79
+ counter: credential.counter,
80
+ transports: credential.transports,
81
+ },
82
+ });
83
+ if (!verification.verified)
84
+ throw new Error("Authentication verification failed");
85
+ }
86
+ }
87
+ exports.WebAuthnTestServer = WebAuthnTestServer;
@@ -0,0 +1,68 @@
1
+ import type { PasskeysCredentialsRepository } from "../repository/credentials-repository";
2
+ import { type AuthenticatorData, type PublicKeyCredentialSource } from "../webauthn/webauthn-model";
3
+ import { type AuthenticatorGetAssertionRequest, type AuthenticatorGetAssertionResponse, type AuthenticatorGetInfoResponse, type AuthenticatorMakeCredentialRequest, type AuthenticatorMakeCredentialResponse, type AuthenticatorOptions, type CTAPAuthenticatorRequest, type CTAPAuthenticatorResponse } from "./ctap-model";
4
+ type InteractionResponse = {
5
+ options: {
6
+ uv: boolean;
7
+ up: boolean;
8
+ };
9
+ };
10
+ export declare const COSEAlgorithmIdentifier: {
11
+ ES256: number;
12
+ RS256: number;
13
+ EdDSA: number;
14
+ };
15
+ export type PasskeyCredential = {
16
+ readonly publicKeyCredentialDescriptor: PublicKeyCredentialDescriptor;
17
+ readonly publicKeyCredentialSource: PublicKeyCredentialSource;
18
+ readonly authenticatorData: AuthenticatorData;
19
+ readonly user: PublicKeyCredentialUserEntity | undefined;
20
+ };
21
+ export type AuthenticatorParameters = {
22
+ readonly aaguid: Uint8Array;
23
+ readonly transports: AuthenticatorTransport[];
24
+ readonly algorithmIdentifiers: readonly (keyof typeof COSEAlgorithmIdentifier)[];
25
+ readonly signCounterIncrement: number;
26
+ readonly verifications: {
27
+ readonly userPresent: boolean;
28
+ readonly userVerified: boolean;
29
+ };
30
+ readonly userMakeCredentialInteraction: (user: PublicKeyCredentialUserEntity, options?: Partial<AuthenticatorOptions>) => InteractionResponse | undefined;
31
+ readonly userGetAssertionInteraction: (user: PublicKeyCredentialUserEntity | undefined, options?: Partial<AuthenticatorOptions>) => InteractionResponse | undefined;
32
+ readonly credentialsRepository: PasskeysCredentialsRepository | undefined;
33
+ readonly stateless: boolean;
34
+ };
35
+ export type MakeCredentialInteraction = (user: PublicKeyCredentialUserEntity, uv: boolean) => boolean;
36
+ /**
37
+ * Authenticator emulator
38
+ */
39
+ export declare class AuthenticatorEmulator {
40
+ private static readonly ENCRYPT_KEY;
41
+ /** Authenticator Attestation Global Unique Identifier (16byte) */
42
+ private static readonly DEFAULT_AAGUID;
43
+ private static readonly DEFAULT_TRANSPORTS;
44
+ private static readonly DEFAULT_ALGORITHM_IDENTIFIERS;
45
+ private static readonly DEFAULT_SIGN_COUNTER_INCREMENT;
46
+ private static readonly DEFAULT_VERIFICATIONS;
47
+ private static readonly DEFAULT_MAKE_CREDENTIAL_INTERACTION;
48
+ private static readonly DEFAULT_GET_ASSERTION_INTERACTION;
49
+ private static readonly DEFAULT_CREDENTIALS_REPOSITORY;
50
+ private static readonly DEFAULT_STATELESS;
51
+ params: AuthenticatorParameters;
52
+ constructor(params?: Partial<AuthenticatorParameters>);
53
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api */
54
+ command(request: CTAPAuthenticatorRequest): CTAPAuthenticatorResponse;
55
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo */
56
+ authenticatorGetInfo(): AuthenticatorGetInfoResponse;
57
+ /**
58
+ * @see https://www.w3.org/TR/webauthn/#sctn-op-make-cred
59
+ * @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential
60
+ **/
61
+ authenticatorMakeCredential(request: AuthenticatorMakeCredentialRequest): AuthenticatorMakeCredentialResponse;
62
+ /**
63
+ * @see https://www.w3.org/TR/webauthn/#sctn-op-get-assertion
64
+ * @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion
65
+ **/
66
+ authenticatorGetAssertion(request: AuthenticatorGetAssertionRequest): AuthenticatorGetAssertionResponse;
67
+ }
68
+ export {};
@@ -0,0 +1,304 @@
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
+ exports.AuthenticatorEmulator = exports.COSEAlgorithmIdentifier = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const encode_utils_1 = __importDefault(require("../libs/encode-utils"));
9
+ const credentials_memory_repository_1 = require("../repository/credentials-memory-repository");
10
+ const cose_key_1 = require("../webauthn/cose-key");
11
+ const webauthn_model_1 = require("../webauthn/webauthn-model");
12
+ const ctap_model_1 = require("./ctap-model");
13
+ exports.COSEAlgorithmIdentifier = {
14
+ ES256: -7,
15
+ RS256: -257,
16
+ EdDSA: -8,
17
+ };
18
+ /**
19
+ * Authenticator emulator
20
+ */
21
+ class AuthenticatorEmulator {
22
+ static ENCRYPT_KEY = encode_utils_1.default.strToUint8Array("NID-AUTH-31415926535897932384626");
23
+ /** Authenticator Attestation Global Unique Identifier (16byte) */
24
+ static DEFAULT_AAGUID = encode_utils_1.default.strToUint8Array("NID-AUTH-3141592");
25
+ static DEFAULT_TRANSPORTS = ["usb"];
26
+ static DEFAULT_ALGORITHM_IDENTIFIERS = ["ES256", "RS256", "EdDSA"];
27
+ static DEFAULT_SIGN_COUNTER_INCREMENT = 1;
28
+ static DEFAULT_VERIFICATIONS = { userPresent: true, userVerified: true };
29
+ static DEFAULT_MAKE_CREDENTIAL_INTERACTION = (user) => ({
30
+ user: user,
31
+ options: { uv: true, up: true },
32
+ });
33
+ static DEFAULT_GET_ASSERTION_INTERACTION = (user) => ({
34
+ user: user,
35
+ options: { uv: true, up: true },
36
+ });
37
+ static DEFAULT_CREDENTIALS_REPOSITORY = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
38
+ static DEFAULT_STATELESS = false;
39
+ params;
40
+ constructor(params = {}) {
41
+ this.params = {
42
+ aaguid: params.aaguid ?? AuthenticatorEmulator.DEFAULT_AAGUID,
43
+ transports: params.transports ?? AuthenticatorEmulator.DEFAULT_TRANSPORTS,
44
+ algorithmIdentifiers: params.algorithmIdentifiers ?? AuthenticatorEmulator.DEFAULT_ALGORITHM_IDENTIFIERS,
45
+ signCounterIncrement: params.stateless
46
+ ? 0
47
+ : params.signCounterIncrement ?? AuthenticatorEmulator.DEFAULT_SIGN_COUNTER_INCREMENT,
48
+ verifications: params.verifications ?? AuthenticatorEmulator.DEFAULT_VERIFICATIONS,
49
+ userMakeCredentialInteraction: params.userMakeCredentialInteraction ?? AuthenticatorEmulator.DEFAULT_MAKE_CREDENTIAL_INTERACTION,
50
+ userGetAssertionInteraction: params.userGetAssertionInteraction ?? AuthenticatorEmulator.DEFAULT_GET_ASSERTION_INTERACTION,
51
+ credentialsRepository: params.stateless ? undefined : AuthenticatorEmulator.DEFAULT_CREDENTIALS_REPOSITORY,
52
+ stateless: params.stateless ?? AuthenticatorEmulator.DEFAULT_STATELESS,
53
+ };
54
+ }
55
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api */
56
+ command(request) {
57
+ const unpackedRequest = (0, ctap_model_1.unpackRequest)(request);
58
+ if (unpackedRequest.command === ctap_model_1.CTAP_COMMAND.authenticatorMakeCredential) {
59
+ const makeCredentialRequest = unpackedRequest.request;
60
+ const makeCredentialResponse = this.authenticatorMakeCredential(makeCredentialRequest);
61
+ return (0, ctap_model_1.packMakeCredentialResponse)(makeCredentialResponse);
62
+ }
63
+ if (unpackedRequest.command === ctap_model_1.CTAP_COMMAND.authenticatorGetAssertion) {
64
+ const getAssertionRequest = unpackedRequest.request;
65
+ const getAssertionResponse = this.authenticatorGetAssertion(getAssertionRequest);
66
+ return (0, ctap_model_1.packGetAssertionResponse)(getAssertionResponse);
67
+ }
68
+ if (unpackedRequest.command === ctap_model_1.CTAP_COMMAND.authenticatorGetInfo) {
69
+ return (0, ctap_model_1.packGetInfoResponse)(this.authenticatorGetInfo());
70
+ }
71
+ throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND);
72
+ }
73
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo */
74
+ authenticatorGetInfo() {
75
+ return {
76
+ versions: ["FIDO_2_0"],
77
+ aaguid: this.params.aaguid,
78
+ options: {
79
+ rk: true,
80
+ uv: this.params.verifications.userVerified,
81
+ up: this.params.verifications.userPresent,
82
+ },
83
+ };
84
+ }
85
+ /**
86
+ * @see https://www.w3.org/TR/webauthn/#sctn-op-make-cred
87
+ * @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential
88
+ **/
89
+ authenticatorMakeCredential(request) {
90
+ const rpId = new webauthn_model_1.RpId(request.rp.id);
91
+ const repository = this.params.credentialsRepository;
92
+ // Exclude list
93
+ if (request.excludeList && request.excludeList.length > 0 && repository) {
94
+ const existingCredentials = getCredentials(rpId, request.excludeList, repository);
95
+ if (existingCredentials.length > 0) {
96
+ throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_CREDENTIAL_EXCLUDED);
97
+ }
98
+ }
99
+ // Algorithm selection
100
+ const allowAlgSet = new Set(request.pubKeyCredParams.map((param) => param.alg));
101
+ const alg = this.params.algorithmIdentifiers.find((alg) => allowAlgSet.has(exports.COSEAlgorithmIdentifier[alg]));
102
+ if (!alg)
103
+ throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_UNSUPPORTED_ALGORITHM);
104
+ // User operation
105
+ const interactionResponse = this.params.userMakeCredentialInteraction(request.user, request.options);
106
+ if (!interactionResponse)
107
+ throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_OPERATION_DENIED);
108
+ // Create credential
109
+ const credential = makeCredential(this.params.aaguid, rpId, alg, this.params.transports, interactionResponse, request.user, repository ? undefined : AuthenticatorEmulator.ENCRYPT_KEY);
110
+ if (repository) {
111
+ const discoverableCredential = {
112
+ ...credential,
113
+ user: request.user,
114
+ };
115
+ saveCredential(discoverableCredential, repository);
116
+ }
117
+ return {
118
+ fmt: "none",
119
+ authData: (0, webauthn_model_1.packAuthenticatorData)(credential.authenticatorData),
120
+ attStmt: {},
121
+ };
122
+ }
123
+ /**
124
+ * @see https://www.w3.org/TR/webauthn/#sctn-op-get-assertion
125
+ * @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion
126
+ **/
127
+ authenticatorGetAssertion(request) {
128
+ const rpId = new webauthn_model_1.RpId(request.rpId);
129
+ const repository = this.params.credentialsRepository;
130
+ const allowList = request.allowList ?? [];
131
+ // Allow list
132
+ const credentials = repository
133
+ ? getCredentials(rpId, allowList, repository)
134
+ : getCredentialsStateless(rpId, allowList, AuthenticatorEmulator.ENCRYPT_KEY);
135
+ if (credentials.length === 0)
136
+ throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS);
137
+ const credential = credentials[credentials.length - 1];
138
+ // User operation
139
+ const interactionResponse = this.params.userGetAssertionInteraction(credential.user, request.options);
140
+ if (!interactionResponse)
141
+ throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_OPERATION_DENIED);
142
+ // Get assertion
143
+ const newSignCount = !repository ? 0 : credential.authenticatorData.signCount + this.params.signCounterIncrement;
144
+ const { authData, signature } = getAssertion(rpId.hash, request.clientDataHash, newSignCount, credential.publicKeyCredentialSource, interactionResponse, !repository);
145
+ // Update sign count
146
+ if (repository && credential.user) {
147
+ const updatedCredential = {
148
+ ...credential,
149
+ authenticatorData: {
150
+ ...credential.authenticatorData,
151
+ signCount: newSignCount,
152
+ },
153
+ user: credential.user,
154
+ };
155
+ saveCredential(updatedCredential, repository);
156
+ }
157
+ return {
158
+ credential: request.allowList?.length === 1 ? undefined : credential.publicKeyCredentialDescriptor,
159
+ authData,
160
+ signature,
161
+ user: credential.user,
162
+ numberOfCredentials: credentials.length,
163
+ };
164
+ }
165
+ }
166
+ exports.AuthenticatorEmulator = AuthenticatorEmulator;
167
+ function getCredentialsStateless(rpId, allowCredentials, key) {
168
+ return allowCredentials.map((descriptor) => {
169
+ const id = encode_utils_1.default.bufferSourceToUint8Array(descriptor.id);
170
+ const publicKeyCredentialSource = {
171
+ type: "public-key",
172
+ id,
173
+ privateKey: decryptBytes(key, id),
174
+ rpId: rpId,
175
+ };
176
+ const authData = {
177
+ rpIdHash: rpId.hash,
178
+ flags: {
179
+ backupEligibility: false,
180
+ backupState: false,
181
+ userPresent: true,
182
+ userVerified: true,
183
+ attestedCredentialData: false,
184
+ extensionData: false,
185
+ },
186
+ signCount: 0,
187
+ };
188
+ return {
189
+ publicKeyCredentialDescriptor: descriptor,
190
+ publicKeyCredentialSource,
191
+ authenticatorData: authData,
192
+ user: undefined,
193
+ };
194
+ });
195
+ }
196
+ function getCredentials(rpId, credentialsFilter, repository) {
197
+ const allowIds = new Set(credentialsFilter.map((descriptor) => encode_utils_1.default.encodeBase64Url(descriptor.id)));
198
+ const credentials = repository.loadCredentials();
199
+ return credentials.filter((credential) => {
200
+ if (rpId.value !== credential.publicKeyCredentialSource.rpId.value)
201
+ return false;
202
+ if (credentialsFilter.length > 0) {
203
+ const rawId = credential.publicKeyCredentialDescriptor.id;
204
+ if (!allowIds?.has(encode_utils_1.default.encodeBase64Url(rawId)))
205
+ return false;
206
+ }
207
+ return true;
208
+ });
209
+ }
210
+ function saveCredential(credential, repository) {
211
+ const credentials = repository.loadCredentials();
212
+ const index = credentials.findIndex((c) => {
213
+ if (c.publicKeyCredentialSource.rpId.value !== credential.publicKeyCredentialSource.rpId.value)
214
+ return false;
215
+ return encode_utils_1.default.encodeBase64Url(c.user.id) === encode_utils_1.default.encodeBase64Url(credential.user.id);
216
+ });
217
+ if (index >= 0) {
218
+ repository.deleteCredential(credentials[index]);
219
+ }
220
+ repository.saveCredential(credential);
221
+ }
222
+ function getAssertion(rpIdHash, clientDataHash, newSignCounter, credential, interactionResponse, stateless) {
223
+ const authenticatorData = {
224
+ rpIdHash,
225
+ flags: {
226
+ userPresent: interactionResponse.options.up,
227
+ userVerified: interactionResponse.options.uv,
228
+ backupEligibility: !stateless,
229
+ backupState: !stateless,
230
+ attestedCredentialData: false,
231
+ extensionData: false,
232
+ },
233
+ signCount: newSignCounter,
234
+ };
235
+ const payload = new Array();
236
+ payload.push(...(0, webauthn_model_1.packAuthenticatorData)(authenticatorData));
237
+ payload.push(...clientDataHash);
238
+ const privateKey = (0, node_crypto_1.createPrivateKey)({
239
+ format: "der",
240
+ type: "pkcs8",
241
+ key: credential.privateKey,
242
+ });
243
+ const signature = (0, node_crypto_1.sign)(null, new Uint8Array(payload), privateKey);
244
+ return { authData: (0, webauthn_model_1.packAuthenticatorData)(authenticatorData), signature };
245
+ }
246
+ function makeCredential(aaguid, rpId, alg, transports, interactionResponse, user, statelessKey) {
247
+ const generatekeyPair = (alg) => {
248
+ if (alg === "RS256")
249
+ return (0, node_crypto_1.generateKeyPairSync)("rsa", { modulusLength: 2048 });
250
+ if (alg === "EdDSA")
251
+ return (0, node_crypto_1.generateKeyPairSync)("ed25519");
252
+ return (0, node_crypto_1.generateKeyPairSync)("ec", { namedCurve: "P-256" });
253
+ };
254
+ const keyPair = generatekeyPair(alg);
255
+ const privateKey = new Uint8Array(keyPair.privateKey.export({ format: "der", type: "pkcs8" }));
256
+ const credentialId = statelessKey ? encryptBytes(statelessKey, privateKey) : new Uint8Array((0, node_crypto_1.randomBytes)(32));
257
+ const publicKeyCredentialSource = {
258
+ type: "public-key",
259
+ id: credentialId,
260
+ privateKey: new Uint8Array(keyPair.privateKey.export({ format: "der", type: "pkcs8" })),
261
+ rpId: rpId,
262
+ userHandle: user && !statelessKey ? encode_utils_1.default.bufferSourceToUint8Array(user.id) : undefined,
263
+ };
264
+ const publicKeyCredentialDescriptor = {
265
+ type: "public-key",
266
+ id: credentialId,
267
+ transports,
268
+ };
269
+ const authenticatorData = {
270
+ rpIdHash: rpId.hash,
271
+ flags: {
272
+ backupEligibility: !statelessKey,
273
+ backupState: !statelessKey,
274
+ userPresent: interactionResponse.options.up,
275
+ userVerified: interactionResponse.options.uv,
276
+ attestedCredentialData: true,
277
+ extensionData: false,
278
+ },
279
+ signCount: 0,
280
+ attestedCredentialData: {
281
+ aaguid,
282
+ credentialId,
283
+ credentialPublicKey: cose_key_1.CoseKey.fromKeyObject(keyPair.publicKey),
284
+ },
285
+ };
286
+ return {
287
+ publicKeyCredentialDescriptor,
288
+ publicKeyCredentialSource,
289
+ authenticatorData,
290
+ user,
291
+ };
292
+ }
293
+ function encryptBytes(key, data) {
294
+ const iv = (0, node_crypto_1.randomBytes)(16);
295
+ const cipher = (0, node_crypto_1.createCipheriv)("aes-256-ctr", key, iv);
296
+ const encrypted = cipher.update(data);
297
+ return Buffer.concat([iv, encrypted, cipher.final()]);
298
+ }
299
+ function decryptBytes(key, data) {
300
+ const iv = data.slice(0, 16);
301
+ const encrypted = data.slice(16);
302
+ const cipher = (0, node_crypto_1.createCipheriv)("aes-256-ctr", key, iv);
303
+ return Buffer.concat([cipher.update(encrypted), cipher.final()]);
304
+ }
@@ -0,0 +1,107 @@
1
+ export type AuthenticatorOptions = {
2
+ rk: boolean;
3
+ uv: boolean;
4
+ up: boolean;
5
+ };
6
+ export type AuthenticatorInteractionOptions = {
7
+ up: boolean;
8
+ uv: boolean;
9
+ };
10
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential */
11
+ export interface AuthenticatorMakeCredentialRequest {
12
+ clientDataHash: Uint8Array;
13
+ rp: PublicKeyCredentialRpEntity & {
14
+ id: string;
15
+ };
16
+ user: PublicKeyCredentialUserEntity;
17
+ pubKeyCredParams: PublicKeyCredentialParameters[];
18
+ excludeList?: PublicKeyCredentialDescriptor[];
19
+ extensions?: object;
20
+ options?: Partial<AuthenticatorOptions>;
21
+ pinAuth?: Uint8Array;
22
+ pinProtocol?: number;
23
+ }
24
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential */
25
+ export interface AuthenticatorMakeCredentialResponse {
26
+ fmt: string;
27
+ authData: Uint8Array;
28
+ attStmt: object;
29
+ }
30
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion */
31
+ export interface AuthenticatorGetAssertionRequest {
32
+ rpId: string;
33
+ clientDataHash: Uint8Array;
34
+ allowList?: PublicKeyCredentialDescriptor[];
35
+ extensions?: object;
36
+ options?: Partial<AuthenticatorOptions>;
37
+ pinAuth?: Uint8Array;
38
+ pinProtocol?: number;
39
+ }
40
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion */
41
+ export interface AuthenticatorGetAssertionResponse {
42
+ credential?: PublicKeyCredentialDescriptor;
43
+ authData: Uint8Array;
44
+ signature: Uint8Array;
45
+ user?: PublicKeyCredentialUserEntity;
46
+ numberOfCredentials: number;
47
+ }
48
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo */
49
+ export type AuthenticatorGetInfoResponse = {
50
+ versions: string[];
51
+ extensions?: string[];
52
+ aaguid: Uint8Array;
53
+ options?: Partial<AuthenticatorOptions> & {
54
+ plat?: boolean;
55
+ clientPin?: boolean;
56
+ };
57
+ maxMsgSize?: number;
58
+ pinProtocols?: number[];
59
+ };
60
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#error-responses */
61
+ export declare enum CTAP_STATUS_CODE {
62
+ CTAP2_OK = 0,
63
+ CTAP1_ERR_INVALID_COMMAND = 1,
64
+ CTAP1_ERR_INVALID_PARAMETER = 2,
65
+ CTAP2_ERR_INVALID_CBOR = 18,
66
+ CTAP2_ERR_CREDENTIAL_EXCLUDED = 25,
67
+ CTAP2_ERR_UNSUPPORTED_ALGORITHM = 38,
68
+ CTAP2_ERR_OPERATION_DENIED = 39,
69
+ CTAP2_ERR_NO_CREDENTIALS = 46,
70
+ CTAP2_ERR_NOT_ALLOWED = 48
71
+ }
72
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands */
73
+ export declare enum CTAP_COMMAND {
74
+ authenticatorMakeCredential = 1,
75
+ authenticatorGetAssertion = 2,
76
+ authenticatorGetInfo = 4,
77
+ authenticatorClientPIN = 6,
78
+ authenticatorReset = 7,
79
+ authenticatorGetNextAssertion = 8
80
+ }
81
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#responses */
82
+ export interface CTAPAuthenticatorResponse {
83
+ status: CTAP_STATUS_CODE;
84
+ data?: Uint8Array;
85
+ }
86
+ /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands */
87
+ export interface CTAPAuthenticatorRequest {
88
+ command: CTAP_COMMAND;
89
+ data?: Uint8Array;
90
+ }
91
+ export declare class AuthenticationEmulatorError extends Error {
92
+ status: CTAP_STATUS_CODE;
93
+ type: string;
94
+ constructor(status: CTAP_STATUS_CODE, options?: ErrorOptions);
95
+ }
96
+ export declare function unpackRequest(request: CTAPAuthenticatorRequest): {
97
+ command: CTAP_COMMAND;
98
+ request: unknown;
99
+ };
100
+ export declare function packMakeCredentialRequest(request: AuthenticatorMakeCredentialRequest): CTAPAuthenticatorRequest;
101
+ export declare function packGetAssertionRequest(request: AuthenticatorGetAssertionRequest): CTAPAuthenticatorRequest;
102
+ export declare function unpackMakeCredentialResponse(response: CTAPAuthenticatorResponse): AuthenticatorMakeCredentialResponse;
103
+ export declare function packMakeCredentialResponse(response: AuthenticatorMakeCredentialResponse): CTAPAuthenticatorResponse;
104
+ export declare function unpackGetAssertionResponse(response: CTAPAuthenticatorResponse): AuthenticatorGetAssertionResponse;
105
+ export declare function packGetAssertionResponse(response: AuthenticatorGetAssertionResponse): CTAPAuthenticatorResponse;
106
+ export declare function unpackGetInfoResponse(response: CTAPAuthenticatorResponse): AuthenticatorGetInfoResponse;
107
+ export declare function packGetInfoResponse(response: AuthenticatorGetInfoResponse): CTAPAuthenticatorResponse;