1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-04 18:53:20 +00:00

[PM-3807] Store passkeys as array (#6288)

* [PM-3807] feat: store passkeys as array

* [PM-3807] fix: issues in views

* [PM-3807] fix: additional view bugs

* [PM-3807] fix: check array length

* [PM-3807] fix: I secretly like build errors
This commit is contained in:
Andreas Coroiu
2023-09-15 09:45:54 +02:00
committed by GitHub
parent dbbbae2f52
commit 7c4e1f9992
20 changed files with 209 additions and 171 deletions

View File

@@ -134,7 +134,7 @@
</div>
<!--Passkey-->
<div class="box" *ngIf="cipher.login.fido2Key && !cloneMode">
<div class="box" *ngIf="cipher.login.fido2Keys[0] && !cloneMode">
<div class="box-content">
<div class="box-content-row text-muted">
<span class="row-label">{{ "typePasskey" | i18n }}</span>

View File

@@ -203,7 +203,7 @@
</div>
<!--Passkey-->
<div class="box" *ngIf="cipher.login.fido2Key">
<div class="box" *ngIf="cipher.login.fido2Keys[0]">
<div class="box-content">
<div class="box-content-row text-muted">
<span class="row-label">{{ "typePasskey" | i18n }}</span>

View File

@@ -117,7 +117,7 @@
<!--Passkey-->
<div
class="box-content-row text-muted"
*ngIf="cipher.login.fido2Key && !cloneMode"
*ngIf="cipher.login.fido2Keys[0] && !cloneMode"
appBoxRow
>
<span class="row-label">{{ "typePasskey" | i18n }}</span>

View File

@@ -119,7 +119,7 @@
</div>
</div>
<!--Passkey-->
<div class="box-content-row text-muted" *ngIf="cipher.login.fido2Key">
<div class="box-content-row text-muted" *ngIf="cipher.login.fido2Keys[0]">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
</div>

View File

@@ -191,7 +191,7 @@
</button>
</div>
</div>
<ng-container *ngIf="cipher.login.fido2Key">
<ng-container *ngIf="cipher.login.fido2Keys[0]">
<div class="row">
<div class="col-6 form-group">
<label for="loginFido2key">{{ "typePasskey" | i18n }}</label>

View File

@@ -669,7 +669,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async cloneCipher(cipher: CipherView) {
if (cipher.login?.fido2Key) {
if (cipher.login?.fido2Keys.length > 0) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "passkeyNotCopied" },
content: { key: "passkeyNotCopiedAlert" },

View File

@@ -140,15 +140,15 @@ export class ShareComponent implements OnInit, OnDestroy {
}
private async checkFido2KeyExistsInOrg(cipher: CipherView, orgId: string): Promise<boolean> {
if (cipher.type === CipherType.Fido2Key || cipher.login?.fido2Key) {
if (cipher.type === CipherType.Fido2Key || cipher.login?.fido2Keys[0]) {
//Determine if Fido2Key object is disvoverable or non discoverable
const newFido2Key = cipher.login?.fido2Key || cipher.fido2Key;
const newFido2Key = cipher.login?.fido2Keys[0] || cipher.fido2Key;
const ciphers = await this.cipherService.getAllDecrypted();
const exisitingOrgCiphers = ciphers.filter((c) => c.organizationId === orgId);
return exisitingOrgCiphers.some((c) => {
const existingFido2key = c.login?.fido2Key || c.fido2Key;
const existingFido2key = c.login?.fido2Keys[0] || c.fido2Key;
return (
!c.isDeleted &&

View File

@@ -323,8 +323,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
if (this.cloneMode) {
this.cipher.id = null;
if (this.cipher.type === CipherType.Login && this.cipher.login.fido2Key) {
this.cipher.login.fido2Key = null;
if (this.cipher.type === CipherType.Login && this.cipher.login.fido2Keys.length > 0) {
this.cipher.login.fido2Keys = [];
}
}

View File

@@ -155,7 +155,7 @@ export class ViewComponent implements OnDestroy, OnInit {
}
async clone() {
if (this.cipher.login?.fido2Key) {
if (this.cipher.login?.fido2Keys.length > 0) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "passkeyNotCopied" },
content: { key: "passkeyNotCopiedAlert" },

View File

@@ -1,3 +1,5 @@
import { JsonObject } from "type-fest";
import { Fido2KeyApi } from "../../vault/api/fido2-key.api";
import { BaseResponse } from "../response/base.response";
@@ -10,7 +12,7 @@ export class LoginApi extends BaseResponse {
passwordRevisionDate: string;
totp: string;
autofillOnPageLoad: boolean;
fido2Key?: Fido2KeyApi;
fido2Keys?: Fido2KeyApi[];
constructor(data: any = null) {
super(data);
@@ -28,9 +30,9 @@ export class LoginApi extends BaseResponse {
this.uris = uris.map((u: any) => new LoginUriApi(u));
}
const fido2Key = this.getResponseProperty("Fido2Key");
if (fido2Key != null) {
this.fido2Key = new Fido2KeyApi(fido2Key);
const fido2Keys = this.getResponseProperty("Fido2Keys");
if (fido2Keys != null) {
this.fido2Keys = fido2Keys.map((key: JsonObject) => new Fido2KeyApi(key));
}
}
}

View File

@@ -12,7 +12,7 @@ export class LoginExport {
req.username = "jdoe";
req.password = "myp@ssword123";
req.totp = "JBSWY3DPEHPK3PXP";
req.fido2Key = Fido2KeyExport.template();
req.fido2Keys = [Fido2KeyExport.template()];
return req;
}
@@ -23,8 +23,8 @@ export class LoginExport {
view.username = req.username;
view.password = req.password;
view.totp = req.totp;
if (req.fido2Key != null) {
view.fido2Key = Fido2KeyExport.toView(req.fido2Key);
if (req.fido2Keys != null) {
view.fido2Keys = req.fido2Keys.map((key) => Fido2KeyExport.toView(key));
}
return view;
}
@@ -44,7 +44,7 @@ export class LoginExport {
username: string;
password: string;
totp: string;
fido2Key: Fido2KeyExport = null;
fido2Keys: Fido2KeyExport[] = [];
constructor(o?: LoginView | LoginDomain) {
if (o == null) {
@@ -59,8 +59,8 @@ export class LoginExport {
}
}
if (o.fido2Key != null) {
this.fido2Key = new Fido2KeyExport(o.fido2Key);
if (o.fido2Keys != null) {
this.fido2Keys = o.fido2Keys.map((key) => new Fido2KeyExport(key));
}
if (o instanceof LoginView) {

View File

@@ -10,7 +10,7 @@ export class LoginData {
passwordRevisionDate: string;
totp: string;
autofillOnPageLoad: boolean;
fido2Key?: Fido2KeyData;
fido2Keys?: Fido2KeyData[];
constructor(data?: LoginApi) {
if (data == null) {
@@ -27,8 +27,8 @@ export class LoginData {
this.uris = data.uris.map((u) => new LoginUriData(u));
}
if (data.fido2Key) {
this.fido2Key = new Fido2KeyData(data.fido2Key);
if (data.fido2Keys) {
this.fido2Keys = data.fido2Keys?.map((key) => new Fido2KeyData(key));
}
}
}

View File

@@ -76,6 +76,7 @@ describe("Cipher DTO", () => {
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
totp: "EncryptedString",
autofillOnPageLoad: false,
fido2Keys: [],
},
passwordHistory: [
{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" },
@@ -143,6 +144,7 @@ describe("Cipher DTO", () => {
password: { encryptedString: "EncryptedString", encryptionType: 0 },
totp: { encryptedString: "EncryptedString", encryptionType: 0 },
uris: [{ match: 0, uri: { encryptedString: "EncryptedString", encryptionType: 0 } }],
fido2Keys: [],
},
attachments: [
{

View File

@@ -4,10 +4,15 @@ import { Substitute, Arg } from "@fluffy-spoon/substitute";
import { mockEnc, mockFromJson } from "../../../../spec";
import { UriMatchType } from "../../../enums";
import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
import { Fido2KeyApi } from "../../api/fido2-key.api";
import { LoginData } from "../../models/data/login.data";
import { Login } from "../../models/domain/login";
import { LoginUri } from "../../models/domain/login-uri";
import { LoginUriView } from "../../models/view/login-uri.view";
import { Fido2KeyData } from "../data/fido2-key.data";
import { Fido2KeyView } from "../view/fido2-key.view";
import { Fido2Key } from "./fido2-key";
describe("Login DTO", () => {
it("Convert from empty LoginData", () => {
@@ -20,10 +25,12 @@ describe("Login DTO", () => {
username: null,
password: null,
totp: null,
fido2Keys: [],
});
});
it("Convert from full LoginData", () => {
const fido2KeyData = initializeFido2Key(new Fido2KeyData());
const data: LoginData = {
uris: [{ uri: "uri", match: UriMatchType.Domain }],
username: "username",
@@ -31,6 +38,7 @@ describe("Login DTO", () => {
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
totp: "123",
autofillOnPageLoad: false,
fido2Keys: [fido2KeyData],
};
const login = new Login(data);
@@ -41,13 +49,16 @@ describe("Login DTO", () => {
password: { encryptedString: "password", encryptionType: 0 },
totp: { encryptedString: "123", encryptionType: 0 },
uris: [{ match: 0, uri: { encryptedString: "uri", encryptionType: 0 } }],
fido2Keys: [encryptFido2Key(fido2KeyData)],
});
});
it("Initialize without LoginData", () => {
const login = new Login();
expect(login).toEqual({});
expect(login).toEqual({
fido2Keys: [],
});
});
it("Decrypts correctly", async () => {
@@ -57,12 +68,14 @@ describe("Login DTO", () => {
loginUri.decrypt(Arg.any()).resolves(loginUriView);
const login = new Login();
const decryptedFido2Key = Symbol();
login.uris = [loginUri];
login.username = mockEnc("encrypted username");
login.password = mockEnc("encrypted password");
login.passwordRevisionDate = new Date("2022-01-31T12:00:00.000Z");
login.totp = mockEnc("encrypted totp");
login.autofillOnPageLoad = true;
login.fido2Keys = [{ decrypt: jest.fn().mockReturnValue(decryptedFido2Key) } as any];
const loginView = await login.decrypt(null);
expect(loginView).toEqual({
@@ -81,6 +94,7 @@ describe("Login DTO", () => {
},
],
autofillOnPageLoad: true,
fido2Keys: [decryptedFido2Key],
});
});
@@ -92,6 +106,7 @@ describe("Login DTO", () => {
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
totp: "123",
autofillOnPageLoad: false,
fido2Keys: [initializeFido2Key(new Fido2KeyData())],
};
const login = new Login(data);
@@ -112,19 +127,21 @@ describe("Login DTO", () => {
password: "myPassword" as EncryptedString,
passwordRevisionDate: passwordRevisionDate.toISOString(),
totp: "myTotp" as EncryptedString,
fido2Key: {
credentialId: "keyId" as EncryptedString,
keyType: "keyType" as EncryptedString,
keyAlgorithm: "keyAlgorithm" as EncryptedString,
keyCurve: "keyCurve" as EncryptedString,
keyValue: "keyValue" as EncryptedString,
rpId: "rpId" as EncryptedString,
userHandle: "userHandle" as EncryptedString,
counter: "counter" as EncryptedString,
rpName: "rpName" as EncryptedString,
userDisplayName: "userDisplayName" as EncryptedString,
discoverable: "discoverable" as EncryptedString,
},
fido2Keys: [
{
credentialId: "keyId" as EncryptedString,
keyType: "keyType" as EncryptedString,
keyAlgorithm: "keyAlgorithm" as EncryptedString,
keyCurve: "keyCurve" as EncryptedString,
keyValue: "keyValue" as EncryptedString,
rpId: "rpId" as EncryptedString,
userHandle: "userHandle" as EncryptedString,
counter: "counter" as EncryptedString,
rpName: "rpName" as EncryptedString,
userDisplayName: "userDisplayName" as EncryptedString,
discoverable: "discoverable" as EncryptedString,
},
],
});
expect(actual).toEqual({
@@ -133,19 +150,21 @@ describe("Login DTO", () => {
password: "myPassword_fromJSON",
passwordRevisionDate: passwordRevisionDate,
totp: "myTotp_fromJSON",
fido2Key: {
credentialId: "keyId_fromJSON",
keyType: "keyType_fromJSON",
keyAlgorithm: "keyAlgorithm_fromJSON",
keyCurve: "keyCurve_fromJSON",
keyValue: "keyValue_fromJSON",
rpId: "rpId_fromJSON",
userHandle: "userHandle_fromJSON",
counter: "counter_fromJSON",
rpName: "rpName_fromJSON",
userDisplayName: "userDisplayName_fromJSON",
discoverable: "discoverable_fromJSON",
},
fido2Keys: [
{
credentialId: "keyId_fromJSON",
keyType: "keyType_fromJSON",
keyAlgorithm: "keyAlgorithm_fromJSON",
keyCurve: "keyCurve_fromJSON",
keyValue: "keyValue_fromJSON",
rpId: "rpId_fromJSON",
userHandle: "userHandle_fromJSON",
counter: "counter_fromJSON",
rpName: "rpName_fromJSON",
userDisplayName: "userDisplayName_fromJSON",
discoverable: "discoverable_fromJSON",
},
],
});
expect(actual).toBeInstanceOf(Login);
});
@@ -155,3 +174,38 @@ describe("Login DTO", () => {
});
});
});
type Fido2KeyLike = Fido2KeyData | Fido2KeyView | Fido2KeyApi;
function initializeFido2Key<T extends Fido2KeyLike>(key: T): T {
key.credentialId = "credentialId";
key.keyType = "public-key";
key.keyAlgorithm = "ECDSA";
key.keyCurve = "P-256";
key.keyValue = "keyValue";
key.rpId = "rpId";
key.userHandle = "userHandle";
key.counter = "counter";
key.rpName = "rpName";
key.userDisplayName = "userDisplayName";
key.discoverable = "discoverable";
return key;
}
function encryptFido2Key(key: Fido2KeyLike): Fido2Key {
const encrypted = new Fido2Key();
encrypted.credentialId = { encryptedString: key.credentialId, encryptionType: 0 } as EncString;
encrypted.keyType = { encryptedString: key.keyType, encryptionType: 0 } as EncString;
encrypted.keyAlgorithm = { encryptedString: key.keyAlgorithm, encryptionType: 0 } as EncString;
encrypted.keyCurve = { encryptedString: key.keyCurve, encryptionType: 0 } as EncString;
encrypted.keyValue = { encryptedString: key.keyValue, encryptionType: 0 } as EncString;
encrypted.rpId = { encryptedString: key.rpId, encryptionType: 0 } as EncString;
encrypted.userHandle = { encryptedString: key.userHandle, encryptionType: 0 } as EncString;
encrypted.counter = { encryptedString: key.counter, encryptionType: 0 } as EncString;
encrypted.rpName = { encryptedString: key.rpName, encryptionType: 0 } as EncString;
encrypted.userDisplayName = {
encryptedString: key.userDisplayName,
encryptionType: 0,
} as EncString;
encrypted.discoverable = { encryptedString: key.discoverable, encryptionType: 0 } as EncString;
return encrypted;
}

View File

@@ -16,7 +16,7 @@ export class Login extends Domain {
passwordRevisionDate?: Date;
totp: EncString;
autofillOnPageLoad: boolean;
fido2Key: Fido2Key;
fido2Keys: Fido2Key[] = [];
constructor(obj?: LoginData) {
super();
@@ -45,8 +45,8 @@ export class Login extends Domain {
});
}
if (obj.fido2Key) {
this.fido2Key = new Fido2Key(obj.fido2Key);
if (obj.fido2Keys) {
this.fido2Keys = obj.fido2Keys.map((key) => new Fido2Key(key));
}
}
@@ -70,8 +70,8 @@ export class Login extends Domain {
}
}
if (this.fido2Key != null) {
view.fido2Key = await this.fido2Key.decrypt(orgId, encKey);
if (this.fido2Keys != null) {
view.fido2Keys = await Promise.all(this.fido2Keys.map((key) => key.decrypt(orgId, encKey)));
}
return view;
@@ -95,9 +95,7 @@ export class Login extends Domain {
});
}
if (this.fido2Key != null) {
l.fido2Key = this.fido2Key.toFido2KeyData();
}
l.fido2Keys = this.fido2Keys.map((key) => key.toFido2KeyData());
return l;
}
@@ -113,7 +111,7 @@ export class Login extends Domain {
const passwordRevisionDate =
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
const uris = obj.uris?.map((uri: any) => LoginUri.fromJSON(uri));
const fido2Key = obj.fido2Key == null ? null : Fido2Key.fromJSON(obj.fido2Key);
const fido2Keys = obj.fido2Keys?.map((key) => Fido2Key.fromJSON(key)) ?? [];
return Object.assign(new Login(), obj, {
username,
@@ -121,7 +119,7 @@ export class Login extends Domain {
totp,
passwordRevisionDate,
uris,
fido2Key,
fido2Keys,
});
}
}

View File

@@ -63,50 +63,28 @@ export class CipherRequest {
});
}
if (cipher.login.fido2Key != null) {
this.login.fido2Key = new Fido2KeyApi();
this.login.fido2Key.credentialId =
cipher.login.fido2Key.credentialId != null
? cipher.login.fido2Key.credentialId.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.counter =
cipher.login.fido2Key.counter != null
? cipher.login.fido2Key.counter.encryptedString
: null;
this.login.fido2Key.userHandle =
cipher.login.fido2Key.userHandle != null
? cipher.login.fido2Key.userHandle.encryptedString
: null;
this.login.fido2Key.userDisplayName =
cipher.login.fido2Key.userDisplayName != null
? cipher.login.fido2Key.userDisplayName.encryptedString
: null;
this.login.fido2Key.discoverable =
cipher.login.fido2Key.discoverable != null
? cipher.login.fido2Key.discoverable.encryptedString
: null;
if (cipher.login.fido2Keys != null) {
this.login.fido2Keys = cipher.login.fido2Keys.map((key) => {
const keyApi = new Fido2KeyApi();
keyApi.credentialId =
key.credentialId != null ? key.credentialId.encryptedString : null;
keyApi.keyType =
key.keyType != null ? (key.keyType.encryptedString as "public-key") : null;
keyApi.keyAlgorithm =
key.keyAlgorithm != null ? (key.keyAlgorithm.encryptedString as "ECDSA") : null;
keyApi.keyCurve =
key.keyCurve != null ? (key.keyCurve.encryptedString as "P-256") : null;
keyApi.keyValue = key.keyValue != null ? key.keyValue.encryptedString : null;
keyApi.rpId = key.rpId != null ? key.rpId.encryptedString : null;
keyApi.rpName = key.rpName != null ? key.rpName.encryptedString : null;
keyApi.counter = key.counter != null ? key.counter.encryptedString : null;
keyApi.userHandle = key.userHandle != null ? key.userHandle.encryptedString : null;
keyApi.userDisplayName =
key.userDisplayName != null ? key.userDisplayName.encryptedString : null;
keyApi.discoverable =
key.discoverable != null ? key.discoverable.encryptedString : null;
return keyApi;
});
}
break;
case CipherType.SecureNote:

View File

@@ -19,7 +19,7 @@ export class LoginView extends ItemView {
totp: string = null;
uris: LoginUriView[] = null;
autofillOnPageLoad: boolean = null;
fido2Key?: Fido2KeyView;
fido2Keys: Fido2KeyView[] = [];
constructor(l?: Login) {
super();
@@ -81,12 +81,12 @@ export class LoginView extends ItemView {
const passwordRevisionDate =
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
const uris = obj.uris?.map((uri: any) => LoginUriView.fromJSON(uri));
const fido2Key = obj.fido2Key == null ? null : Fido2KeyView.fromJSON(obj.fido2Key);
const fido2Keys = obj.fido2Keys?.map((key) => Fido2KeyView.fromJSON(key)) ?? [];
return Object.assign(new LoginView(), obj, {
passwordRevisionDate,
uris,
fido2Key,
fido2Keys,
});
}
}

View File

@@ -1089,32 +1089,34 @@ export class CipherService implements CipherServiceAbstraction {
}
}
if (model.login.fido2Key != null) {
cipher.login.fido2Key = new Fido2Key();
await this.encryptObjProperty(
model.login.fido2Key,
cipher.login.fido2Key,
{
credentialId: null,
keyType: null,
keyAlgorithm: null,
keyCurve: null,
keyValue: null,
rpId: null,
rpName: null,
userHandle: null,
userDisplayName: null,
origin: null,
},
key
);
cipher.login.fido2Key.counter = await this.cryptoService.encrypt(
String(model.login.fido2Key.counter),
key
);
cipher.login.fido2Key.discoverable = await this.cryptoService.encrypt(
String(model.login.fido2Key.discoverable),
key
if (model.login.fido2Keys != null) {
cipher.login.fido2Keys = await Promise.all(
model.login.fido2Keys.map(async (viewKey) => {
const domainKey = new Fido2Key();
await this.encryptObjProperty(
viewKey,
domainKey,
{
credentialId: null,
keyType: null,
keyAlgorithm: null,
keyCurve: null,
keyValue: null,
rpId: null,
rpName: null,
userHandle: null,
userDisplayName: null,
origin: null,
},
key
);
domainKey.counter = await this.cryptoService.encrypt(String(viewKey.counter), key);
domainKey.discoverable = await this.cryptoService.encrypt(
String(viewKey.discoverable),
key
);
return domainKey;
})
);
}
return;

View File

@@ -116,7 +116,7 @@ describe("FidoAuthenticatorService", () => {
params = await createParams({
excludeCredentialDescriptorList: [
{
id: guidToRawFormat(excludedCipher.login.fido2Key.credentialId),
id: guidToRawFormat(excludedCipher.login.fido2Keys[0].credentialId),
type: "public-key",
},
],
@@ -237,18 +237,20 @@ describe("FidoAuthenticatorService", () => {
name: existingCipher.name,
login: expect.objectContaining({
fido2Key: expect.objectContaining({
credentialId: expect.anything(),
keyType: "public-key",
keyAlgorithm: "ECDSA",
keyCurve: "P-256",
rpId: params.rpEntity.id,
rpName: params.rpEntity.name,
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
counter: 0,
userDisplayName: params.userEntity.displayName,
discoverable: false,
}),
fido2Keys: [
expect.objectContaining({
credentialId: expect.anything(),
keyType: "public-key",
keyAlgorithm: "ECDSA",
keyCurve: "P-256",
rpId: params.rpEntity.id,
rpName: params.rpEntity.name,
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
counter: 0,
userDisplayName: params.userEntity.displayName,
discoverable: false,
}),
],
}),
})
);
@@ -305,7 +307,7 @@ describe("FidoAuthenticatorService", () => {
);
cipherService.getAllDecrypted.mockResolvedValue([await cipher]);
cipherService.encrypt.mockImplementation(async (cipher) => {
cipher.login.fido2Key.credentialId = credentialId; // Replace id for testability
cipher.login.fido2Keys[0].credentialId = credentialId; // Replace id for testability
return {} as any;
});
cipherService.createWithServer.mockImplementation(async (cipher) => {
@@ -467,8 +469,8 @@ describe("FidoAuthenticatorService", () => {
it("should inform user if credential exists but rpId does not match", async () => {
const cipher = await createCipherView({ type: CipherType.Login });
cipher.login.fido2Key.credentialId = credentialId;
cipher.login.fido2Key.rpId = "mismatch-rpid";
cipher.login.fido2Keys[0].credentialId = credentialId;
cipher.login.fido2Keys[0].rpId = "mismatch-rpid";
cipherService.getAllDecrypted.mockResolvedValue([cipher]);
userInterfaceSession.informCredentialNotFound.mockResolvedValue();
@@ -543,7 +545,7 @@ describe("FidoAuthenticatorService", () => {
it("should only ask for discoverable credentials matched by rpId when params does not contains allowedCredentials list", async () => {
params.allowCredentialDescriptorList = undefined;
const discoverableCiphers = ciphers.filter((c) => c.login.fido2Key.discoverable);
const discoverableCiphers = ciphers.filter((c) => c.login.fido2Keys[0].discoverable);
userInterfaceSession.pickCredential.mockResolvedValue({
cipherId: discoverableCiphers[0].id,
userVerified: false,
@@ -608,7 +610,7 @@ describe("FidoAuthenticatorService", () => {
{ credentialId: id, rpId: RpId, counter: 9000, keyValue }
)
);
fido2Keys = ciphers.map((c) => c.login.fido2Key);
fido2Keys = ciphers.map((c) => c.login.fido2Keys[0]);
selectedCredentialId = credentialIds[0];
params = await createParams({
allowCredentialDescriptorList: credentialIds.map((credentialId) => ({
@@ -638,9 +640,11 @@ describe("FidoAuthenticatorService", () => {
expect.objectContaining({
id: ciphers[0].id,
login: expect.objectContaining({
fido2Key: expect.objectContaining({
counter: 9001,
}),
fido2Keys: [
expect.objectContaining({
counter: 9001,
}),
],
}),
})
);
@@ -761,7 +765,7 @@ function createCipherView(
if (cipher.type === CipherType.Login) {
cipher.login = new LoginView();
cipher.login.fido2Key = fido2KeyView;
cipher.login.fido2Keys = [fido2KeyView];
} else {
cipher.fido2Key = fido2KeyView;
}

View File

@@ -131,10 +131,11 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
const encrypted = await this.cipherService.get(cipherId);
cipher = await encrypted.decrypt();
cipher.login.fido2Key = fido2Key = await createKeyView(params, keyPair.privateKey);
fido2Key = await createKeyView(params, keyPair.privateKey);
cipher.login.fido2Keys = [fido2Key];
const reencrypted = await this.cipherService.encrypt(cipher);
await this.cipherService.updateWithServer(reencrypted);
credentialId = cipher.login.fido2Key.credentialId;
credentialId = fido2Key.credentialId;
} catch (error) {
this.logService?.error(
`[Fido2Authenticator] Aborting because of unknown error when creating credential: ${error}`
@@ -233,10 +234,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
}
try {
const selectedFido2Key =
selectedCipher.type === CipherType.Login
? selectedCipher.login.fido2Key
: selectedCipher.fido2Key;
const selectedFido2Key = selectedCipher.login.fido2Keys[0];
const selectedCredentialId = selectedFido2Key.credentialId;
++selectedFido2Key.counter;
@@ -310,8 +308,8 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
!cipher.isDeleted &&
cipher.organizationId == undefined &&
cipher.type === CipherType.Login &&
cipher.login.fido2Key != undefined &&
ids.includes(cipher.login.fido2Key.credentialId)
cipher.login.fido2Keys.length > 0 &&
ids.includes(cipher.login.fido2Keys[0].credentialId)
)
.map((cipher) => cipher.id);
}
@@ -343,9 +341,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
(cipher) =>
!cipher.isDeleted &&
cipher.type === CipherType.Login &&
cipher.login.fido2Key != undefined &&
cipher.login.fido2Key.rpId === rpId &&
ids.includes(cipher.login.fido2Key.credentialId)
cipher.login.fido2Keys.length > 0 &&
cipher.login.fido2Keys[0].rpId === rpId &&
ids.includes(cipher.login.fido2Keys[0].credentialId)
);
}
@@ -360,9 +358,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
(cipher) =>
!cipher.isDeleted &&
cipher.type === CipherType.Login &&
cipher.login.fido2Key != undefined &&
cipher.login.fido2Key.rpId === rpId &&
cipher.login.fido2Key.discoverable
cipher.login.fido2Keys.length > 0 &&
cipher.login.fido2Keys[0].rpId === rpId &&
cipher.login.fido2Keys[0].discoverable
);
}
}