Skip to content
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

Release 24.11.1 #478

Merged
merged 4 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading