Skip to content

Commit

Permalink
Merge pull request #478 from selfcustody/release-24.11.1
Browse files Browse the repository at this point in the history
Release 24.11.1
  • Loading branch information
odudex authored Nov 19, 2024
2 parents 9cd6e97 + 9e4f7e2 commit 2006140
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Changelog 24.11.1 - November 2024

### Security Fix
This release addresses a vulnerability affecting AES-CBC encrypted mnemonics stored on flash storage, SD cards, and QR codes. Due to an implementation error, the Initialization Vector (IV) in our CBC encryption, which used camera-generated entropy, was not being correctly utilized, which meant it did not provide the intended additional entropy.

# Changelog 24.11.0 - November 2024

### Tamper Check Flash Hash and Tamper Check Code (Experimental)
Expand Down
2 changes: 1 addition & 1 deletion i18n/translations/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"% of the amount.": "% do valor.",
"%d of %d multisig": "%d da %d multisig",
"%d to %d": "%d a %d",
"%s removed.": "removido.",
"%s removed.": "%s removido.",
"(%d total)": "(%d total)",
"(Experimental)": "(Experimental)",
"(watch-only)": "(Somente visualização)",
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ edit_uri: edit/main/docs
docs_dir: docs
site_dir: public
extra:
latest_krux: krux-v24.11.0
latest_krux: krux-v24.11.1
latest_installer: v0.0.20-beta
latest_installer_rpm: krux-installer-0.0.20_beta-1.x86_64.rpm
latest_installer_deb: krux-installer_0.0.20-beta_amd64.deb
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

[tool.poetry]
name = "krux"
version = "24.11.0"
version = "24.11.1"
description = "Open-source signing device firmware for Bitcoin"
authors = ["Jeff S <[email protected]>"]

Expand Down
3 changes: 2 additions & 1 deletion src/krux/encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,13 @@ def encrypt(self, raw, mode=ucryptolib.MODE_ECB, i_vector=None):
data_bytes = raw.encode("latin-1") if isinstance(raw, str) else raw
if i_vector:
encryptor = ucryptolib.aes(self.key, mode, i_vector)
data_bytes = i_vector + data_bytes
else:
encryptor = ucryptolib.aes(self.key, mode)
encrypted = encryptor.encrypt(
data_bytes + b"\x00" * ((16 - (len(data_bytes) % 16)) % 16)
)
if i_vector:
encrypted = i_vector + encrypted
return base_encode(encrypted, 64)

