1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-20 21:01:29 +01:00

[EC-598] feat: fully wokring non-discoverable implementation

This commit is contained in:
Andreas Coroiu 2023-04-04 16:21:43 +02:00
parent 9dfd85dcd7
commit 55cd736ec3
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
13 changed files with 313 additions and 93 deletions

View File

@ -47,6 +47,15 @@ export type BrowserFido2Message = { requestId: string } & (
| {
type: "ConfirmNewCredentialResponse";
}
| {
type: "ConfirmNewNonDiscoverableCredentialRequest";
credentialName: string;
userName: string;
}
| {
type: "ConfirmNewNonDiscoverableCredentialResponse";
cipherId: string;
}
| {
type: "AbortRequest";
}
@ -201,10 +210,47 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
}
async confirmNewNonDiscoverableCredential(
params: NewCredentialParams,
{ credentialName, userName }: NewCredentialParams,
abortController?: AbortController
): 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(

View File

@ -11,7 +11,12 @@
Authenticate
</button>
</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:
<div class="box list">
<div class="box-content">

View File

@ -48,6 +48,10 @@ export class Fido2Component implements OnInit, OnDestroy {
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$)
@ -66,11 +70,19 @@ export class Fido2Component implements OnInit, OnDestroy {
}
async pick(cipher: CipherView) {
BrowserFido2UserInterfaceService.sendMessage({
requestId: this.data.requestId,
cipherId: cipher.id,
type: "PickCredentialResponse",
});
if (this.data?.type === "PickCredentialRequest") {
BrowserFido2UserInterfaceService.sendMessage({
requestId: this.data.requestId,
cipherId: cipher.id,
type: "PickCredentialResponse",
});
} else if (this.data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
BrowserFido2UserInterfaceService.sendMessage({
requestId: this.data.requestId,
cipherId: cipher.id,
type: "ConfirmNewNonDiscoverableCredentialResponse",
});
}
window.close();
}

View File

@ -1,3 +1,4 @@
import { Fido2KeyApi } from "../../webauthn/models/api/fido2-key.api";
import { BaseResponse } from "../response/base.response";
import { LoginUriApi } from "./login-uri.api";
@ -9,6 +10,7 @@ export class LoginApi extends BaseResponse {
passwordRevisionDate: string;
totp: string;
autofillOnPageLoad: boolean;
fido2Key?: Fido2KeyApi;
constructor(data: any = null) {
super(data);
@ -25,5 +27,10 @@ export class LoginApi extends BaseResponse {
if (uris != null) {
this.uris = uris.map((u: any) => new LoginUriApi(u));
}
const fido2Key = this.getResponseProperty("Fido2Key");
if (fido2Key != null) {
this.fido2Key = new Fido2KeyApi(fido2Key);
}
}
}

View File

@ -60,6 +60,8 @@ export class CipherData {
switch (this.type) {
case CipherType.Login:
this.login = new LoginData(response.login);
this.fido2Key =
response.fido2Key != undefined ? new Fido2KeyData(response.fido2Key) : undefined;
break;
case CipherType.SecureNote:
this.secureNote = new SecureNoteData(response.secureNote);

View File

@ -1,4 +1,5 @@
import { LoginApi } from "../../../models/api/login.api";
import { Fido2KeyData } from "../../../webauthn/models/data/fido2-key.data";
import { LoginUriData } from "./login-uri.data";
@ -9,6 +10,7 @@ export class LoginData {
passwordRevisionDate: string;
totp: string;
autofillOnPageLoad: boolean;
fido2Key?: Fido2KeyData;
constructor(data?: LoginApi) {
if (data == null) {
@ -24,5 +26,9 @@ export class LoginData {
if (data.uris) {
this.uris = data.uris.map((u) => new LoginUriData(u));
}
if (data.fido2Key) {
this.fido2Key = new Fido2KeyData(data.fido2Key);
}
}
}

View File

@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
import Domain from "../../../models/domain/domain-base";
import { EncString } from "../../../models/domain/enc-string";
import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key";
import { Fido2Key } from "../../../webauthn/models/domain/fido2-key";
import { LoginData } from "../data/login.data";
import { LoginView } from "../view/login.view";
@ -15,6 +16,7 @@ export class Login extends Domain {
passwordRevisionDate?: Date;
totp: EncString;
autofillOnPageLoad: boolean;
fido2Key: Fido2Key;
constructor(obj?: LoginData) {
super();
@ -42,6 +44,10 @@ export class Login extends Domain {
this.uris.push(new LoginUri(u));
});
}
if (obj.fido2Key) {
this.fido2Key = new Fido2Key(obj.fido2Key);
}
}
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;
}
@ -85,6 +95,10 @@ export class Login extends Domain {
});
}
if (this.fido2Key != null) {
l.fido2Key = this.fido2Key.toFido2KeyData();
}
return l;
}
@ -99,13 +113,15 @@ 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);
return Object.assign(new Login(), obj, {
username,
password,
totp,
passwordRevisionDate: passwordRevisionDate,
uris: uris,
passwordRevisionDate,
uris,
fido2Key,
});
}
}

