1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[EC-598] feat: only show fallback options if supported

This commit is contained in:
Andreas Coroiu
2023-04-28 10:58:26 +02:00
parent c3ce8d87f0
commit 8e08190620
10 changed files with 81 additions and 16 deletions

View File

@@ -10,7 +10,8 @@ export class WebauthnUtils {
static mapCredentialCreationOptions(
options: CredentialCreationOptions,
origin: string,
sameOriginWithAncestors: boolean
sameOriginWithAncestors: boolean,
fallbackSupported: boolean
): CreateCredentialParams {
const keyOptions = options.publicKey;
@@ -47,6 +48,7 @@ export class WebauthnUtils {
},
timeout: keyOptions.timeout,
sameOriginWithAncestors,
fallbackSupported,
};
}
@@ -91,7 +93,8 @@ export class WebauthnUtils {
static mapCredentialRequestOptions(
options: CredentialRequestOptions,
origin: string,
sameOriginWithAncestors: boolean
sameOriginWithAncestors: boolean,
fallbackSupported: boolean
): AssertCredentialParams {
const keyOptions = options.publicKey;
@@ -108,6 +111,7 @@ export class WebauthnUtils {
userVerification: keyOptions.userVerification,
timeout: keyOptions.timeout,
sameOriginWithAncestors,
fallbackSupported,
};
}

View File

@@ -55,12 +55,22 @@ navigator.credentials.create = async (
options?: CredentialCreationOptions,
abortController?: AbortController
): Promise<Credential> => {
const fallbackSupported =
(options?.publicKey?.authenticatorSelection.authenticatorAttachment === "platform" &&
browserNativeWebauthnPlatformAuthenticatorSupport) ||
(options?.publicKey?.authenticatorSelection.authenticatorAttachment !== "platform" &&
browserNativeWebauthnSupport);
try {
const response = await messenger.request(
{
type: MessageType.CredentialCreationRequest,
// TODO: Fix sameOriginWithAncestors!
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin, true),
data: WebauthnUtils.mapCredentialCreationOptions(
options,
window.location.origin,
true,
fallbackSupported
),
},
abortController
);
@@ -71,7 +81,7 @@ navigator.credentials.create = async (
return WebauthnUtils.mapCredentialRegistrationResult(response.result);
} catch (error) {
if (error && error.fallbackRequested) {
if (error && error.fallbackRequested && fallbackSupported) {
return await browserCredentials.create(options);
}
@@ -83,12 +93,18 @@ navigator.credentials.get = async (
options?: CredentialRequestOptions,
abortController?: AbortController
): Promise<Credential> => {
const fallbackSupported = browserNativeWebauthnSupport;
try {
const response = await messenger.request(
{
type: MessageType.CredentialGetRequest,
// TODO: Fix sameOriginWithAncestors!
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin, true),
data: WebauthnUtils.mapCredentialRequestOptions(
options,
window.location.origin,
true,
fallbackSupported
),
},
abortController
);
@@ -99,7 +115,7 @@ navigator.credentials.get = async (
return WebauthnUtils.mapCredentialAssertResult(response.result);
} catch (error) {
if (error && error.fallbackRequested) {
if (error && error.fallbackRequested && fallbackSupported) {
return await browserCredentials.get(options);
}

View File

@@ -45,7 +45,12 @@
You do not have a matching login for this site.
</ng-container>
</ng-container>
<button type="button" class="btn btn-outline-secondary" (click)="abort(true)">
<button
*ngIf="data.fallbackSupported"
type="button"
class="btn btn-outline-secondary"
(click)="abort(true)"
>
Use browser built-in
</button>
<button type="button" class="btn btn-outline-secondary" (click)="abort(false)">Abort</button>

View File

@@ -27,6 +27,7 @@ import {
interface ViewData {
message: BrowserFido2Message;
showUnsupportedVerification: boolean;
fallbackSupported: boolean;
}
@Component({
@@ -108,11 +109,10 @@ export class Fido2Component implements OnInit, OnDestroy {
return {
message,
showUnsupportedVerification:
(message.type === "ConfirmNewCredentialRequest" ||
message.type === "ConfirmNewNonDiscoverableCredentialRequest" ||
message.type === "PickCredentialRequest") &&
"userVerification" in message &&
message.userVerification &&
!(await this.passwordRepromptService.enabled()),
fallbackSupported: "fallbackSupported" in message && message.fallbackSupported,
};
}),
takeUntil(this.destroy$)

View File

@@ -48,6 +48,7 @@ export type BrowserFido2Message = { sessionId: string } & (
type: "PickCredentialRequest";
cipherIds: string[];
userVerification: boolean;
fallbackSupported: boolean;
}
| {
type: "PickCredentialResponse";
@@ -59,6 +60,7 @@ export type BrowserFido2Message = { sessionId: string } & (
credentialName: string;
userName: string;
userVerification: boolean;
fallbackSupported: boolean;
}
| {
type: "ConfirmNewCredentialResponse";
@@ -69,6 +71,7 @@ export type BrowserFido2Message = { sessionId: string } & (
credentialName: string;
userName: string;
userVerification: boolean;
fallbackSupported: boolean;
}
| {
type: "ConfirmNewNonDiscoverableCredentialResponse";
@@ -78,9 +81,11 @@ export type BrowserFido2Message = { sessionId: string } & (
| {
type: "InformExcludedCredentialRequest";
existingCipherIds: string[];
fallbackSupported: boolean;
}
| {
type: "InformCredentialNotFoundRequest";
fallbackSupported: boolean;
}
| {
type: "AbortRequest";
@@ -94,17 +99,29 @@ export type BrowserFido2Message = { sessionId: string } & (
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
constructor(private popupUtilsService: PopupUtilsService) {}
async newSession(abortController?: AbortController): Promise<Fido2UserInterfaceSession> {
return await BrowserFido2UserInterfaceSession.create(this.popupUtilsService, abortController);
async newSession(
fallbackSupported: boolean,
abortController?: AbortController
): Promise<Fido2UserInterfaceSession> {
return await BrowserFido2UserInterfaceSession.create(
this.popupUtilsService,
fallbackSupported,
abortController
);
}
}
export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSession {
static async create(
popupUtilsService: PopupUtilsService,
fallbackSupported: boolean,
abortController?: AbortController
): Promise<BrowserFido2UserInterfaceSession> {
return new BrowserFido2UserInterfaceSession(popupUtilsService, abortController);
return new BrowserFido2UserInterfaceSession(
popupUtilsService,
fallbackSupported,
abortController
);
}
static sendMessage(msg: BrowserFido2Message) {
@@ -121,6 +138,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private constructor(
private readonly popupUtilsService: PopupUtilsService,
private readonly fallbackSupported: boolean,
readonly abortController = new AbortController(),
readonly sessionId = Utils.newGuid()
) {
@@ -182,6 +200,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
cipherIds,
sessionId: this.sessionId,
userVerification,
fallbackSupported: this.fallbackSupported,
};
await this.send(data);
@@ -201,6 +220,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
credentialName,
userName,
userVerification,
fallbackSupported: this.fallbackSupported,
};
await this.send(data);
@@ -220,6 +240,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
credentialName,
userName,
userVerification,
fallbackSupported: this.fallbackSupported,
};
await this.send(data);
@@ -233,6 +254,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
type: "InformExcludedCredentialRequest",
sessionId: this.sessionId,
existingCipherIds,
fallbackSupported: this.fallbackSupported,
};
await this.send(data);
@@ -243,6 +265,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
const data: BrowserFido2Message = {
type: "InformCredentialNotFoundRequest",
sessionId: this.sessionId,
fallbackSupported: this.fallbackSupported,
};
await this.send(data);

View File

@@ -84,6 +84,8 @@ export interface Fido2AuthenticatorMakeCredentialsParams {
/** The effective resident key requirement for credential creation, a Boolean value determined by the client. */
requireResidentKey: boolean;
requireUserVerification: boolean;
/** Forwarded to user interface */
fallbackSupported: boolean;
/** The constant Boolean value true. It is included here as a pseudo-parameter to simplify applying this abstract authenticator model to implementations that may wish to make a test of user presence optional although WebAuthn does not. */
// requireUserPresence: true; // Always required
}
@@ -106,6 +108,8 @@ export interface Fido2AuthenticatorGetAssertionParams {
/** The constant Boolean value true. It is included here as a pseudo-parameter to simplify applying this abstract authenticator model to implementations that may wish to make a test of user presence optional although WebAuthn does not. */
// requireUserPresence: boolean; // Always required
extensions: unknown;
/** Forwarded to user interface */
fallbackSupported: boolean;
}
export interface Fido2AuthenticatorGetAssertionResult {

View File

@@ -44,6 +44,7 @@ export interface CreateCredentialParams {
id: string; // b64 encoded
displayName: string;
};
fallbackSupported: boolean;
timeout?: number;
}
@@ -64,6 +65,7 @@ export interface AssertCredentialParams {
userVerification?: UserVerification;
timeout: number;
sameOriginWithAncestors: boolean;
fallbackSupported: boolean;
}
export interface AssertCredentialResult {

View File

@@ -10,7 +10,10 @@ export interface PickCredentialParams {
}
export abstract class Fido2UserInterfaceService {
newSession: (abortController?: AbortController) => Promise<Fido2UserInterfaceSession>;
newSession: (
fallbackSupported: boolean,
abortController?: AbortController
) => Promise<Fido2UserInterfaceSession>;
}
export abstract class Fido2UserInterfaceSession {

View File

@@ -43,7 +43,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
params: Fido2AuthenticatorMakeCredentialsParams,
abortController?: AbortController
): Promise<Fido2AuthenticatorMakeCredentialResult> {
const userInterfaceSession = await this.userInterface.newSession(abortController);
const userInterfaceSession = await this.userInterface.newSession(
params.fallbackSupported,
abortController
);
try {
if (params.credTypesAndPubKeyAlgs.every((p) => p.alg !== Fido2AlgorithmIdentifier.ES256)) {
@@ -211,7 +214,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
params: Fido2AuthenticatorGetAssertionParams,
abortController?: AbortController
): Promise<Fido2AuthenticatorGetAssertionResult> {
const userInterfaceSession = await this.userInterface.newSession(abortController);
const userInterfaceSession = await this.userInterface.newSession(
params.fallbackSupported,
abortController
);
try {
if (

View File

@@ -127,6 +127,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
id: Fido2Utils.stringToBuffer(params.user.id),
displayName: params.user.displayName,
},
fallbackSupported: params.fallbackSupported,
};
let makeCredentialResult;
try {
@@ -226,6 +227,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
hash: clientDataHash,
allowCredentialDescriptorList,
extensions: {},
fallbackSupported: params.fallbackSupported,
};
let getAssertionResult;