def decrypt(self, encrypted, mode, i_vector=None):
Expand Down
2 changes: 1 addition & 1 deletion src/krux/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
VERSION = "24.11.0"
VERSION = "24.11.1"
SIGNER_PUBKEY = "03339e883157e45891e61ca9df4cd3bb895ef32d475b8e793559ea10a36766689b"
2 changes: 1 addition & 1 deletion tests/pages/test_encryption_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
}"""
ENCRYPTED_QR_TITLE_CBC = "353175d8"
ENCRYPTED_QR_DATA_CBC = b"\x08353175d8\x01\x00\x00\n!\xa1\xf3\x8b\x9e\xa1^\x8d\xab\x08\xf7\t\xf3\x94\x06\x89Q\x15]\xe0\xc6\xabf\x9c\x12E\xbcw\xcaa\x14\xfc\xa5\x16\x15\x0f;\x88\xbc\xb4H\xbe_\xf3\xf1b\x1e\x02\xff\xea\x9a\xe9z\xfd\xc9\xef\xcd\xa0A\x0c\xd1:a\x08"
ENCRYPTED_QR_DATA_CBC = b"\x08353175d8\x01\x00\x00\nOR\xa1\x93l>2q \x9e\x9dd\x05\x9e\xd7\x8e\xa5\x95(IzR\x81\xabI:\x1e\x8a\x1d\xe7|O\xac\x9c\xe8.\x8cc\xc0\x93\x0e\xe67vpO#i\x99\xd1.\x85\xf7\x00\xfez\xadN\x9d7\xaex\xa6\xd3"

ENCRYPTED_QR_TITLE_ECB = "06b79aa2"
ENCRYPTED_QR_DATA_ECB = b"\x0806b79aa2\x00\x00\x00\n\xa4\xaaa\xb9h\x0c\xdc-i\x85\x83.9,\x91\xf1\x19E,\xc9\xf0'\xb1b7\x91mo\xa2-\xb6\x16\xac\x04-2F\x10\xda\xd1\xdb,\x85\x9fr\x1c\x8aH"
Expand Down
39 changes: 34 additions & 5 deletions tests/test_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
CBC_WORDS = "dog guitar hotel random owner gadget salute riot patrol work advice panic erode leader pass cross section laundry elder asset soul scale immune scatter"

ECB_ENCRYPTED_WORDS = "1NV55l0ny9vkFV6s4MnDvDlpiWUJo35sv5hs6ZKp4T0zVrOxXft8E/RLX9unZJJwii2/crVgr+XE/lAgWhL7YoKYtimDmbpdOFK9U84+3bE="
CBC_ENCRYPTED_WORDS = "pJy/goOD11Nulfzd07PPKCOuPWsy2/tONwHrpY/AihVDcGxmIgzasyhs3fY90E0khrCqqgCvzjukMCdxif2OljKDxZQPGoVNeJKqE4nu5fq5023WhO1yKtAcPt3mML6Q"
CBC_ENCRYPTED_WORDS = "T1Khk2w+MnEgnp1kBZ7XjpLaWFYlK9sShaYMyO7BPmjUacEhco2X1LFWDC8YRP+Z2GG4hYEoCMoHyOAoBPXh8P3Z1D5F34pWtBlgEOpGAwFBc+VeDWFRhDLWLjl6pXia"

ECB_ENCRYPTED_QR = b"\x07test ID\x00\x00\x00\n*\xe1\x9d\xc5\x82\xc1\x19\x9b\xb7&\xf2?\x03\xc7o\xf6\xaf\x9e\x81#F,Qs\xe6\x1d\xeb\xd1Y\xa0/\xcf"
CBC_ENCRYPTED_QR = b"\x07test ID\x01\x00\x00\n\xf3<k\xc1Qn\x95`hrs],^R\x9b\xfa\xec\xfe4\x9e\xf1\xaaT\x8f\xdan<,\xa7\x87Pm\xd8\x80\xd7\x15@\x95\xeb\xc1\xdb\xcd\xb2\xfc\xf7 \x8e"
CBC_ENCRYPTED_QR = b'\x07test ID\x01\x00\x00\nOR\xa1\x93l>2q \x9e\x9dd\x05\x9e\xd7\x8e\x01\x03`u_\xd7\xab/N\xbc@\x19\xcc\n"\xc5\x8a^3xt\xa4\xb3\x0bK\xca\x8a@\x82\xdaz\xd3'

ECB_QR_PUBLIC_DATA = (
"Encrypted QR Code:\nID: test ID\nVersion: AES-ECB\nKey iter.: 100000"
Expand All @@ -34,12 +34,12 @@
"cbcID": {
"version": 1,
"key_iterations": 100000,
"data": "GpNxj9kzdiTuIf1UYC6R0FHoUokBhiNLkxWgSOHBhmBHb0Ew8wk1M+VlsR4v/koCfSGOTkgjFshC36+n7mx0W0PI6NizAoPClO8DUVamd5hS6irS+Lfff0//VJWK1BcdvOJjzYw8TBiVaL1swAEEySjn5GsqF1RaJXzAMMgu03Kq32iDIDy7h/jHJTiIPCoVQAle/C9vXq2HQeVx43c0LhGXTZmIhhkHPMgDzFTsMGM="
"data": "T1Khk2w+MnEgnp1kBZ7Xjp+66c9sy20J39ffK11XvVAaDSyQybsM6txAwKy/U1iU4KKYRu3ywDDN9q9sWAi1R+y7x4aHwQd0C0rRcW0iDxvWtFyWMKilA0AsDQwvBSgkhf5PQnQ1rfjnKVF75rTrG5vUNF01FRwa9PoM5cq30Yki/hFnWj/4niaeXqgQvIwjSzBNbXgaRLjfoaUyHiu8+zBX25rkpI0PW243fgDEfqI="
}
}"""