View File

@ -63,6 +63,44 @@ export class CipherRequest {
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;
case CipherType.SecureNote:
this.secureNote = new SecureNoteApi();

View File

@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
import { LoginLinkedId as LinkedId } from "../../../enums/linkedIdType";
import { linkedFieldOption } from "../../../misc/linkedFieldOption.decorator";
import { Utils } from "../../../misc/utils";
import { Fido2KeyView } from "../../../webauthn/models/view/fido2-key.view";
import { Login } from "../domain/login";
import { ItemView } from "./item.view";
@ -18,6 +19,7 @@ export class LoginView extends ItemView {
totp: string = null;
uris: LoginUriView[] = null;
autofillOnPageLoad: boolean = null;
fido2Key?: Fido2KeyView;
constructor(l?: Login) {
super();
@ -67,10 +69,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);
return Object.assign(new LoginView(), obj, {
passwordRevisionDate: passwordRevisionDate,
uris: uris,
uris,
fido2Key,
});
}
}

View File

@ -1117,6 +1117,27 @@ export class CipherService implements CipherServiceAbstraction {
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;
case CipherType.SecureNote:
cipher.secureNote = new SecureNote();

View File

@ -104,7 +104,7 @@ describe("FidoAuthenticatorService", () => {
params = await createParams({
excludeCredentialDescriptorList: [
{
id: Utils.guidToRawFormat(excludedCipher.fido2Key.nonDiscoverableId),
id: Utils.guidToRawFormat(excludedCipher.login.fido2Key.nonDiscoverableId),
type: "public-key",
},
],
@ -162,15 +162,16 @@ describe("FidoAuthenticatorService", () => {
let params: Fido2AuthenticatorMakeCredentialsParams;
beforeEach(async () => {
const excludedCipher = createCipherView();
excludedCipherView = await excludedCipher;
excludedCipherView = createCipherView();
params = await createParams({
excludeCredentialDescriptorList: [
{ id: Utils.guidToRawFormat(excludedCipher.id), type: "public-key" },
{ id: Utils.guidToRawFormat(excludedCipherView.id), type: "public-key" },
],
});
cipherService.get.mockImplementation(async (id) =>
id === excludedCipher.id ? excludedCipher : undefined
id === excludedCipherView.id
? ({ decrypt: async () => excludedCipherView } as any)
: undefined
);
cipherService.getAllDecrypted.mockResolvedValue([excludedCipherView]);
});
@ -237,12 +238,15 @@ describe("FidoAuthenticatorService", () => {
return cipher;
});
await authenticator.makeCredential(params);
await authenticator.makeCredential(params, new AbortController());
expect(userInterface.confirmNewCredential).toHaveBeenCalledWith({
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
} as NewCredentialParams);
expect(userInterface.confirmNewCredential).toHaveBeenCalledWith(
{
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
} as NewCredentialParams,
expect.anything()
);
});
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 () => {
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipher.id);
await authenticator.makeCredential(params);
await authenticator.makeCredential(params, new AbortController());
expect(userInterface.confirmNewNonDiscoverableCredential).toHaveBeenCalledWith({
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
} as NewCredentialParams);
expect(userInterface.confirmNewNonDiscoverableCredential).toHaveBeenCalledWith(
{
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
} as NewCredentialParams,
expect.anything()
);
});
it("should save credential to vault if request confirmed by user", async () => {
@ -341,16 +348,18 @@ describe("FidoAuthenticatorService", () => {
type: CipherType.Login,
name: existingCipher.name,
fido2Key: expect.objectContaining({
nonDiscoverableId: 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,
userName: params.userEntity.displayName,
login: expect.objectContaining({
fido2Key: expect.objectContaining({
nonDiscoverableId: 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,
userName: params.userEntity.displayName,
}),
}),
})
);
@ -406,7 +415,9 @@ describe("FidoAuthenticatorService", () => {
);
cipherService.getAllDecrypted.mockResolvedValue([await 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;
});
cipherService.createWithServer.mockImplementation(async (cipher) => {
@ -561,8 +572,8 @@ describe("FidoAuthenticatorService", () => {
it("should throw error if credential exists but rpId does not match", async () => {
const cipher = await createCipherView({ type: CipherType.Login });
cipher.fido2Key.nonDiscoverableId = credentialId;
cipher.fido2Key.rpId = "mismatch-rpid";
cipher.login.fido2Key.nonDiscoverableId = credentialId;
cipher.login.fido2Key.rpId = "mismatch-rpid";
cipherService.getAllDecrypted.mockResolvedValue([cipher]);
const result = async () => await authenticator.getAssertion(params);
@ -639,6 +650,7 @@ describe("FidoAuthenticatorService", () => {
let credentialIds: string[];
let selectedCredentialId: string;
let ciphers: CipherView[];
let fido2Keys: Fido2KeyView[];
let params: Fido2AuthenticatorGetAssertionParams;
beforeEach(async () => {
@ -654,6 +666,7 @@ describe("FidoAuthenticatorService", () => {
{ rpId: RpId, counter: 9000, keyValue }
)
);
fido2Keys = ciphers.map((c) => c.fido2Key);
selectedCredentialId = ciphers[0].id;
params = await createParams({
allowCredentialDescriptorList: undefined,
@ -666,6 +679,7 @@ describe("FidoAuthenticatorService", () => {
{ nonDiscoverableId: id, rpId: RpId, counter: 9000 }
)
);
fido2Keys = ciphers.map((c) => c.login.fido2Key);
selectedCredentialId = credentialIds[0];
params = await createParams({
allowCredentialDescriptorList: credentialIds.map((credentialId) => ({
@ -686,15 +700,28 @@ describe("FidoAuthenticatorService", () => {
await authenticator.getAssertion(params);
expect(cipherService.encrypt).toHaveBeenCalledWith(
expect.objectContaining({
id: ciphers[0].id,
fido2Key: expect.objectContaining({
counter: 9001,
}),
})
);
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 () => {
@ -707,7 +734,7 @@ describe("FidoAuthenticatorService", () => {
expect(result.selectedCredential.id).toEqual(Utils.guidToRawFormat(selectedCredentialId));
expect(result.selectedCredential.userHandle).toEqual(
Fido2Utils.stringToBuffer(ciphers[0].fido2Key.userHandle)
Fido2Utils.stringToBuffer(fido2Keys[0].userHandle)
);
expect(rpIdHash).toEqual(
new Uint8Array([
@ -779,18 +806,25 @@ function createCipherView(
cipher.id = data.id ?? Utils.newGuid();
cipher.type = data.type ?? CipherType.Fido2Key;
cipher.localData = {};
cipher.login = data.type ?? data.type === CipherType.Login ? new LoginView() : null;
cipher.fido2Key = new Fido2KeyView();
cipher.fido2Key.nonDiscoverableId = fido2Key.nonDiscoverableId;
cipher.fido2Key.rpId = fido2Key.rpId ?? RpId;
cipher.fido2Key.counter = fido2Key.counter ?? 0;
cipher.fido2Key.userHandle = fido2Key.userHandle ?? Fido2Utils.bufferToString(randomBytes(16));
cipher.fido2Key.keyAlgorithm = fido2Key.keyAlgorithm ?? "ECDSA";
cipher.fido2Key.keyCurve = fido2Key.keyCurve ?? "P-256";
cipher.fido2Key.keyValue =
fido2Key.keyValue ??
const fido2KeyView = new Fido2KeyView();
fido2KeyView.nonDiscoverableId = fido2Key.nonDiscoverableId;
fido2KeyView.rpId = fido2Key.rpId ?? RpId;
fido2KeyView.counter = fido2Key.counter ?? 0;
fido2KeyView.userHandle = fido2Key.userHandle ?? Fido2Utils.bufferToString(randomBytes(16));
fido2KeyView.keyAlgorithm = fido2Key.keyAlgorithm ?? "ECDSA";
fido2KeyView.keyCurve = fido2Key.keyCurve ?? "P-256";
fido2KeyView.keyValue =
fido2KeyView.keyValue ??
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTC-7XDZipXbaVBlnkjlBgO16ZmqBZWejK2iYo6lV0dehRANCAASOcM2WduNq1DriRYN7ZekvZz-bRhA-qNT4v0fbp5suUFJyWmgOQ0bybZcLXHaerK5Ep1JiSrQcewtQNgLtry7f";
if (cipher.type === CipherType.Login) {
cipher.login = new LoginView();
cipher.login.fido2Key = fido2KeyView;
} else {
cipher.fido2Key = fido2KeyView;
}
return cipher;
}

View File

@ -38,7 +38,8 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
private userInterface: Fido2UserInterfaceService
) {}
async makeCredential(
params: Fido2AuthenticatorMakeCredentialsParams
params: Fido2AuthenticatorMakeCredentialsParams,
abortController?: AbortController
): Promise<Fido2AuthenticatorMakeCredentialResult> {
if (params.credTypesAndPubKeyAlgs.every((p) => p.alg !== Fido2AlgorithmIdentifier.ES256)) {
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotSupported);
@ -66,19 +67,24 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
{
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
}
},
abortController
);
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
}
let cipher: CipherView;
let fido2Key: Fido2KeyView;
let keyPair: CryptoKeyPair;
if (params.requireResidentKey) {
const userVerification = await this.userInterface.confirmNewCredential({
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
});
const userVerification = await this.userInterface.confirmNewCredential(
{
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
},
abortController
);
if (!userVerification) {
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
@ -90,7 +96,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
cipher = new CipherView();
cipher.type = CipherType.Fido2Key;
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);
await this.cipherService.createWithServer(encrypted); // encrypted.id is assigned inside here
cipher.id = encrypted.id;
@ -98,10 +104,13 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Unknown);
}
} else {
const cipherId = await this.userInterface.confirmNewNonDiscoverableCredential({
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
});
const cipherId = await this.userInterface.confirmNewNonDiscoverableCredential(
{
credentialName: params.rpEntity.name,
userName: params.userEntity.displayName,
},
abortController
);
if (cipherId === undefined) {
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
@ -112,19 +121,21 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
const encrypted = await this.cipherService.get(cipherId);
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);
await this.cipherService.updateWithServer(reencrypted);
} catch (error) {
} catch {
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({
rpId: params.rpEntity.id,
credentialId: Utils.guidToRawFormat(credentialId),
counter: cipher.fido2Key.counter,
counter: fido2Key.counter,
userPresence: true,
userVerification: false,
keyPair,
@ -185,12 +196,16 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
}
try {
const selectedFido2Key =
selectedCipher.type === CipherType.Login
? selectedCipher.login.fido2Key
: selectedCipher.fido2Key;
const selectedCredentialId =
params.allowCredentialDescriptorList?.length > 0
? selectedCipher.fido2Key.nonDiscoverableId
selectedCipher.type === CipherType.Login
? selectedFido2Key.nonDiscoverableId
: selectedCipher.id;
++selectedCipher.fido2Key.counter;
++selectedFido2Key.counter;
selectedCipher.localData = {
...selectedCipher.localData,
@ -200,9 +215,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
await this.cipherService.updateWithServer(encrypted);
const authenticatorData = await generateAuthData({
rpId: selectedCipher.fido2Key.rpId,
rpId: selectedFido2Key.rpId,
credentialId: Utils.guidToRawFormat(selectedCredentialId),
counter: selectedCipher.fido2Key.counter,
counter: selectedFido2Key.counter,
userPresence: true,
userVerification: false,
});
@ -210,14 +225,14 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
const signature = await generateSignature({
authData: authenticatorData,
clientDataHash: params.hash,
privateKey: await getPrivateKeyFromCipher(selectedCipher),
privateKey: await getPrivateKeyFromFido2Key(selectedFido2Key),
});
return {
authenticatorData,
selectedCredential: {
id: Utils.guidToRawFormat(selectedCredentialId),
userHandle: Fido2Utils.stringToBuffer(selectedCipher.fido2Key.userHandle),
userHandle: Fido2Utils.stringToBuffer(selectedFido2Key.userHandle),
},
signature,
};
@ -247,8 +262,8 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
(cipher) =>
(cipher.type === CipherType.Fido2Key && ids.includes(cipher.id)) ||
(cipher.type === CipherType.Login &&
cipher.fido2Key != undefined &&
ids.includes(cipher.fido2Key.nonDiscoverableId))
cipher.login.fido2Key != undefined &&
ids.includes(cipher.login.fido2Key.nonDiscoverableId))
);
}
@ -274,9 +289,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
(cipher) =>
!cipher.isDeleted &&
cipher.type === CipherType.Login &&
cipher.fido2Key != undefined &&
cipher.fido2Key.rpId === rpId &&
ids.includes(cipher.fido2Key.nonDiscoverableId)
cipher.login.fido2Key != undefined &&
cipher.login.fido2Key.rpId === rpId &&
ids.includes(cipher.login.fido2Key.nonDiscoverableId)
);
}
@ -324,14 +339,14 @@ async function createKeyView(
return fido2Key;
}
async function getPrivateKeyFromCipher(cipher: CipherView): Promise<CryptoKey> {
const keyBuffer = Fido2Utils.stringToBuffer(cipher.fido2Key.keyValue);
async function getPrivateKeyFromFido2Key(fido2Key: Fido2KeyView): Promise<CryptoKey> {
const keyBuffer = Fido2Utils.stringToBuffer(fido2Key.keyValue);
return await crypto.subtle.importKey(
"pkcs8",
keyBuffer,
{
name: cipher.fido2Key.keyAlgorithm,
namedCurve: cipher.fido2Key.keyCurve,
name: fido2Key.keyAlgorithm,
namedCurve: fido2Key.keyCurve,
} as EcKeyImportParams,
true,
KeyUsages

View File

@ -1,5 +1,8 @@
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "../abstractions/fido2-user-interface.service.abstraction";
import { RequestAbortedError } from "../abstractions/fido2.service.abstraction";
import { RequestAbortedError } from "../abstractions/fido2-client.service.abstraction";
import {
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
NewCredentialParams,
} from "../abstractions/fido2-user-interface.service.abstraction";
export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
async confirmCredential(): Promise<boolean> {
@ -14,7 +17,18 @@ export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstr
return false;
}
async confirmDuplicateCredential() {
return false;
async confirmNewNonDiscoverableCredential(
params: NewCredentialParams,
abortController?: AbortController
): Promise<string> {
return null;
}
async informExcludedCredential(
existingCipherIds: string[],
newCredential: NewCredentialParams,
abortController?: AbortController
): Promise<void> {
// Not Implemented
}
}