PM-4877: Only allow replacing passkeys for the same userhandle (#9804)

* Initial draft

* small cleanup

* show vaul items without passkeys

* Refactored a bit

* tests run for me?

* Fixed platform test

* null and undefined

* lint
This commit is contained in:
Anders Åberg 2024-07-16 19:39:41 +02:00 committed by GitHub
parent aa8c5b1516
commit dbc9b9c90b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 28 additions and 2 deletions

View File

@ -65,6 +65,7 @@ export type BrowserFido2Message = { sessionId: string } & (
type: "ConfirmNewCredentialRequest"; type: "ConfirmNewCredentialRequest";
credentialName: string; credentialName: string;
userName: string; userName: string;
userHandle: string;
userVerification: boolean; userVerification: boolean;
fallbackSupported: boolean; fallbackSupported: boolean;
rpId: string; rpId: string;
@ -242,6 +243,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
async confirmNewCredential({ async confirmNewCredential({
credentialName, credentialName,
userName, userName,
userHandle,
userVerification, userVerification,
rpId, rpId,
}: NewCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { }: NewCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> {
@ -250,6 +252,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
sessionId: this.sessionId, sessionId: this.sessionId,
credentialName, credentialName,
userName, userName,
userHandle,
userVerification, userVerification,
fallbackSupported: this.fallbackSupported, fallbackSupported: this.fallbackSupported,
rpId, rpId,

View File

@ -143,8 +143,10 @@ export class Fido2Component implements OnInit, OnDestroy {
this.ciphers = (await this.cipherService.getAllDecrypted()).filter( this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
); );
this.displayedCiphers = this.ciphers.filter((cipher) => this.displayedCiphers = this.ciphers.filter(
cipher.login.matchesUri(this.url, equivalentDomains), (cipher) =>
cipher.login.matchesUri(this.url, equivalentDomains) &&
this.hasNoOtherPasskeys(cipher, message.userHandle),
); );
if (this.displayedCiphers.length > 0) { if (this.displayedCiphers.length > 0) {
@ -405,4 +407,18 @@ export class Fido2Component implements OnInit, OnDestroy {
...msg, ...msg,
}); });
} }
/**
* This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle
* @param userHandle
*/
private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean {
if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) {
return true;
}
return cipher.login.fido2Credentials.some((passkey) => {
passkey.userHandle === userHandle;
});
}
} }

View File

@ -12,6 +12,11 @@ export interface NewCredentialParams {
*/ */
userName: string; userName: string;
/**
* The userhandle (userid) of the user.
*/
userHandle: string;
/** /**
* Whether or not the user must be verified before completing the operation. * Whether or not the user must be verified before completing the operation.
*/ */

View File

@ -215,6 +215,7 @@ describe("FidoAuthenticatorService", () => {
expect(userInterfaceSession.confirmNewCredential).toHaveBeenCalledWith({ expect(userInterfaceSession.confirmNewCredential).toHaveBeenCalledWith({
credentialName: params.rpEntity.name, credentialName: params.rpEntity.name,
userName: params.userEntity.name, userName: params.userEntity.name,
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
userVerification, userVerification,
rpId: params.rpEntity.id, rpId: params.rpEntity.id,
} as NewCredentialParams); } as NewCredentialParams);

View File

@ -112,6 +112,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
const response = await userInterfaceSession.confirmNewCredential({ const response = await userInterfaceSession.confirmNewCredential({
credentialName: params.rpEntity.name, credentialName: params.rpEntity.name,
userName: params.userEntity.name, userName: params.userEntity.name,
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
userVerification: params.requireUserVerification, userVerification: params.requireUserVerification,
rpId: params.rpEntity.id, rpId: params.rpEntity.id,
}); });