ECB_ONLY_JSON = """{"ecbID": {"version": 0, "key_iterations": 100000, "data": "sMCvAUvVpGSCsXsBl7EBNGPZLymZoyB8eAUHb2TMbarhqD4GJga/SW/AstxIvZz6MR1opXLfF7Pyd+IJBe3E0lDQCkvqytSQfVGnVSeYz+sNfd5T1CXS0/C2zYKTKFL7RTpHd0IXHZ+GQuzX1hoJMHkh0sx0VgorVdDj87ykUQIeC95MS98y/ha2q/vWfLyIZU1hc5VcehzmTA1B6ExMGA=="}}"""
CBC_ONLY_JSON = """{"cbcID": {"version": 1, "key_iterations": 100000, "data": "GpNxj9kzdiTuIf1UYC6R0FHoUokBhiNLkxWgSOHBhmBHb0Ew8wk1M+VlsR4v/koCfSGOTkgjFshC36+n7mx0W0PI6NizAoPClO8DUVamd5hS6irS+Lfff0//VJWK1BcdvOJjzYw8TBiVaL1swAEEySjn5GsqF1RaJXzAMMgu03Kq32iDIDy7h/jHJTiIPCoVQAle/C9vXq2HQeVx43c0LhGXTZmIhhkHPMgDzFTsMGM="}}"""
CBC_ONLY_JSON = """{"cbcID": {"version": 1, "key_iterations": 100000, "data": "T1Khk2w+MnEgnp1kBZ7Xjp+66c9sy20J39ffK11XvVAaDSyQybsM6txAwKy/U1iU4KKYRu3ywDDN9q9sWAi1R+y7x4aHwQd0C0rRcW0iDxvWtFyWMKilA0AsDQwvBSgkhf5PQnQ1rfjnKVF75rTrG5vUNF01FRwa9PoM5cq30Yki/hFnWj/4niaeXqgQvIwjSzBNbXgaRLjfoaUyHiu8+zBX25rkpI0PW243fgDEfqI="}}"""
I_VECTOR = b"OR\xa1\x93l>2q \x9e\x9dd\x05\x9e\xd7\x8e"


Expand Down Expand Up @@ -70,7 +70,7 @@ def test_cbc_encryption(m5stickv):
from Crypto.Random import get_random_bytes

encryptor = AESCipher(TEST_KEY, TEST_MNEMONIC_ID, ITERATIONS)
iv = get_random_bytes(AES.block_size)
iv = I_VECTOR
encrypted = encryptor.encrypt(TEST_WORDS, AES.MODE_CBC, iv).decode("utf-8")
assert encrypted == CBC_ENCRYPTED_WORDS
data = base64.b64decode(encrypted)
Expand All @@ -80,6 +80,35 @@ def test_cbc_encryption(m5stickv):
assert decrypted == TEST_WORDS


def test_cbc_iv_use(m5stickv):
from krux.encryption import AESCipher
from Crypto.Random import get_random_bytes

SECOND_IV = b"\x8e\xc8b\x8f\xe2\xa8`\xaa\x06d\xe8\xe7\xaa.0\x03"
CBC_ENCRYPTED_WORDS_SECOND_IV = "jshij+KoYKoGZOjnqi4wA8BON70ndcVal949EOpP9Hi15hBsz3XvnpQDTC3c/6Nt8GnU4gp7OUcXvy6WuhoHrGtLOZAttnNmMQFZK6aAYy95T5MnZIsNbnJ15xewAZqb"

encryptor = AESCipher(TEST_KEY, TEST_MNEMONIC_ID, ITERATIONS)
iv = I_VECTOR
encrypted = encryptor.encrypt(TEST_WORDS, AES.MODE_CBC, iv).decode("utf-8")
assert encrypted == CBC_ENCRYPTED_WORDS
data = base64.b64decode(encrypted)
encrypted_mnemonic = data[AES.block_size :]
i_vector = data[: AES.block_size]
decrypted = encryptor.decrypt(encrypted_mnemonic, AES.MODE_CBC, i_vector)
assert decrypted == TEST_WORDS
# Encrypt again with same data except for the IV
iv = SECOND_IV
print(iv)
encrypted = encryptor.encrypt(TEST_WORDS, AES.MODE_CBC, iv).decode("utf-8")
print(encrypted)
assert encrypted == CBC_ENCRYPTED_WORDS_SECOND_IV
data = base64.b64decode(encrypted)
encrypted_mnemonic = data[AES.block_size :]
i_vector = data[: AES.block_size]
decrypted = encryptor.decrypt(encrypted_mnemonic, AES.MODE_CBC, i_vector)
assert decrypted == TEST_WORDS


def test_list_mnemonic_storage(m5stickv, mock_file_operations):
from krux.encryption import MnemonicStorage

Expand Down

0 comments on commit 2006140

Please sign in to comment.