mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[EC-598] feat: fully wokring non-discoverable implementation
This commit is contained in:
@@ -47,6 +47,15 @@ export type BrowserFido2Message = { requestId: string } & (
|
|||||||
| {
|
| {
|
||||||
type: "ConfirmNewCredentialResponse";
|
type: "ConfirmNewCredentialResponse";
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "ConfirmNewNonDiscoverableCredentialRequest";
|
||||||
|
credentialName: string;
|
||||||
|
userName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "ConfirmNewNonDiscoverableCredentialResponse";
|
||||||
|
cipherId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "AbortRequest";
|
type: "AbortRequest";
|
||||||
}
|
}
|
||||||
@@ -201,10 +210,47 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async confirmNewNonDiscoverableCredential(
|
async confirmNewNonDiscoverableCredential(
|
||||||
params: NewCredentialParams,
|
{ credentialName, userName }: NewCredentialParams,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return null;
|
const requestId = Utils.newGuid();
|
||||||
|
const data: BrowserFido2Message = {
|
||||||
|
type: "ConfirmNewNonDiscoverableCredentialRequest",
|
||||||
|
requestId,
|
||||||
|
credentialName,
|
||||||
|
userName,
|
||||||
|
};
|
||||||
|
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||||
|
|
||||||
|
const abortHandler = () =>
|
||||||
|
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||||
|
abortController.signal.addEventListener("abort", abortHandler);
|
||||||
|
|
||||||
|
this.popupUtilsService.popOut(
|
||||||
|
null,
|
||||||
|
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
||||||
|
{ center: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await lastValueFrom(
|
||||||
|
this.messages$.pipe(
|
||||||
|
filter((msg) => msg.requestId === requestId),
|
||||||
|
first(),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.type === "ConfirmNewNonDiscoverableCredentialResponse") {
|
||||||
|
return response.cipherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.type === "AbortResponse") {
|
||||||
|
throw new RequestAbortedError(response.fallbackRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
abortController.signal.removeEventListener("abort", abortHandler);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async informExcludedCredential(
|
async informExcludedCredential(
|
||||||
|
|||||||
@@ -11,7 +11,12 @@
|
|||||||
Authenticate
|
Authenticate
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="data.type == 'PickCredentialRequest'">
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
data.type == 'PickCredentialRequest' ||
|
||||||
|
data.type == 'ConfirmNewNonDiscoverableCredentialRequest'
|
||||||
|
"
|
||||||
|
>
|
||||||
A site is asking for authentication, please choose one of the following credentials to use:
|
A site is asking for authentication, please choose one of the following credentials to use:
|
||||||
<div class="box list">
|
<div class="box list">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
return cipher.decrypt();
|
return cipher.decrypt();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} else if (this.data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
|
||||||
|
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||||
|
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
takeUntil(this.destroy$)
|
takeUntil(this.destroy$)
|
||||||
@@ -66,11 +70,19 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async pick(cipher: CipherView) {
|
async pick(cipher: CipherView) {
|
||||||
BrowserFido2UserInterfaceService.sendMessage({
|
if (this.data?.type === "PickCredentialRequest") {
|
||||||
requestId: this.data.requestId,
|
BrowserFido2UserInterfaceService.sendMessage({
|
||||||
cipherId: cipher.id,
|
requestId: this.data.requestId,
|
||||||
type: "PickCredentialResponse",
|
cipherId: cipher.id,
|
||||||
});
|
type: "PickCredentialResponse",
|
||||||
|
});
|
||||||
|
} else if (this.data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
|
||||||
|
BrowserFido2UserInterfaceService.sendMessage({
|
||||||
|
requestId: this.data.requestId,
|
||||||
|
cipherId: cipher.id,
|
||||||
|
type: "ConfirmNewNonDiscoverableCredentialResponse",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Fido2KeyApi } from "../../webauthn/models/api/fido2-key.api";
|
||||||
import { BaseResponse } from "../response/base.response";
|
import { BaseResponse } from "../response/base.response";
|
||||||
|
|
||||||
import { LoginUriApi } from "./login-uri.api";
|
import { LoginUriApi } from "./login-uri.api";
|
||||||
@@ -9,6 +10,7 @@ export class LoginApi extends BaseResponse {
|
|||||||
passwordRevisionDate: string;
|
passwordRevisionDate: string;
|
||||||
totp: string;
|
totp: string;
|
||||||
autofillOnPageLoad: boolean;
|
autofillOnPageLoad: boolean;
|
||||||
|
fido2Key?: Fido2KeyApi;
|
||||||
|
|
||||||
constructor(data: any = null) {
|
constructor(data: any = null) {
|
||||||
super(data);
|
super(data);
|
||||||
@@ -25,5 +27,10 @@ export class LoginApi extends BaseResponse {
|
|||||||
if (uris != null) {
|
if (uris != null) {
|
||||||
this.uris = uris.map((u: any) => new LoginUriApi(u));
|
this.uris = uris.map((u: any) => new LoginUriApi(u));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fido2Key = this.getResponseProperty("Fido2Key");
|
||||||
|
if (fido2Key != null) {
|
||||||
|
this.fido2Key = new Fido2KeyApi(fido2Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ export class CipherData {
|
|||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
this.login = new LoginData(response.login);
|
this.login = new LoginData(response.login);
|
||||||
|
this.fido2Key =
|
||||||
|
response.fido2Key != undefined ? new Fido2KeyData(response.fido2Key) : undefined;
|
||||||
break;
|
break;
|
||||||
case CipherType.SecureNote:
|
case CipherType.SecureNote:
|
||||||
this.secureNote = new SecureNoteData(response.secureNote);
|
this.secureNote = new SecureNoteData(response.secureNote);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { LoginApi } from "../../../models/api/login.api";
|
import { LoginApi } from "../../../models/api/login.api";
|
||||||
|
import { Fido2KeyData } from "../../../webauthn/models/data/fido2-key.data";
|
||||||
|
|
||||||
import { LoginUriData } from "./login-uri.data";
|
import { LoginUriData } from "./login-uri.data";
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ export class LoginData {
|
|||||||
passwordRevisionDate: string;
|
passwordRevisionDate: string;
|
||||||
totp: string;
|
totp: string;
|
||||||
autofillOnPageLoad: boolean;
|
autofillOnPageLoad: boolean;
|
||||||
|
fido2Key?: Fido2KeyData;
|
||||||
|
|
||||||
constructor(data?: LoginApi) {
|
constructor(data?: LoginApi) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
@@ -24,5 +26,9 @@ export class LoginData {
|
|||||||
if (data.uris) {
|
if (data.uris) {
|
||||||
this.uris = data.uris.map((u) => new LoginUriData(u));
|
this.uris = data.uris.map((u) => new LoginUriData(u));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.fido2Key) {
|
||||||
|
this.fido2Key = new Fido2KeyData(data.fido2Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
|
|||||||
import Domain from "../../../models/domain/domain-base";
|
import Domain from "../../../models/domain/domain-base";
|
||||||
import { EncString } from "../../../models/domain/enc-string";
|
import { EncString } from "../../../models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key";
|
||||||
|
import { Fido2Key } from "../../../webauthn/models/domain/fido2-key";
|
||||||
import { LoginData } from "../data/login.data";
|
import { LoginData } from "../data/login.data";
|
||||||
import { LoginView } from "../view/login.view";
|
import { LoginView } from "../view/login.view";
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export class Login extends Domain {
|
|||||||
passwordRevisionDate?: Date;
|
passwordRevisionDate?: Date;
|
||||||
totp: EncString;
|
totp: EncString;
|
||||||
autofillOnPageLoad: boolean;
|
autofillOnPageLoad: boolean;
|
||||||
|
fido2Key: Fido2Key;
|
||||||
|
|
||||||
constructor(obj?: LoginData) {
|
constructor(obj?: LoginData) {
|
||||||
super();
|
super();
|
||||||
@@ -42,6 +44,10 @@ export class Login extends Domain {
|
|||||||
this.uris.push(new LoginUri(u));
|
this.uris.push(new LoginUri(u));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj.fido2Key) {
|
||||||
|
this.fido2Key = new Fido2Key(obj.fido2Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginView> {
|
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginView> {
|
||||||
@@ -64,6 +70,10 @@ export class Login extends Domain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.fido2Key != null) {
|
||||||
|
view.fido2Key = await this.fido2Key.decrypt(orgId, encKey);
|
||||||
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +95,10 @@ export class Login extends Domain {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.fido2Key != null) {
|
||||||
|
l.fido2Key = this.fido2Key.toFido2KeyData();
|
||||||
|
}
|
||||||
|
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,13 +113,15 @@ export class Login extends Domain {
|
|||||||
const passwordRevisionDate =
|
const passwordRevisionDate =
|
||||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||||
const uris = obj.uris?.map((uri: any) => LoginUri.fromJSON(uri));
|
const uris = obj.uris?.map((uri: any) => LoginUri.fromJSON(uri));
|
||||||
|
const fido2Key = obj.fido2Key == null ? null : Fido2Key.fromJSON(obj.fido2Key);
|
||||||
|
|
||||||
return Object.assign(new Login(), obj, {
|
return Object.assign(new Login(), obj, {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
totp,
|
totp,
|
||||||
passwordRevisionDate: passwordRevisionDate,
|
passwordRevisionDate,
|
||||||
uris: uris,
|
uris,
|
||||||
|
fido2Key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,44 @@ export class CipherRequest {
|
|||||||
return uri;
|
return uri;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cipher.login.fido2Key != null) {
|
||||||
|
this.login.fido2Key = new Fido2KeyApi();
|
||||||
|
this.login.fido2Key.nonDiscoverableId =
|
||||||
|
cipher.login.fido2Key.nonDiscoverableId != null
|
||||||
|
? cipher.login.fido2Key.nonDiscoverableId.encryptedString
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.keyType =
|
||||||
|
cipher.login.fido2Key.keyType != null
|
||||||
|
? (cipher.login.fido2Key.keyType.encryptedString as "public-key")
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.keyAlgorithm =
|
||||||
|
cipher.login.fido2Key.keyAlgorithm != null
|
||||||
|
? (cipher.login.fido2Key.keyAlgorithm.encryptedString as "ECDSA")
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.keyCurve =
|
||||||
|
cipher.login.fido2Key.keyCurve != null
|
||||||
|
? (cipher.login.fido2Key.keyCurve.encryptedString as "P-256")
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.keyValue =
|
||||||
|
cipher.login.fido2Key.keyValue != null
|
||||||
|
? cipher.login.fido2Key.keyValue.encryptedString
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.rpId =
|
||||||
|
cipher.login.fido2Key.rpId != null ? cipher.login.fido2Key.rpId.encryptedString : null;
|
||||||
|
this.login.fido2Key.rpName =
|
||||||
|
cipher.login.fido2Key.rpName != null
|
||||||
|
? cipher.login.fido2Key.rpName.encryptedString
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.userHandle =
|
||||||
|
cipher.login.fido2Key.userHandle != null
|
||||||
|
? cipher.login.fido2Key.userHandle.encryptedString
|
||||||
|
: null;
|
||||||
|
this.login.fido2Key.userName =
|
||||||
|
cipher.login.fido2Key.userName != null
|
||||||
|
? cipher.login.fido2Key.userName.encryptedString
|
||||||
|
: null;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CipherType.SecureNote:
|
case CipherType.SecureNote:
|
||||||
this.secureNote = new SecureNoteApi();
|
this.secureNote = new SecureNoteApi();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
|
|||||||
import { LoginLinkedId as LinkedId } from "../../../enums/linkedIdType";
|
import { LoginLinkedId as LinkedId } from "../../../enums/linkedIdType";
|
||||||
import { linkedFieldOption } from "../../../misc/linkedFieldOption.decorator";
|
import { linkedFieldOption } from "../../../misc/linkedFieldOption.decorator";
|
||||||
import { Utils } from "../../../misc/utils";
|
import { Utils } from "../../../misc/utils";
|
||||||
|
import { Fido2KeyView } from "../../../webauthn/models/view/fido2-key.view";
|
||||||
import { Login } from "../domain/login";
|
import { Login } from "../domain/login";
|
||||||
|
|
||||||
import { ItemView } from "./item.view";
|
import { ItemView } from "./item.view";
|
||||||
@@ -18,6 +19,7 @@ export class LoginView extends ItemView {
|
|||||||
totp: string = null;
|
totp: string = null;
|
||||||
uris: LoginUriView[] = null;
|
uris: LoginUriView[] = null;
|
||||||
autofillOnPageLoad: boolean = null;
|
autofillOnPageLoad: boolean = null;
|
||||||
|
fido2Key?: Fido2KeyView;
|
||||||
|
|
||||||
constructor(l?: Login) {
|
constructor(l?: Login) {
|
||||||
super();
|
super();
|
||||||
@@ -67,10 +69,12 @@ export class LoginView extends ItemView {
|
|||||||
const passwordRevisionDate =
|
const passwordRevisionDate =
|
||||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||||
const uris = obj.uris?.map((uri: any) => LoginUriView.fromJSON(uri));
|
const uris = obj.uris?.map((uri: any) => LoginUriView.fromJSON(uri));
|
||||||
|
const fido2Key = obj.fido2Key == null ? null : Fido2KeyView.fromJSON(obj.fido2Key);
|
||||||
|
|
||||||
return Object.assign(new LoginView(), obj, {
|
return Object.assign(new LoginView(), obj, {
|
||||||
passwordRevisionDate: passwordRevisionDate,
|
passwordRevisionDate: passwordRevisionDate,
|
||||||
uris: uris,
|
uris,
|
||||||
|
fido2Key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1117,6 +1117,27 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
cipher.login.uris.push(loginUri);
|
cipher.login.uris.push(loginUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model.login.fido2Key != null) {
|
||||||
|
cipher.login.fido2Key = new Fido2Key();
|
||||||
|
await this.encryptObjProperty(
|
||||||
|
model.login.fido2Key,
|
||||||
|
cipher.login.fido2Key,
|
||||||
|
{
|
||||||
|
nonDiscoverableId: null,
|
||||||
|
keyType: null,
|
||||||
|
keyAlgorithm: null,
|
||||||
|
keyCurve: null,
|
||||||
|
keyValue: null,
|
||||||
|
rpId: null,
|
||||||
|
rpName: null,
|
||||||
|
userHandle: null,
|
||||||
|
userName: null,
|
||||||
|
origin: null,
|
||||||
|
},
|
||||||
|
key
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case CipherType.SecureNote:
|
case CipherType.SecureNote:
|
||||||
cipher.secureNote = new SecureNote();
|
cipher.secureNote = new SecureNote();
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
params = await createParams({
|
params = await createParams({
|
||||||
excludeCredentialDescriptorList: [
|
excludeCredentialDescriptorList: [
|
||||||
{
|
{
|
||||||
id: Utils.guidToRawFormat(excludedCipher.fido2Key.nonDiscoverableId),
|
id: Utils.guidToRawFormat(excludedCipher.login.fido2Key.nonDiscoverableId),
|
||||||
type: "public-key",
|
type: "public-key",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -162,15 +162,16 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
let params: Fido2AuthenticatorMakeCredentialsParams;
|
let params: Fido2AuthenticatorMakeCredentialsParams;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const excludedCipher = createCipherView();
|
excludedCipherView = createCipherView();
|
||||||
excludedCipherView = await excludedCipher;
|
|
||||||
params = await createParams({
|
params = await createParams({
|
||||||
excludeCredentialDescriptorList: [
|
excludeCredentialDescriptorList: [
|
||||||
{ id: Utils.guidToRawFormat(excludedCipher.id), type: "public-key" },
|
{ id: Utils.guidToRawFormat(excludedCipherView.id), type: "public-key" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
cipherService.get.mockImplementation(async (id) =>
|
cipherService.get.mockImplementation(async (id) =>
|
||||||
id === excludedCipher.id ? excludedCipher : undefined
|
id === excludedCipherView.id
|
||||||
|
? ({ decrypt: async () => excludedCipherView } as any)
|
||||||
|
: undefined
|
||||||
);
|
);
|
||||||
cipherService.getAllDecrypted.mockResolvedValue([excludedCipherView]);
|
cipherService.getAllDecrypted.mockResolvedValue([excludedCipherView]);
|
||||||
});
|
});
|
||||||
@@ -237,12 +238,15 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
return cipher;
|
return cipher;
|
||||||
});
|
});
|
||||||
|
|
||||||
await authenticator.makeCredential(params);
|
await authenticator.makeCredential(params, new AbortController());
|
||||||
|
|
||||||
expect(userInterface.confirmNewCredential).toHaveBeenCalledWith({
|
expect(userInterface.confirmNewCredential).toHaveBeenCalledWith(
|
||||||
credentialName: params.rpEntity.name,
|
{
|
||||||
userName: params.userEntity.displayName,
|
credentialName: params.rpEntity.name,
|
||||||
} as NewCredentialParams);
|
userName: params.userEntity.displayName,
|
||||||
|
} as NewCredentialParams,
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should save credential to vault if request confirmed by user", async () => {
|
it("should save credential to vault if request confirmed by user", async () => {
|
||||||
@@ -320,12 +324,15 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
it("should request confirmation from user", async () => {
|
it("should request confirmation from user", async () => {
|
||||||
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipher.id);
|
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipher.id);
|
||||||
|
|
||||||
await authenticator.makeCredential(params);
|
await authenticator.makeCredential(params, new AbortController());
|
||||||
|
|
||||||
expect(userInterface.confirmNewNonDiscoverableCredential).toHaveBeenCalledWith({
|
expect(userInterface.confirmNewNonDiscoverableCredential).toHaveBeenCalledWith(
|
||||||
credentialName: params.rpEntity.name,
|
{
|
||||||
userName: params.userEntity.displayName,
|
credentialName: params.rpEntity.name,
|
||||||
} as NewCredentialParams);
|
userName: params.userEntity.displayName,
|
||||||
|
} as NewCredentialParams,
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should save credential to vault if request confirmed by user", async () => {
|
it("should save credential to vault if request confirmed by user", async () => {
|
||||||
@@ -341,16 +348,18 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
type: CipherType.Login,
|
type: CipherType.Login,
|
||||||
name: existingCipher.name,
|
name: existingCipher.name,
|
||||||
|
|
||||||
fido2Key: expect.objectContaining({
|
login: expect.objectContaining({
|
||||||
nonDiscoverableId: expect.anything(),
|
fido2Key: expect.objectContaining({
|
||||||
keyType: "public-key",
|
nonDiscoverableId: expect.anything(),
|
||||||
keyAlgorithm: "ECDSA",
|
keyType: "public-key",
|
||||||
keyCurve: "P-256",
|
keyAlgorithm: "ECDSA",
|
||||||
rpId: params.rpEntity.id,
|
keyCurve: "P-256",
|
||||||
rpName: params.rpEntity.name,
|
rpId: params.rpEntity.id,
|
||||||
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
|
rpName: params.rpEntity.name,
|
||||||
counter: 0,
|
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
|
||||||
userName: params.userEntity.displayName,
|
counter: 0,
|
||||||
|
userName: params.userEntity.displayName,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -406,7 +415,9 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
);
|
);
|
||||||
cipherService.getAllDecrypted.mockResolvedValue([await cipher]);
|
cipherService.getAllDecrypted.mockResolvedValue([await cipher]);
|
||||||
cipherService.encrypt.mockImplementation(async (cipher) => {
|
cipherService.encrypt.mockImplementation(async (cipher) => {
|
||||||
cipher.fido2Key.nonDiscoverableId = nonDiscoverableId; // Replace id for testability
|
if (!requireResidentKey) {
|
||||||
|
cipher.login.fido2Key.nonDiscoverableId = nonDiscoverableId; // Replace id for testability
|
||||||
|
}
|
||||||
return {} as any;
|
return {} as any;
|
||||||
});
|
});
|
||||||
cipherService.createWithServer.mockImplementation(async (cipher) => {
|
cipherService.createWithServer.mockImplementation(async (cipher) => {
|
||||||
@@ -561,8 +572,8 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
|
|
||||||
it("should throw error if credential exists but rpId does not match", async () => {
|
it("should throw error if credential exists but rpId does not match", async () => {
|
||||||
const cipher = await createCipherView({ type: CipherType.Login });
|
const cipher = await createCipherView({ type: CipherType.Login });
|
||||||
cipher.fido2Key.nonDiscoverableId = credentialId;
|
cipher.login.fido2Key.nonDiscoverableId = credentialId;
|
||||||
cipher.fido2Key.rpId = "mismatch-rpid";
|
cipher.login.fido2Key.rpId = "mismatch-rpid";
|
||||||
cipherService.getAllDecrypted.mockResolvedValue([cipher]);
|
cipherService.getAllDecrypted.mockResolvedValue([cipher]);
|
||||||
|
|
||||||
const result = async () => await authenticator.getAssertion(params);
|
const result = async () => await authenticator.getAssertion(params);
|
||||||
@@ -639,6 +650,7 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
let credentialIds: string[];
|
let credentialIds: string[];
|
||||||
let selectedCredentialId: string;
|
let selectedCredentialId: string;
|
||||||
let ciphers: CipherView[];
|
let ciphers: CipherView[];
|
||||||
|
let fido2Keys: Fido2KeyView[];
|
||||||
let params: Fido2AuthenticatorGetAssertionParams;
|
let params: Fido2AuthenticatorGetAssertionParams;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -654,6 +666,7 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
{ rpId: RpId, counter: 9000, keyValue }
|
{ rpId: RpId, counter: 9000, keyValue }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
fido2Keys = ciphers.map((c) => c.fido2Key);
|
||||||
selectedCredentialId = ciphers[0].id;
|
selectedCredentialId = ciphers[0].id;
|
||||||
params = await createParams({
|
params = await createParams({
|
||||||
allowCredentialDescriptorList: undefined,
|
allowCredentialDescriptorList: undefined,
|
||||||
@@ -666,6 +679,7 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
{ nonDiscoverableId: id, rpId: RpId, counter: 9000 }
|
{ nonDiscoverableId: id, rpId: RpId, counter: 9000 }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
fido2Keys = ciphers.map((c) => c.login.fido2Key);
|
||||||
selectedCredentialId = credentialIds[0];
|
selectedCredentialId = credentialIds[0];
|
||||||
params = await createParams({
|
params = await createParams({
|
||||||
allowCredentialDescriptorList: credentialIds.map((credentialId) => ({
|
allowCredentialDescriptorList: credentialIds.map((credentialId) => ({
|
||||||
@@ -686,15 +700,28 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
|
|
||||||
await authenticator.getAssertion(params);
|
await authenticator.getAssertion(params);
|
||||||
|
|
||||||
expect(cipherService.encrypt).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: ciphers[0].id,
|
|
||||||
fido2Key: expect.objectContaining({
|
|
||||||
counter: 9001,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(cipherService.updateWithServer).toHaveBeenCalledWith(encrypted);
|
expect(cipherService.updateWithServer).toHaveBeenCalledWith(encrypted);
|
||||||
|
if (residentKey) {
|
||||||
|
expect(cipherService.encrypt).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: ciphers[0].id,
|
||||||
|
fido2Key: expect.objectContaining({
|
||||||
|
counter: 9001,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
expect(cipherService.encrypt).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: ciphers[0].id,
|
||||||
|
login: expect.objectContaining({
|
||||||
|
fido2Key: expect.objectContaining({
|
||||||
|
counter: 9001,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return an assertion result", async () => {
|
it("should return an assertion result", async () => {
|
||||||
@@ -707,7 +734,7 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
|
|
||||||
expect(result.selectedCredential.id).toEqual(Utils.guidToRawFormat(selectedCredentialId));
|
expect(result.selectedCredential.id).toEqual(Utils.guidToRawFormat(selectedCredentialId));
|
||||||
expect(result.selectedCredential.userHandle).toEqual(
|
expect(result.selectedCredential.userHandle).toEqual(
|
||||||
Fido2Utils.stringToBuffer(ciphers[0].fido2Key.userHandle)
|
Fido2Utils.stringToBuffer(fido2Keys[0].userHandle)
|
||||||
);
|
);
|
||||||
expect(rpIdHash).toEqual(
|
expect(rpIdHash).toEqual(
|
||||||
new Uint8Array([
|
new Uint8Array([
|
||||||
@@ -779,18 +806,25 @@ function createCipherView(
|
|||||||
cipher.id = data.id ?? Utils.newGuid();
|
cipher.id = data.id ?? Utils.newGuid();
|
||||||
cipher.type = data.type ?? CipherType.Fido2Key;
|
cipher.type = data.type ?? CipherType.Fido2Key;
|
||||||
cipher.localData = {};
|
cipher.localData = {};
|
||||||
cipher.login = data.type ?? data.type === CipherType.Login ? new LoginView() : null;
|
|
||||||
cipher.fido2Key = new Fido2KeyView();
|
const fido2KeyView = new Fido2KeyView();
|
||||||
cipher.fido2Key.nonDiscoverableId = fido2Key.nonDiscoverableId;
|
fido2KeyView.nonDiscoverableId = fido2Key.nonDiscoverableId;
|
||||||
cipher.fido2Key.rpId = fido2Key.rpId ?? RpId;
|
fido2KeyView.rpId = fido2Key.rpId ?? RpId;
|
||||||
cipher.fido2Key.counter = fido2Key.counter ?? 0;
|
fido2KeyView.counter = fido2Key.counter ?? 0;
|
||||||
cipher.fido2Key.userHandle = fido2Key.userHandle ?? Fido2Utils.bufferToString(randomBytes(16));
|
fido2KeyView.userHandle = fido2Key.userHandle ?? Fido2Utils.bufferToString(randomBytes(16));
|
||||||
cipher.fido2Key.keyAlgorithm = fido2Key.keyAlgorithm ?? "ECDSA";
|
fido2KeyView.keyAlgorithm = fido2Key.keyAlgorithm ?? "ECDSA";
|
||||||
cipher.fido2Key.keyCurve = fido2Key.keyCurve ?? "P-256";
|
fido2KeyView.keyCurve = fido2Key.keyCurve ?? "P-256";
|
||||||
cipher.fido2Key.keyValue =
|
fido2KeyView.keyValue =
|
||||||
fido2Key.keyValue ??
|
fido2KeyView.keyValue ??
|
||||||
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTC-7XDZipXbaVBlnkjlBgO16ZmqBZWejK2iYo6lV0dehRANCAASOcM2WduNq1DriRYN7ZekvZz-bRhA-qNT4v0fbp5suUFJyWmgOQ0bybZcLXHaerK5Ep1JiSrQcewtQNgLtry7f";
|
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTC-7XDZipXbaVBlnkjlBgO16ZmqBZWejK2iYo6lV0dehRANCAASOcM2WduNq1DriRYN7ZekvZz-bRhA-qNT4v0fbp5suUFJyWmgOQ0bybZcLXHaerK5Ep1JiSrQcewtQNgLtry7f";
|
||||||
|
|
||||||
|
if (cipher.type === CipherType.Login) {
|
||||||
|
cipher.login = new LoginView();
|
||||||
|
cipher.login.fido2Key = fido2KeyView;
|
||||||
|
} else {
|
||||||
|
cipher.fido2Key = fido2KeyView;
|
||||||
|
}
|
||||||
|
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
private userInterface: Fido2UserInterfaceService
|
private userInterface: Fido2UserInterfaceService
|
||||||
) {}
|
) {}
|
||||||
async makeCredential(
|
async makeCredential(
|
||||||
params: Fido2AuthenticatorMakeCredentialsParams
|
params: Fido2AuthenticatorMakeCredentialsParams,
|
||||||
|
abortController?: AbortController
|
||||||
): Promise<Fido2AuthenticatorMakeCredentialResult> {
|
): Promise<Fido2AuthenticatorMakeCredentialResult> {
|
||||||
if (params.credTypesAndPubKeyAlgs.every((p) => p.alg !== Fido2AlgorithmIdentifier.ES256)) {
|
if (params.credTypesAndPubKeyAlgs.every((p) => p.alg !== Fido2AlgorithmIdentifier.ES256)) {
|
||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotSupported);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotSupported);
|
||||||
@@ -66,19 +67,24 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
{
|
{
|
||||||
credentialName: params.rpEntity.name,
|
credentialName: params.rpEntity.name,
|
||||||
userName: params.userEntity.displayName,
|
userName: params.userEntity.displayName,
|
||||||
}
|
},
|
||||||
|
abortController
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cipher: CipherView;
|
let cipher: CipherView;
|
||||||
|
let fido2Key: Fido2KeyView;
|
||||||
let keyPair: CryptoKeyPair;
|
let keyPair: CryptoKeyPair;
|
||||||
if (params.requireResidentKey) {
|
if (params.requireResidentKey) {
|
||||||
const userVerification = await this.userInterface.confirmNewCredential({
|
const userVerification = await this.userInterface.confirmNewCredential(
|
||||||
credentialName: params.rpEntity.name,
|
{
|
||||||
userName: params.userEntity.displayName,
|
credentialName: params.rpEntity.name,
|
||||||
});
|
userName: params.userEntity.displayName,
|
||||||
|
},
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
if (!userVerification) {
|
if (!userVerification) {
|
||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
||||||
@@ -90,7 +96,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
cipher = new CipherView();
|
cipher = new CipherView();
|
||||||
cipher.type = CipherType.Fido2Key;
|
cipher.type = CipherType.Fido2Key;
|
||||||
cipher.name = params.rpEntity.name;
|
cipher.name = params.rpEntity.name;
|
||||||
cipher.fido2Key = await createKeyView(params, keyPair.privateKey);
|
cipher.fido2Key = fido2Key = await createKeyView(params, keyPair.privateKey);
|
||||||
const encrypted = await this.cipherService.encrypt(cipher);
|
const encrypted = await this.cipherService.encrypt(cipher);
|
||||||
await this.cipherService.createWithServer(encrypted); // encrypted.id is assigned inside here
|
await this.cipherService.createWithServer(encrypted); // encrypted.id is assigned inside here
|
||||||
cipher.id = encrypted.id;
|
cipher.id = encrypted.id;
|
||||||
@@ -98,10 +104,13 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const cipherId = await this.userInterface.confirmNewNonDiscoverableCredential({
|
const cipherId = await this.userInterface.confirmNewNonDiscoverableCredential(
|
||||||
credentialName: params.rpEntity.name,
|
{
|
||||||
userName: params.userEntity.displayName,
|
credentialName: params.rpEntity.name,
|
||||||
});
|
userName: params.userEntity.displayName,
|
||||||
|
},
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
if (cipherId === undefined) {
|
if (cipherId === undefined) {
|
||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
|
||||||
@@ -112,19 +121,21 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
|
|
||||||
const encrypted = await this.cipherService.get(cipherId);
|
const encrypted = await this.cipherService.get(cipherId);
|
||||||
cipher = await encrypted.decrypt();
|
cipher = await encrypted.decrypt();
|
||||||
cipher.fido2Key = await createKeyView(params, keyPair.privateKey);
|
cipher.login.fido2Key = fido2Key = await createKeyView(params, keyPair.privateKey);
|
||||||
const reencrypted = await this.cipherService.encrypt(cipher);
|
const reencrypted = await this.cipherService.encrypt(cipher);
|
||||||
await this.cipherService.updateWithServer(reencrypted);
|
await this.cipherService.updateWithServer(reencrypted);
|
||||||
} catch (error) {
|
} catch {
|
||||||
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown);
|
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentialId = params.requireResidentKey ? cipher.id : cipher.fido2Key.nonDiscoverableId;
|
const credentialId =
|
||||||
|
cipher.type === CipherType.Fido2Key ? cipher.id : cipher.login.fido2Key.nonDiscoverableId;
|
||||||
|
|
||||||
const authData = await generateAuthData({
|
const authData = await generateAuthData({
|
||||||
rpId: params.rpEntity.id,
|
rpId: params.rpEntity.id,
|
||||||
credentialId: Utils.guidToRawFormat(credentialId),
|
credentialId: Utils.guidToRawFormat(credentialId),
|
||||||
counter: cipher.fido2Key.counter,
|
counter: fido2Key.counter,
|
||||||
userPresence: true,
|
userPresence: true,
|
||||||
userVerification: false,
|
userVerification: false,
|
||||||
keyPair,
|
keyPair,
|
||||||
@@ -185,12 +196,16 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const selectedFido2Key =
|
||||||
|
selectedCipher.type === CipherType.Login
|
||||||
|
? selectedCipher.login.fido2Key
|
||||||
|
: selectedCipher.fido2Key;
|
||||||
const selectedCredentialId =
|
const selectedCredentialId =
|
||||||
params.allowCredentialDescriptorList?.length > 0
|
selectedCipher.type === CipherType.Login
|
||||||
? selectedCipher.fido2Key.nonDiscoverableId
|
? selectedFido2Key.nonDiscoverableId
|
||||||
: selectedCipher.id;
|
: selectedCipher.id;
|
||||||
|
|
||||||
++selectedCipher.fido2Key.counter;
|
++selectedFido2Key.counter;
|
||||||
|
|
||||||
selectedCipher.localData = {
|
selectedCipher.localData = {
|
||||||
...selectedCipher.localData,
|
...selectedCipher.localData,
|
||||||
@@ -200,9 +215,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
await this.cipherService.updateWithServer(encrypted);
|
await this.cipherService.updateWithServer(encrypted);
|
||||||
|
|
||||||
const authenticatorData = await generateAuthData({
|
const authenticatorData = await generateAuthData({
|
||||||
rpId: selectedCipher.fido2Key.rpId,
|
rpId: selectedFido2Key.rpId,
|
||||||
credentialId: Utils.guidToRawFormat(selectedCredentialId),
|
credentialId: Utils.guidToRawFormat(selectedCredentialId),
|
||||||
counter: selectedCipher.fido2Key.counter,
|
counter: selectedFido2Key.counter,
|
||||||
userPresence: true,
|
userPresence: true,
|
||||||
userVerification: false,
|
userVerification: false,
|
||||||
});
|
});
|
||||||
@@ -210,14 +225,14 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
const signature = await generateSignature({
|
const signature = await generateSignature({
|
||||||
authData: authenticatorData,
|
authData: authenticatorData,
|
||||||
clientDataHash: params.hash,
|
clientDataHash: params.hash,
|
||||||
privateKey: await getPrivateKeyFromCipher(selectedCipher),
|
privateKey: await getPrivateKeyFromFido2Key(selectedFido2Key),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authenticatorData,
|
authenticatorData,
|
||||||
selectedCredential: {
|
selectedCredential: {
|
||||||
id: Utils.guidToRawFormat(selectedCredentialId),
|
id: Utils.guidToRawFormat(selectedCredentialId),
|
||||||
userHandle: Fido2Utils.stringToBuffer(selectedCipher.fido2Key.userHandle),
|
userHandle: Fido2Utils.stringToBuffer(selectedFido2Key.userHandle),
|
||||||
},
|
},
|
||||||
signature,
|
signature,
|
||||||
};
|
};
|
||||||
@@ -247,8 +262,8 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
(cipher) =>
|
(cipher) =>
|
||||||
(cipher.type === CipherType.Fido2Key && ids.includes(cipher.id)) ||
|
(cipher.type === CipherType.Fido2Key && ids.includes(cipher.id)) ||
|
||||||
(cipher.type === CipherType.Login &&
|
(cipher.type === CipherType.Login &&
|
||||||
cipher.fido2Key != undefined &&
|
cipher.login.fido2Key != undefined &&
|
||||||
ids.includes(cipher.fido2Key.nonDiscoverableId))
|
ids.includes(cipher.login.fido2Key.nonDiscoverableId))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,9 +289,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
|
|||||||
(cipher) =>
|
(cipher) =>
|
||||||
!cipher.isDeleted &&
|
!cipher.isDeleted &&
|
||||||
cipher.type === CipherType.Login &&
|
cipher.type === CipherType.Login &&
|
||||||
cipher.fido2Key != undefined &&
|
cipher.login.fido2Key != undefined &&
|
||||||
cipher.fido2Key.rpId === rpId &&
|
cipher.login.fido2Key.rpId === rpId &&
|
||||||
ids.includes(cipher.fido2Key.nonDiscoverableId)
|
ids.includes(cipher.login.fido2Key.nonDiscoverableId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,14 +339,14 @@ async function createKeyView(
|
|||||||
return fido2Key;
|
return fido2Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPrivateKeyFromCipher(cipher: CipherView): Promise<CryptoKey> {
|
async function getPrivateKeyFromFido2Key(fido2Key: Fido2KeyView): Promise<CryptoKey> {
|
||||||
const keyBuffer = Fido2Utils.stringToBuffer(cipher.fido2Key.keyValue);
|
const keyBuffer = Fido2Utils.stringToBuffer(fido2Key.keyValue);
|
||||||
return await crypto.subtle.importKey(
|
return await crypto.subtle.importKey(
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
keyBuffer,
|
keyBuffer,
|
||||||
{
|
{
|
||||||
name: cipher.fido2Key.keyAlgorithm,
|
name: fido2Key.keyAlgorithm,
|
||||||
namedCurve: cipher.fido2Key.keyCurve,
|
namedCurve: fido2Key.keyCurve,
|
||||||
} as EcKeyImportParams,
|
} as EcKeyImportParams,
|
||||||
true,
|
true,
|
||||||
KeyUsages
|
KeyUsages
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "../abstractions/fido2-user-interface.service.abstraction";
|
import { RequestAbortedError } from "../abstractions/fido2-client.service.abstraction";
|
||||||
import { RequestAbortedError } from "../abstractions/fido2.service.abstraction";
|
import {
|
||||||
|
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||||
|
NewCredentialParams,
|
||||||
|
} from "../abstractions/fido2-user-interface.service.abstraction";
|
||||||
|
|
||||||
export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
|
export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
|
||||||
async confirmCredential(): Promise<boolean> {
|
async confirmCredential(): Promise<boolean> {
|
||||||
@@ -14,7 +17,18 @@ export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstr
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmDuplicateCredential() {
|
async confirmNewNonDiscoverableCredential(
|
||||||
return false;
|
params: NewCredentialParams,
|
||||||
|
abortController?: AbortController
|
||||||
|
): Promise<string> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async informExcludedCredential(
|
||||||
|
existingCipherIds: string[],
|
||||||
|
newCredential: NewCredentialParams,
|
||||||
|
abortController?: AbortController
|
||||||
|
): Promise<void> {
|
||||||
|
// Not Implemented
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user