From aa51529b992065575a50e7332dfdd0a024f62008 Mon Sep 17 00:00:00 2001 From: asi345 Date: Wed, 18 Dec 2024 23:03:25 +0100 Subject: [PATCH] src: change password functionality This commit adds changing password feature. The feature can only be used when the device is initialized. It shares a lot of the functionality with set password feature, but it requires the user to enter the old password correctly, and also does some seed handling to not cause any data loss. Signed-off-by: asi345 --- CHANGELOG.md | 1 + messages/bitbox02_system.proto | 3 + messages/hww.proto | 1 + py/bitbox02/bitbox02/bitbox02/bitbox02.py | 11 +++ .../generated/bitbox02_system_pb2.py | 4 +- .../generated/bitbox02_system_pb2.pyi | 6 ++ .../communication/generated/hww_pb2.py | 8 +-- .../communication/generated/hww_pb2.pyi | 10 ++- py/send_message.py | 7 ++ src/keystore.c | 68 ++++++++++++++++--- src/keystore.h | 8 +++ src/rust/bitbox02-rust/src/hww/api.rs | 3 + .../src/hww/api/change_password.rs | 34 ++++++++++ .../bitbox02-rust/src/shiftcrypto.bitbox02.rs | 7 +- src/rust/bitbox02-sys/build.rs | 1 + src/rust/bitbox02/src/keystore.rs | 12 ++++ test/unit-test/test_keystore_functional.c | 29 ++++++++ 17 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 src/rust/bitbox02-rust/src/hww/api/change_password.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index af749713d..f46af7f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately. - Ethereum: remove deprecated Goerli network - SD card: solve backup bug when sd card is re-inserted - Cardano: allow serialization using 258-tagged sets +- Add change password functionality ### 9.21.0 - Bitcoin: add support for sending to silent payment (BIP-352) addresses diff --git a/messages/bitbox02_system.proto b/messages/bitbox02_system.proto index a51aceef1..dd6dbfba8 100644 --- a/messages/bitbox02_system.proto +++ b/messages/bitbox02_system.proto @@ -56,3 +56,6 @@ message SetDeviceNameRequest { message SetPasswordRequest { bytes entropy = 1; } + +message ChangePasswordRequest { +} diff --git a/messages/hww.proto b/messages/hww.proto index 54c34f6d4..4e3ee8388 100644 --- a/messages/hww.proto +++ b/messages/hww.proto @@ -67,6 +67,7 @@ message Request { ElectrumEncryptionKeyRequest electrum_encryption_key = 26; CardanoRequest cardano = 27; BIP85Request bip85 = 28; + ChangePasswordRequest change_password = 29; } } diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py index 768a657e7..20f9d88fb 100644 --- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py +++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py @@ -190,6 +190,17 @@ def set_password(self, entropy_size: int = 32) -> bool: raise return True + def change_password(self) -> bool: + request = hww.Request() + request.change_password.CopyFrom(bitbox02_system.ChangePasswordRequest()) + try: + self._msg_query(request, expected_response="success") + except Bitbox02Exception as err: + if err.code == ERR_GENERIC: + return False + raise + return True + def create_backup(self) -> bool: """ Returns True if the backup was created successfully. diff --git a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py index 4dbeb63fc..c00bfe3ab 100644 --- a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py +++ b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xaf\x01\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xaf\x01\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\x17\n\x15\x43hangePasswordRequestb\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'bitbox02_system_pb2', globals()) @@ -40,4 +40,6 @@ _SETDEVICENAMEREQUEST._serialized_end=565 _SETPASSWORDREQUEST._serialized_start=567 _SETPASSWORDREQUEST._serialized_end=604 + _CHANGEPASSWORDREQUEST._serialized_start=606 + _CHANGEPASSWORDREQUEST._serialized_end=629 # @@protoc_insertion_point(module_scope) diff --git a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi index 6b6829e2c..15365b09b 100644 --- a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi @@ -124,3 +124,9 @@ class SetPasswordRequest(google.protobuf.message.Message): ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["entropy",b"entropy"]) -> None: ... global___SetPasswordRequest = SetPasswordRequest + +class ChangePasswordRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + def __init__(self, + ) -> None: ... +global___ChangePasswordRequest = ChangePasswordRequest diff --git a/py/bitbox02/bitbox02/communication/generated/hww_pb2.py b/py/bitbox02/bitbox02/communication/generated/hww_pb2.py index 4606d1d94..9084469f0 100644 --- a/py/bitbox02/bitbox02/communication/generated/hww_pb2.py +++ b/py/bitbox02/bitbox02/communication/generated/hww_pb2.py @@ -23,7 +23,7 @@ from . import perform_attestation_pb2 as perform__attestation__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thww.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x15\x62\x61\x63kup_commands.proto\x1a\x15\x62itbox02_system.proto\x1a\tbtc.proto\x1a\rcardano.proto\x1a\teth.proto\x1a\x0ekeystore.proto\x1a\x0emnemonic.proto\x1a\x0csystem.proto\x1a\x19perform_attestation.proto\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"\t\n\x07Success\"\xfd\r\n\x07Request\x12\x41\n\x0b\x64\x65vice_name\x18\x02 \x01(\x0b\x32*.shiftcrypto.bitbox02.SetDeviceNameRequestH\x00\x12I\n\x0f\x64\x65vice_language\x18\x03 \x01(\x0b\x32..shiftcrypto.bitbox02.SetDeviceLanguageRequestH\x00\x12>\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32\'.shiftcrypto.bitbox02.DeviceInfoRequestH\x00\x12@\n\x0cset_password\x18\x05 \x01(\x0b\x32(.shiftcrypto.bitbox02.SetPasswordRequestH\x00\x12\x42\n\rcreate_backup\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.CreateBackupRequestH\x00\x12\x42\n\rshow_mnemonic\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ShowMnemonicRequestH\x00\x12\x36\n\x07\x62tc_pub\x18\x08 \x01(\x0b\x32#.shiftcrypto.bitbox02.BTCPubRequestH\x00\x12\x41\n\rbtc_sign_init\x18\t \x01(\x0b\x32(.shiftcrypto.bitbox02.BTCSignInitRequestH\x00\x12\x43\n\x0e\x62tc_sign_input\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignInputRequestH\x00\x12\x45\n\x0f\x62tc_sign_output\x18\x0b \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCSignOutputRequestH\x00\x12O\n\x14insert_remove_sdcard\x18\x0c \x01(\x0b\x32/.shiftcrypto.bitbox02.InsertRemoveSDCardRequestH\x00\x12@\n\x0c\x63heck_sdcard\x18\r \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckSDCardRequestH\x00\x12\x64\n\x1fset_mnemonic_passphrase_enabled\x18\x0e \x01(\x0b\x32\x39.shiftcrypto.bitbox02.SetMnemonicPassphraseEnabledRequestH\x00\x12@\n\x0clist_backups\x18\x0f \x01(\x0b\x32(.shiftcrypto.bitbox02.ListBackupsRequestH\x00\x12\x44\n\x0erestore_backup\x18\x10 \x01(\x0b\x32*.shiftcrypto.bitbox02.RestoreBackupRequestH\x00\x12N\n\x13perform_attestation\x18\x11 \x01(\x0b\x32/.shiftcrypto.bitbox02.PerformAttestationRequestH\x00\x12\x35\n\x06reboot\x18\x12 \x01(\x0b\x32#.shiftcrypto.bitbox02.RebootRequestH\x00\x12@\n\x0c\x63heck_backup\x18\x13 \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckBackupRequestH\x00\x12/\n\x03\x65th\x18\x14 \x01(\x0b\x32 .shiftcrypto.bitbox02.ETHRequestH\x00\x12\x33\n\x05reset\x18\x15 \x01(\x0b\x32\".shiftcrypto.bitbox02.ResetRequestH\x00\x12Q\n\x15restore_from_mnemonic\x18\x16 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.RestoreFromMnemonicRequestH\x00\x12\x43\n\x0b\x66ingerprint\x18\x18 \x01(\x0b\x32,.shiftcrypto.bitbox02.RootFingerprintRequestH\x00\x12/\n\x03\x62tc\x18\x19 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCRequestH\x00\x12U\n\x17\x65lectrum_encryption_key\x18\x1a \x01(\x0b\x32\x32.shiftcrypto.bitbox02.ElectrumEncryptionKeyRequestH\x00\x12\x37\n\x07\x63\x61rdano\x18\x1b \x01(\x0b\x32$.shiftcrypto.bitbox02.CardanoRequestH\x00\x12\x33\n\x05\x62ip85\x18\x1c \x01(\x0b\x32\".shiftcrypto.bitbox02.BIP85RequestH\x00\x42\t\n\x07requestJ\x04\x08\x01\x10\x02J\x04\x08\x17\x10\x18\"\xbf\x07\n\x08Response\x12\x30\n\x07success\x18\x01 \x01(\x0b\x32\x1d.shiftcrypto.bitbox02.SuccessH\x00\x12,\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x1b.shiftcrypto.bitbox02.ErrorH\x00\x12?\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32(.shiftcrypto.bitbox02.DeviceInfoResponseH\x00\x12\x30\n\x03pub\x18\x05 \x01(\x0b\x32!.shiftcrypto.bitbox02.PubResponseH\x00\x12\x42\n\rbtc_sign_next\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x41\n\x0clist_backups\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ListBackupsResponseH\x00\x12\x41\n\x0c\x63heck_backup\x18\x08 \x01(\x0b\x32).shiftcrypto.bitbox02.CheckBackupResponseH\x00\x12O\n\x13perform_attestation\x18\t \x01(\x0b\x32\x30.shiftcrypto.bitbox02.PerformAttestationResponseH\x00\x12\x41\n\x0c\x63heck_sdcard\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.CheckSDCardResponseH\x00\x12\x30\n\x03\x65th\x18\x0b \x01(\x0b\x32!.shiftcrypto.bitbox02.ETHResponseH\x00\x12\x44\n\x0b\x66ingerprint\x18\x0c \x01(\x0b\x32-.shiftcrypto.bitbox02.RootFingerprintResponseH\x00\x12\x30\n\x03\x62tc\x18\r \x01(\x0b\x32!.shiftcrypto.bitbox02.BTCResponseH\x00\x12V\n\x17\x65lectrum_encryption_key\x18\x0e \x01(\x0b\x32\x33.shiftcrypto.bitbox02.ElectrumEncryptionKeyResponseH\x00\x12\x38\n\x07\x63\x61rdano\x18\x0f \x01(\x0b\x32%.shiftcrypto.bitbox02.CardanoResponseH\x00\x12\x34\n\x05\x62ip85\x18\x10 \x01(\x0b\x32#.shiftcrypto.bitbox02.BIP85ResponseH\x00\x42\n\n\x08responseJ\x04\x08\x03\x10\x04\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thww.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x15\x62\x61\x63kup_commands.proto\x1a\x15\x62itbox02_system.proto\x1a\tbtc.proto\x1a\rcardano.proto\x1a\teth.proto\x1a\x0ekeystore.proto\x1a\x0emnemonic.proto\x1a\x0csystem.proto\x1a\x19perform_attestation.proto\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"\t\n\x07Success\"\xc5\x0e\n\x07Request\x12\x41\n\x0b\x64\x65vice_name\x18\x02 \x01(\x0b\x32*.shiftcrypto.bitbox02.SetDeviceNameRequestH\x00\x12I\n\x0f\x64\x65vice_language\x18\x03 \x01(\x0b\x32..shiftcrypto.bitbox02.SetDeviceLanguageRequestH\x00\x12>\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32\'.shiftcrypto.bitbox02.DeviceInfoRequestH\x00\x12@\n\x0cset_password\x18\x05 \x01(\x0b\x32(.shiftcrypto.bitbox02.SetPasswordRequestH\x00\x12\x42\n\rcreate_backup\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.CreateBackupRequestH\x00\x12\x42\n\rshow_mnemonic\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ShowMnemonicRequestH\x00\x12\x36\n\x07\x62tc_pub\x18\x08 \x01(\x0b\x32#.shiftcrypto.bitbox02.BTCPubRequestH\x00\x12\x41\n\rbtc_sign_init\x18\t \x01(\x0b\x32(.shiftcrypto.bitbox02.BTCSignInitRequestH\x00\x12\x43\n\x0e\x62tc_sign_input\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignInputRequestH\x00\x12\x45\n\x0f\x62tc_sign_output\x18\x0b \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCSignOutputRequestH\x00\x12O\n\x14insert_remove_sdcard\x18\x0c \x01(\x0b\x32/.shiftcrypto.bitbox02.InsertRemoveSDCardRequestH\x00\x12@\n\x0c\x63heck_sdcard\x18\r \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckSDCardRequestH\x00\x12\x64\n\x1fset_mnemonic_passphrase_enabled\x18\x0e \x01(\x0b\x32\x39.shiftcrypto.bitbox02.SetMnemonicPassphraseEnabledRequestH\x00\x12@\n\x0clist_backups\x18\x0f \x01(\x0b\x32(.shiftcrypto.bitbox02.ListBackupsRequestH\x00\x12\x44\n\x0erestore_backup\x18\x10 \x01(\x0b\x32*.shiftcrypto.bitbox02.RestoreBackupRequestH\x00\x12N\n\x13perform_attestation\x18\x11 \x01(\x0b\x32/.shiftcrypto.bitbox02.PerformAttestationRequestH\x00\x12\x35\n\x06reboot\x18\x12 \x01(\x0b\x32#.shiftcrypto.bitbox02.RebootRequestH\x00\x12@\n\x0c\x63heck_backup\x18\x13 \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckBackupRequestH\x00\x12/\n\x03\x65th\x18\x14 \x01(\x0b\x32 .shiftcrypto.bitbox02.ETHRequestH\x00\x12\x33\n\x05reset\x18\x15 \x01(\x0b\x32\".shiftcrypto.bitbox02.ResetRequestH\x00\x12Q\n\x15restore_from_mnemonic\x18\x16 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.RestoreFromMnemonicRequestH\x00\x12\x43\n\x0b\x66ingerprint\x18\x18 \x01(\x0b\x32,.shiftcrypto.bitbox02.RootFingerprintRequestH\x00\x12/\n\x03\x62tc\x18\x19 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCRequestH\x00\x12U\n\x17\x65lectrum_encryption_key\x18\x1a \x01(\x0b\x32\x32.shiftcrypto.bitbox02.ElectrumEncryptionKeyRequestH\x00\x12\x37\n\x07\x63\x61rdano\x18\x1b \x01(\x0b\x32$.shiftcrypto.bitbox02.CardanoRequestH\x00\x12\x33\n\x05\x62ip85\x18\x1c \x01(\x0b\x32\".shiftcrypto.bitbox02.BIP85RequestH\x00\x12\x46\n\x0f\x63hange_password\x18\x1d \x01(\x0b\x32+.shiftcrypto.bitbox02.ChangePasswordRequestH\x00\x42\t\n\x07requestJ\x04\x08\x01\x10\x02J\x04\x08\x17\x10\x18\"\xbf\x07\n\x08Response\x12\x30\n\x07success\x18\x01 \x01(\x0b\x32\x1d.shiftcrypto.bitbox02.SuccessH\x00\x12,\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x1b.shiftcrypto.bitbox02.ErrorH\x00\x12?\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32(.shiftcrypto.bitbox02.DeviceInfoResponseH\x00\x12\x30\n\x03pub\x18\x05 \x01(\x0b\x32!.shiftcrypto.bitbox02.PubResponseH\x00\x12\x42\n\rbtc_sign_next\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x41\n\x0clist_backups\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ListBackupsResponseH\x00\x12\x41\n\x0c\x63heck_backup\x18\x08 \x01(\x0b\x32).shiftcrypto.bitbox02.CheckBackupResponseH\x00\x12O\n\x13perform_attestation\x18\t \x01(\x0b\x32\x30.shiftcrypto.bitbox02.PerformAttestationResponseH\x00\x12\x41\n\x0c\x63heck_sdcard\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.CheckSDCardResponseH\x00\x12\x30\n\x03\x65th\x18\x0b \x01(\x0b\x32!.shiftcrypto.bitbox02.ETHResponseH\x00\x12\x44\n\x0b\x66ingerprint\x18\x0c \x01(\x0b\x32-.shiftcrypto.bitbox02.RootFingerprintResponseH\x00\x12\x30\n\x03\x62tc\x18\r \x01(\x0b\x32!.shiftcrypto.bitbox02.BTCResponseH\x00\x12V\n\x17\x65lectrum_encryption_key\x18\x0e \x01(\x0b\x32\x33.shiftcrypto.bitbox02.ElectrumEncryptionKeyResponseH\x00\x12\x38\n\x07\x63\x61rdano\x18\x0f \x01(\x0b\x32%.shiftcrypto.bitbox02.CardanoResponseH\x00\x12\x34\n\x05\x62ip85\x18\x10 \x01(\x0b\x32#.shiftcrypto.bitbox02.BIP85ResponseH\x00\x42\n\n\x08responseJ\x04\x08\x03\x10\x04\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hww_pb2', globals()) @@ -35,7 +35,7 @@ _SUCCESS._serialized_start=245 _SUCCESS._serialized_end=254 _REQUEST._serialized_start=257 - _REQUEST._serialized_end=2046 - _RESPONSE._serialized_start=2049 - _RESPONSE._serialized_end=3008 + _REQUEST._serialized_end=2118 + _RESPONSE._serialized_start=2121 + _RESPONSE._serialized_end=3080 # @@protoc_insertion_point(module_scope) diff --git a/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi index df9aeacf1..4809bbd60 100644 --- a/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi @@ -68,6 +68,7 @@ class Request(google.protobuf.message.Message): ELECTRUM_ENCRYPTION_KEY_FIELD_NUMBER: builtins.int CARDANO_FIELD_NUMBER: builtins.int BIP85_FIELD_NUMBER: builtins.int + CHANGE_PASSWORD_FIELD_NUMBER: builtins.int @property def device_name(self) -> bitbox02_system_pb2.SetDeviceNameRequest: """removed: RandomNumberRequest random_number = 1;""" @@ -124,6 +125,8 @@ class Request(google.protobuf.message.Message): def cardano(self) -> cardano_pb2.CardanoRequest: ... @property def bip85(self) -> keystore_pb2.BIP85Request: ... + @property + def change_password(self) -> bitbox02_system_pb2.ChangePasswordRequest: ... def __init__(self, *, device_name: typing.Optional[bitbox02_system_pb2.SetDeviceNameRequest] = ..., @@ -152,10 +155,11 @@ class Request(google.protobuf.message.Message): electrum_encryption_key: typing.Optional[keystore_pb2.ElectrumEncryptionKeyRequest] = ..., cardano: typing.Optional[cardano_pb2.CardanoRequest] = ..., bip85: typing.Optional[keystore_pb2.BIP85Request] = ..., + change_password: typing.Optional[bitbox02_system_pb2.ChangePasswordRequest] = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["request",b"request"]) -> typing.Optional[typing_extensions.Literal["device_name","device_language","device_info","set_password","create_backup","show_mnemonic","btc_pub","btc_sign_init","btc_sign_input","btc_sign_output","insert_remove_sdcard","check_sdcard","set_mnemonic_passphrase_enabled","list_backups","restore_backup","perform_attestation","reboot","check_backup","eth","reset","restore_from_mnemonic","fingerprint","btc","electrum_encryption_key","cardano","bip85"]]: ... + def HasField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","change_password",b"change_password","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","change_password",b"change_password","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["request",b"request"]) -> typing.Optional[typing_extensions.Literal["device_name","device_language","device_info","set_password","create_backup","show_mnemonic","btc_pub","btc_sign_init","btc_sign_input","btc_sign_output","insert_remove_sdcard","check_sdcard","set_mnemonic_passphrase_enabled","list_backups","restore_backup","perform_attestation","reboot","check_backup","eth","reset","restore_from_mnemonic","fingerprint","btc","electrum_encryption_key","cardano","bip85","change_password"]]: ... global___Request = Request class Response(google.protobuf.message.Message): diff --git a/py/send_message.py b/py/send_message.py index 89821c534..971882e95 100755 --- a/py/send_message.py +++ b/py/send_message.py @@ -184,6 +184,12 @@ def _change_name_workflow(self, name: Optional[str] = None) -> None: info = self._device.device_info() print(f"New device name: {info['name']}") + def _change_password_worklow(self) -> None: + if not self._device.change_password(): + print("Failed to change password") + return + print("Successfully changed password") + def _setup_workflow(self) -> None: """TODO: Document""" self._change_name_workflow() @@ -1407,6 +1413,7 @@ def _menu_init(self) -> None: choices = ( ("List device info", self._list_device_info), ("Change device name", self._change_name_workflow), + ("Change device password", self._change_password_worklow), ("Get root fingerprint", self._get_root_fingerprint), ("Retrieve zpub of first account", self._display_zpub), ("Retrieve a BTC address", self._btc_address), diff --git a/src/keystore.c b/src/keystore.c index c6d5b0949..d9f6d72f1 100644 --- a/src/keystore.c +++ b/src/keystore.c @@ -234,18 +234,11 @@ static bool _verify_seed( return true; } -keystore_error_t keystore_encrypt_and_store_seed( +static keystore_error_t _set_new_password( const uint8_t* seed, size_t seed_length, const char* password) { - if (memory_is_initialized()) { - return KEYSTORE_ERR_MEMORY; - } - keystore_lock(); - if (!_validate_seed_length(seed_length)) { - return KEYSTORE_ERR_SEED_SIZE; - } if (securechip_init_new_password(password)) { return KEYSTORE_ERR_SECURECHIP; } @@ -277,6 +270,21 @@ keystore_error_t keystore_encrypt_and_store_seed( return KEYSTORE_OK; } +keystore_error_t keystore_encrypt_and_store_seed( + const uint8_t* seed, + size_t seed_length, + const char* password) +{ + if (memory_is_initialized()) { + return KEYSTORE_ERR_MEMORY; + } + keystore_lock(); + if (!_validate_seed_length(seed_length)) { + return KEYSTORE_ERR_SEED_SIZE; + } + return _set_new_password(seed, seed_length, password); +} + keystore_error_t keystore_create_and_store_seed( const char* password, const uint8_t* host_entropy, @@ -380,6 +388,50 @@ static void _delete_retained_seeds(void) _retained_bip39_seed_encrypted_len = 0; } +keystore_error_t keystore_change_password(const char* old_password, const char* new_password) +{ + uint8_t bip39_seed[64] = {0}; + UTIL_CLEANUP_64(bip39_seed); + if (!_copy_bip39_seed(bip39_seed)) { + return KEYSTORE_ERR_MEMORY; + } + + uint8_t encrypted_seed_and_hmac[96]; + UTIL_CLEANUP_32(encrypted_seed_and_hmac); + uint8_t encrypted_len; + if (!memory_get_encrypted_seed_and_hmac(encrypted_seed_and_hmac, &encrypted_len)) { + return KEYSTORE_ERR_MEMORY; + } + + uint8_t secret[32]; + UTIL_CLEANUP_32(secret); + if (securechip_stretch_password(old_password, secret)) { + return KEYSTORE_ERR_SECURECHIP; + } + + size_t decrypted_len = encrypted_len - 48; + uint8_t decrypted[decrypted_len]; + if (!cipher_aes_hmac_decrypt( + encrypted_seed_and_hmac, encrypted_len, decrypted, &decrypted_len, secret)) { + return KEYSTORE_ERR_INCORRECT_PASSWORD; + } + + keystore_error_t result = _set_new_password(decrypted, decrypted_len, new_password); + if (result != KEYSTORE_OK) { + return result; + } + + keystore_error_t retain_seed_result = _retain_seed(decrypted, decrypted_len); + if (retain_seed_result != KEYSTORE_OK) { + return retain_seed_result; + } + if (!_retain_bip39_seed(bip39_seed)) { + return KEYSTORE_ERR_MEMORY; + } + + return KEYSTORE_OK; +} + keystore_error_t keystore_unlock( const char* password, uint8_t* remaining_attempts_out, diff --git a/src/keystore.h b/src/keystore.h index ba2f37f9b..1c7106464 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -77,6 +77,14 @@ USE_RESULT keystore_error_t keystore_create_and_store_seed( const uint8_t* host_entropy, size_t host_entropy_size); +/** + * Changes the password of the keystore. + * @param[in] old_password The old password. + * @param[in] new_password The new password. + */ +USE_RESULT keystore_error_t +keystore_change_password(const char* old_password, const char* new_password); + /** Unlocks the keystore seed or checks the password: * If the keystore is locked, it decrypts and loads the seed, unlocking the keystore: * 1) loads the stored seed and tries to decrypt using password. diff --git a/src/rust/bitbox02-rust/src/hww/api.rs b/src/rust/bitbox02-rust/src/hww/api.rs index ba528f22a..d898fc6da 100644 --- a/src/rust/bitbox02-rust/src/hww/api.rs +++ b/src/rust/bitbox02-rust/src/hww/api.rs @@ -27,6 +27,7 @@ mod cardano; mod backup; mod bip85; +mod change_password; mod device_info; mod electrum; mod reset; @@ -115,6 +116,7 @@ fn can_call(request: &Request) -> bool { Request::CheckSdcard(_) => true, Request::InsertRemoveSdcard(_) => true, Request::ListBackups(_) => true, + Request::ChangePassword(_) => true, Request::SetPassword(_) => matches!(state, State::Uninitialized | State::Seeded), Request::RestoreBackup(_) => matches!(state, State::Uninitialized | State::Seeded), Request::RestoreFromMnemonic(_) => matches!(state, State::Uninitialized | State::Seeded), @@ -145,6 +147,7 @@ async fn process_api(request: &Request) -> Result { Request::DeviceInfo(_) => device_info::process(), Request::DeviceName(ref request) => set_device_name::process(request).await, Request::SetPassword(ref request) => set_password::process(request).await, + Request::ChangePassword(_) => change_password::process().await, Request::Reset(_) => reset::process().await, Request::SetMnemonicPassphraseEnabled(ref request) => { set_mnemonic_passphrase_enabled::process(request).await diff --git a/src/rust/bitbox02-rust/src/hww/api/change_password.rs b/src/rust/bitbox02-rust/src/hww/api/change_password.rs new file mode 100644 index 000000000..332d53cea --- /dev/null +++ b/src/rust/bitbox02-rust/src/hww/api/change_password.rs @@ -0,0 +1,34 @@ +// Copyright 2024 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::Error; +use crate::pb; + +use crate::workflow::{password, status, unlock}; +use bitbox02::keystore; +use pb::response::Response; + +pub async fn process() -> Result { + let old_password = password::enter("Old password", false, unlock::CanCancel::Yes).await?; + + if keystore::unlock(&old_password).is_err() { + status::status("Wrong password", false).await; + return Err(Error::Generic); + } + + let new_password = password::enter_twice().await?; + keystore::change_password(&old_password, &new_password).unwrap(); + + Ok(Response::Success(pb::Success {})) +} diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs index b30b7eaf1..1237d5b32 100644 --- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs +++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs @@ -194,6 +194,9 @@ pub struct SetPasswordRequest { pub entropy: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct ChangePasswordRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AntiKleptoHostNonceCommitment { #[prost(bytes = "vec", tag = "1")] @@ -1815,7 +1818,7 @@ pub struct Success {} pub struct Request { #[prost( oneof = "request::Request", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29" )] pub request: ::core::option::Option, } @@ -1878,6 +1881,8 @@ pub mod request { Cardano(super::CardanoRequest), #[prost(message, tag = "28")] Bip85(super::Bip85Request), + #[prost(message, tag = "29")] + ChangePassword(super::ChangePasswordRequest), } } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 7bc80b41e..d1174077a 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -63,6 +63,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "keystore_bip39_mnemonic_to_seed", "keystore_bip85_bip39", "keystore_bip85_ln", + "keystore_change_password", "keystore_copy_seed", "keystore_create_and_store_seed", "keystore_encode_xpub_at_keypath", diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs index 53076f686..76b51a4ff 100644 --- a/src/rust/bitbox02/src/keystore.rs +++ b/src/rust/bitbox02/src/keystore.rs @@ -106,6 +106,18 @@ pub fn create_and_store_seed(password: &SafeInputString, host_entropy: &[u8]) -> } } +pub fn change_password( + old_password: &SafeInputString, + new_password: &SafeInputString, +) -> Result<(), Error> { + match unsafe { + bitbox02_sys::keystore_change_password(old_password.as_cstr(), new_password.as_cstr()) + } { + keystore_error_t::KEYSTORE_OK => Ok(()), + err => Err(err.into()), + } +} + pub fn copy_seed() -> Result>, ()> { let mut seed = zeroize::Zeroizing::new([0u8; MAX_SEED_LENGTH].to_vec()); let mut seed_len: usize = 0; diff --git a/test/unit-test/test_keystore_functional.c b/test/unit-test/test_keystore_functional.c index 9de48edc4..8435660b4 100644 --- a/test/unit-test/test_keystore_functional.c +++ b/test/unit-test/test_keystore_functional.c @@ -262,6 +262,34 @@ static void _test_fixtures(void** state) } } +static void _test_passwords(void** state) +{ + _smarteeprom_reset(); + assert_true(keystore_is_locked()); + + will_return(__wrap_memory_is_initialized, false); + assert_int_equal(keystore_encrypt_and_store_seed(_seed, 32, _some_password), KEYSTORE_OK); + + uint8_t remaining_attempts; + will_return(__wrap_memory_is_seeded, true); + assert_int_equal(KEYSTORE_OK, keystore_unlock(_some_password, &remaining_attempts, NULL)); + + assert_int_equal(KEYSTORE_OK, keystore_change_password(_some_password, _some_other_password)); + + keystore_lock(); + assert_true(keystore_is_locked()); + + will_return(__wrap_memory_is_seeded, true); + assert_int_equal( + KEYSTORE_ERR_INCORRECT_PASSWORD, + keystore_unlock(_some_password, &remaining_attempts, NULL)); + + will_return(__wrap_memory_is_seeded, true); + assert_int_equal(KEYSTORE_OK, keystore_unlock(_some_other_password, &remaining_attempts, NULL)); + + keystore_lock(); +} + int main(void) { mock_memory_set_salt_root(_salt_root); @@ -269,6 +297,7 @@ int main(void) const struct CMUnitTest tests[] = { cmocka_unit_test(_test_seeds), cmocka_unit_test(_test_fixtures), + cmocka_unit_test(_test_passwords), }; return cmocka_run_group_tests(tests, NULL, NULL); }