-
Notifications
You must be signed in to change notification settings - Fork 304
feat(sdk-core): add v2 decryption support to password change #8804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ import { | |
| UpdateSingleKeychainPasswordOptions, | ||
| } from './iKeychains'; | ||
| import { BitGoKeyFromOvcShares, BitGoToOvcJSON, OvcToBitGoJSON } from './ovcJsonCodec'; | ||
| import { EncryptionVersion } from '../../api'; | ||
|
|
||
| export class Keychains implements IKeychains { | ||
| private readonly bitgo: BitGoBase; | ||
|
|
@@ -113,7 +114,7 @@ export class Keychains implements IKeychains { | |
| continue; | ||
| } | ||
| try { | ||
| const updatedKeychain = this.updateSingleKeychainPassword({ | ||
| const updatedKeychain = await this.updateSingleKeychainPasswordAsync({ | ||
| keychain: key, | ||
| oldPassword: params.oldPassword, | ||
| newPassword: params.newPassword, | ||
|
|
@@ -145,12 +146,13 @@ export class Keychains implements IKeychains { | |
| } | ||
|
|
||
| /** | ||
| * Update the password used to decrypt a single keychain | ||
| * Update the password used to decrypt a single keychain. | ||
| * Handles v1 (SJCL) envelopes only. For v2 (Argon2id) support use {@link updateSingleKeychainPasswordAsync}. | ||
| * @param params | ||
| * @param params.keychain - The keychain whose password should be updated | ||
| * @param params.oldPassword - The old password used for encrypting the key | ||
| * @param params.newPassword - The new password to be used for encrypting the key | ||
| * @returns {object} | ||
| * @returns {Keychain} | ||
| */ | ||
| updateSingleKeychainPassword(params: UpdateSingleKeychainPasswordOptions = {}): Keychain { | ||
| if (!_.isString(params.oldPassword)) { | ||
|
|
@@ -176,6 +178,53 @@ export class Keychains implements IKeychains { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Update the password used to decrypt a single keychain, with support for v2 (Argon2id) envelopes. | ||
| * Automatically detects and preserves the envelope version — a v2-encrypted key stays v2 after the password change. | ||
| * @param params | ||
| * @param params.keychain - The keychain whose password should be updated | ||
| * @param params.oldPassword - The old password used for encrypting the key | ||
| * @param params.newPassword - The new password to be used for encrypting the key | ||
| * @returns {Promise<Keychain>} | ||
| */ | ||
| async updateSingleKeychainPasswordAsync(params: UpdateSingleKeychainPasswordOptions = {}): Promise<Keychain> { | ||
| if (!_.isString(params.oldPassword)) { | ||
| throw new Error('expected old password to be a string'); | ||
| } | ||
|
|
||
| if (!_.isString(params.newPassword)) { | ||
| throw new Error('expected new password to be a string'); | ||
| } | ||
|
|
||
| if (!_.isObject(params.keychain) || !_.isString(params.keychain.encryptedPrv)) { | ||
| throw new Error('expected keychain to be an object with an encryptedPrv property'); | ||
| } | ||
|
|
||
| const oldEncryptedPrv = params.keychain.encryptedPrv; | ||
| try { | ||
| const decryptedPrv = await this.bitgo.decryptAsync({ input: oldEncryptedPrv, password: params.oldPassword }); | ||
| // Preserve the original envelope's encryption version so v2-encrypted keys stay v2 after the password change. | ||
| let encryptionVersion: EncryptionVersion | undefined; | ||
| try { | ||
| const parsed = JSON.parse(oldEncryptedPrv); | ||
| if (parsed.v === 2) { | ||
| encryptionVersion = 2; | ||
| } | ||
| } catch { | ||
| // non-JSON input — default to v1 (no encryptionVersion) | ||
| } | ||
| const newEncryptedPrv = await this.bitgo.encryptAsync({ | ||
| input: decryptedPrv, | ||
| password: params.newPassword, | ||
| encryptionVersion, | ||
| }); | ||
| return _.assign({}, params.keychain, { encryptedPrv: newEncryptedPrv }); | ||
| } catch (e) { | ||
| // catching an error here means that the password was incorrect or, less likely, the input to decrypt is corrupted | ||
| throw new Error('password used to decrypt keychain private key is incorrect'); | ||
|
Comment on lines
+222
to
+224
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cant the input to decrypt be corrupted even for v1?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is existing logic from the original |
||
| } | ||
| } | ||
|
Comment on lines
+203
to
+226
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be made bit cleaner like this with a helper for detecing the encryption version. Also narrowing down the the try/catch.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure can add this helper, though i think the callers will still need to handle try/catch since not all of them should throw if the ciphertext is not parsable |
||
|
|
||
| /** | ||
| * Create a public/private key pair | ||
| * @param params - optional params | ||
|
|
@@ -359,17 +408,17 @@ export class Keychains implements IKeychains { | |
| throw new Error('failed to get recovery info'); | ||
| } | ||
|
|
||
| const decryptedWalletPassphrase = this.bitgo.decrypt({ | ||
| const decryptedWalletPassphrase = await this.bitgo.decryptAsync({ | ||
| input: params.encryptedMaterial.encryptedWalletPassphrase, | ||
| password: recoveryInfo.passcodeEncryptionCode, | ||
| }); | ||
|
|
||
| const decryptedUserKey = this.bitgo.decrypt({ | ||
| const decryptedUserKey = await this.bitgo.decryptAsync({ | ||
| input: params.encryptedMaterial.encryptedUserKey, | ||
| password: decryptedWalletPassphrase, | ||
| }); | ||
|
|
||
| const decryptedBackupKey = this.bitgo.decrypt({ | ||
| const decryptedBackupKey = await this.bitgo.decryptAsync({ | ||
| input: params.encryptedMaterial.encryptedBackupKey, | ||
| password: decryptedWalletPassphrase, | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would add a todo to deprecate this in the future