1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-31 22:51:28 +01:00

[EC-598] feat: first tested rule in new authentitcator

This commit is contained in:
Andreas Coroiu 2023-03-21 14:16:33 +01:00
parent e70d6cdcd4
commit c8ab590086
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
6 changed files with 156 additions and 8 deletions

View File

@ -189,6 +189,15 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
return false;
}
async confirmDuplicateCredential(
existingCipherIds: string[],
newCredential: NewCredentialParams,
abortController?: AbortController
) {
// Not Implemented
return false;
}
private setAbortTimeout(abortController: AbortController) {
return setTimeout(() => abortController.abort());
}

View File

@ -18,18 +18,19 @@ export interface Fido2AuthenticatorMakeCredentialsParams {
id?: string;
};
user: {
name: string;
displayName: string;
id: BufferSource;
name?: string;
displayName?: string;
icon?: string;
};
pubKeyCredParams: {
alg: number;
// type: "public-key"; // not used
type: "public-key"; // not used
}[];
excludeList?: {
id: BufferSource;
transports?: ("ble" | "internal" | "nfc" | "usb")[];
// type: "public-key"; // not used
type: "public-key"; // not used
}[];
extensions?: {
appid?: string;

View File

@ -10,4 +10,9 @@ export abstract class Fido2UserInterfaceService {
params: NewCredentialParams,
abortController?: AbortController
) => Promise<boolean>;
confirmDuplicateCredential: (
existingCipherIds: string[],
newCredential: NewCredentialParams,
abortController?: AbortController
) => Promise<boolean>;
}

View File

@ -1,5 +1,118 @@
import { TextEncoder } from "util";
import { mock, MockProxy } from "jest-mock-extended";
import { Utils } from "../../misc/utils";
import { CipherService } from "../../vault/abstractions/cipher.service";
import { CipherType } from "../../vault/enums/cipher-type";
import { CipherView } from "../../vault/models/view/cipher.view";
import { Fido2AuthenticatorMakeCredentialsParams } from "../abstractions/fido2-authenticator.service.abstraction";
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
import { Fido2Utils } from "../abstractions/fido2-utils";
import { Fido2KeyView } from "../models/view/fido2-key.view";
import { Fido2AuthenticatorService } from "./fido2-authenticator.service";
const RpId = "bitwarden.com";
describe("FidoAuthenticatorService", () => {
let cipherService!: MockProxy<CipherService>;
let userInterface!: MockProxy<Fido2UserInterfaceService>;
let authenticator!: Fido2AuthenticatorService;
beforeEach(() => {
cipherService = mock<CipherService>();
userInterface = mock<Fido2UserInterfaceService>();
authenticator = new Fido2AuthenticatorService(cipherService, userInterface);
});
describe("authenticatorMakeCredential", () => {
test.skip("To be implemented");
describe("when vault contains excluded credential", () => {
let excludedCipher: CipherView;
let params: Fido2AuthenticatorMakeCredentialsParams;
beforeEach(async () => {
excludedCipher = createCipherView();
params = await createCredentialParams({
excludeList: [{ id: Fido2Utils.stringToBuffer(excludedCipher.id), type: "public-key" }],
});
cipherService.getAllDecrypted.mockResolvedValue([excludedCipher]);
});
/** Spec: wait for user presence */
it("should wait for confirmation from user", async () => {
userInterface.confirmDuplicateCredential.mockResolvedValue(true);
await authenticator.makeCredential(params);
expect(userInterface.confirmDuplicateCredential).toHaveBeenCalled();
});
});
});
});
async function createCredentialParams(
params: Partial<Fido2AuthenticatorMakeCredentialsParams> = {}
): Promise<Fido2AuthenticatorMakeCredentialsParams> {
return {
clientDataHash: params.clientDataHash ?? (await createClientDataHash()),
rp: params.rp ?? {
name: "Bitwarden",
id: RpId,
},
user: params.user ?? {
id: randomBytes(64),
name: "jane.doe@bitwarden.com",
displayName: "Jane Doe",
icon: " data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAOhJREFUeNpiFI+9E8DAwDAfiAUYSAMfgDiQBVmzlSYnUTqPXf/OANWzngVZ87pKKaIMCGp/BjeEhRjFMKAjx8bQFC2CIs9CpHNxAiYGCsEQM4Cfiwm3AY9f/yZogIcRN4ZahAFv/jAcu4E7xMNtecEYpAakFqsX8me9Yvj07R+G5jR3foaJqWJgOZAaZMAIzAv/kQV05NgZ5hdIMMiKQJIIyEYrDU6wrYkTXjBcefQTvwGwwCoJFGJIBdoMArN3fmToWf+O4SMW14EMeI8rJ8Jcgexn9BwJCoNEaNbEACCN+DSDsjNAgAEAri9Zii/uDMsAAAAASUVORK5CYII=",
},
pubKeyCredParams: params.pubKeyCredParams ?? [
{
alg: -1, // ES256
type: "public-key",
},
],
excludeList: params.excludeList ?? [
{
id: randomBytes(16),
transports: ["internal"],
type: "public-key",
},
],
extensions: params.extensions ?? {
appid: undefined,
appidExclude: undefined,
credProps: undefined,
uvm: false as boolean,
},
options: params.options ?? {
rk: false as boolean,
uv: false as boolean,
},
};
}
function createCipherView(id = Utils.newGuid()): CipherView {
const cipher = new CipherView();
cipher.id = id;
cipher.type = CipherType.Fido2Key;
cipher.fido2Key = new Fido2KeyView();
return cipher;
}
async function createClientDataHash() {
const encoder = new TextEncoder();
const clientData = encoder.encode(
JSON.stringify({
type: "webauthn.create",
challenge: Fido2Utils.bufferToString(randomBytes(16)),
origin: RpId,
crossOrigin: false,
})
);
return await crypto.subtle.digest({ name: "SHA-256" }, clientData);
}
function randomBytes(length: number) {
return new Uint8Array(Array.from({ length }, () => Math.floor(Math.random() * 255)));
}

View File

@ -1,12 +1,28 @@
import { CipherService } from "../../vault/services/cipher.service";
import {
Fido2AuthenticatorMakeCredentialsParams,
Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction,
} from "../abstractions/fido2-authenticator.service.abstraction";
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
import { Fido2Utils } from "../abstractions/fido2-utils";
/**
* Bitwarden implementation of the Authenticator API described by the FIDO Alliance
* https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html
*/
export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstraction {
makeCredential: (params: Fido2AuthenticatorMakeCredentialsParams) => void;
constructor(
private cipherService: CipherService,
private userInterface: Fido2UserInterfaceService
) {}
async makeCredential(params: Fido2AuthenticatorMakeCredentialsParams): Promise<void> {
this.userInterface.confirmDuplicateCredential(
[Fido2Utils.bufferToString(params.excludeList[0].id)],
{
credentialName: params.rp.name,
userName: params.user.name,
}
);
}
}

View File

@ -2,15 +2,19 @@ import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } fro
import { RequestAbortedError } from "../abstractions/fido2.service.abstraction";
export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
async confirmCredential(cipherId: string): Promise<boolean> {
async confirmCredential(): Promise<boolean> {
return false;
}
pickCredential(cipherIds: string[]): Promise<string> {
pickCredential(): Promise<string> {
throw new RequestAbortedError();
}
async confirmNewCredential(): Promise<boolean> {
return false;
}
async confirmDuplicateCredential() {
return false;
}
}