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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user