Skip to content

Conversation

@douglas-xt
Copy link
Contributor

This PR adds validation of public keys during account recovery to prevent users from restoring their account using a backup file that doesn't belong to them.

When a user performs account recovery, the public keys included in the backup file are now compared against the keys stored in the database. If the keys don't match, the recovery is rejected with a clear error message indicating the backup file doesn't correspond to the account.

This change works in conjunction with updates in the SDK and drive-web that now include public keys in the backup file generation and send them during the recovery request.

@douglas-xt douglas-xt force-pushed the fix/validate-public-keys-on-recovery branch from 212af49 to c635ea3 Compare January 7, 2026 18:26
@sg-gs sg-gs self-requested a review January 8, 2026 08:59
@ApiProperty()
@IsOptional()
kyber?: string;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a line between this class and the next one

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why in this class ecc and kyber are optional? Isn't it enough just to have optional public keys field in RecoverAccountDto? Because now we can have {ecc: undefined, kyber: underfined}, {ecc: , kyber: underfined}, {ecc: undefined, kyber: } and I don't think we should accept those as valid input

}

if (!withReset && !publicKeys) {
throw new BadRequestException('Invalid backup file');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Invalid keys' it's a better naming as you do not know if those come from a backup file or anything else in the future

publicKeys.kyber && existingKeys.kyber !== publicKeys.kyber;

if (eccMismatch || kyberMismatch) {
throw new BadRequestException('Invalid backup file');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. In this case it could be worth it to specify which key is failing.

@sg-gs
Copy link
Member

sg-gs commented Jan 8, 2026

WDYT of this PR @TamaraFinogina ?


const user = await this.userRepository.findByUuid(userUuid);

if (publicKeys) {
Copy link
Contributor

@TamaraFinogina TamaraFinogina Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if withReset = true and mismatching public keys, then backup won't go through?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sg-gs Do we require/can send publicKeys for account reset? Becuase now we check public keys and if they fail we abort even if it's withReset=true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TamaraFinogina @douglas-xt no, when account is being reset its because they have no backup file in the first place

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so yeah, looking closer to the flow it would go through. withReset = true, no backup file so publicKeys are undefined and this doesn't execute.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jzunigax2 Ok, thanks! I would really prefer this function to be more explicit, so that the key reset only happens when the account is reset, and public keys are checked only when it's a backup. But it's ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed it's kinda hard to read, @douglas-xt could you refactor this a bit? maybe simply moving the if withReset check to the top of the function and doing an early return would do the trick? otherwise all else looks good to me


if (publicKeys) {
const existingKeys = await this.keyServerUseCases.getPublicKeys(user.id);
const eccMismatch = publicKeys.ecc && existingKeys.ecc !== publicKeys.ecc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we send publicKeys = {ecc: underfined, kyber: underfined}, then publicKeys.ecc would be false, right? so, the eccMismatch will be false without even checking existingKeys.ecc !== publicKeys.ecc; part, no?

I think we shouldn't accept those keys. Or at least use something like
! publicKeys.ecc || existingKeys.ecc !== publicKeys.ecc

then undefined, null or similar keeys will give a mismatch

const existingKeys = await this.keyServerUseCases.getPublicKeys(user.id);
const eccMismatch = publicKeys.ecc && existingKeys.ecc !== publicKeys.ecc;
const kyberMismatch =
publicKeys.kyber && existingKeys.kyber !== publicKeys.kyber;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@douglas-xt This check would pass for publicKeys = {ecc: underfined, kyber: underfined} without even checking what existingKeys are. I know that now they are not empty, but if we change that in the future this line becomes an issue. Can we remove publicKeys.kyber && and just do the comparison?

const { mnemonic, password, salt, privateKeys, publicKeys } =
newCredentials;

const user = await this.userRepository.findByUuid(userUuid);
Copy link
Contributor

@TamaraFinogina TamaraFinogina Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other parts of the code check this after calling findByUuid

if (!user) {
      throw new NotFoundException('User not found');
}

or

if (!user) {
      throw new BadRequestException();
    }

if (!withReset && !shouldUpdateKeys) {
if (!shouldUpdateKeys) {
throw new BadRequestException(
'Keys are required if the account is not being reset',
Copy link
Contributor

@TamaraFinogina TamaraFinogina Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jzunigax2 @douglas-xt Do we need to check public keys too? Or do we still have both backups with/without publicKeys?

P.S. I think we also need to throw an error when privateKeys are not given at all

TamaraFinogina
TamaraFinogina previously approved these changes Jan 26, 2026
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants