diff --git a/legacy/bootloader/firmware_sign.py b/legacy/bootloader/firmware_sign.py index b58f3f5563..8cf69d8c46 100755 --- a/legacy/bootloader/firmware_sign.py +++ b/legacy/bootloader/firmware_sign.py @@ -3,7 +3,13 @@ import argparse import hashlib import struct -import ecdsa +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature, + encode_dss_signature, +) +from cryptography.exceptions import InvalidSignature SLOTS = 3 @@ -129,14 +135,16 @@ def check_signatures(data): print(f"Slot #{x + 1}", "is empty") else: pk = pubkeys[indexes[x]] - verify = ecdsa.VerifyingKey.from_string( - bytes.fromhex(pk)[1:], - curve=ecdsa.curves.SECP256k1, - hashfunc=hashlib.sha256, + verify = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256K1(), + bytes.fromhex(pk), ) try: - verify.verify(signature, to_sign, hashfunc=hashlib.sha256) + r = int.from_bytes(signature[:32], "big") + s = int.from_bytes(signature[32:], "big") + der_sig = encode_dss_signature(r, s) + verify.verify(der_sig, to_sign, ec.ECDSA(hashes.SHA256())) if indexes[x] in used: print(f"Slot #{x + 1} signature: DUPLICATE", signature.hex()) @@ -176,7 +184,7 @@ def sign(data, is_pem): if pem_key.strip() == "": # Blank key,let's remove existing signature from slot return modify(data, slot, 0, b"\x00" * 64) - key = ecdsa.SigningKey.from_pem(pem_key) + key = serialization.load_pem_private_key(pem_key.encode(), password=None) else: print("Paste SECEXP (in hex) and press Enter:") print("(blank private key removes the signature on given index)") @@ -184,16 +192,15 @@ def sign(data, is_pem): if secexp.strip() == "": # Blank key,let's remove existing signature from slot return modify(data, slot, 0, b"\x00" * 64) - key = ecdsa.SigningKey.from_secret_exponent( - secexp=int(secexp, 16), - curve=ecdsa.curves.SECP256k1, - hashfunc=hashlib.sha256, - ) + key = ec.derive_private_key(int(secexp, 16), ec.SECP256K1()) to_sign = get_header(data, zero_signatures=True) # Locate proper index of current signing key - pubkey = "04" + key.get_verifying_key().to_string().hex() + pubkey = key.public_key().public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ).hex() index = None for i, pk in pubkeys.items(): if pk == pubkey: @@ -203,26 +210,41 @@ def sign(data, is_pem): if index is None: raise Exception("Unable to find private key index. Unknown private key?") - signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256) + der_sig = key.sign(to_sign, ec.ECDSA(hashes.SHA256())) + r, s = decode_dss_signature(der_sig) + signature = r.to_bytes(32, "big") + s.to_bytes(32, "big") return modify(data, slot, index, signature) def main(args): if args.generate: - key = ecdsa.SigningKey.generate( - curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256 - ) + key = ec.generate_private_key(ec.SECP256K1()) print("PRIVATE KEY (SECEXP):") - print(key.to_string().hex()) + print( + key.private_numbers().private_value.to_bytes(32, "big").hex() + ) print() print("PRIVATE KEY (PEM):") - print(key.to_pem()) + print( + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption(), + ).decode() + ) print("PUBLIC KEY:") - print("04" + key.get_verifying_key().to_string().hex()) + print( + key.public_key() + .public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ) + .hex() + ) return if not args.path: diff --git a/legacy/bootloader/firmware_sign_dev.py b/legacy/bootloader/firmware_sign_dev.py index 393fc83dab..48a05ddc07 100644 --- a/legacy/bootloader/firmware_sign_dev.py +++ b/legacy/bootloader/firmware_sign_dev.py @@ -3,8 +3,13 @@ import argparse import hashlib import struct -import ecdsa -from ecdsa import BadSignatureError +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature, + encode_dss_signature, +) +from cryptography.exceptions import InvalidSignature SLOTS = 3 @@ -121,14 +126,16 @@ def check_signatures(data): else: pubkeys = pubkeys_dev pk = pubkeys[indexes[x]] - verify = ecdsa.VerifyingKey.from_string( - bytes.fromhex(pk)[1:], - curve=ecdsa.curves.SECP256k1, - hashfunc=hashlib.sha256, + verify = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256K1(), + bytes.fromhex(pk), ) try: - verify.verify(signature, to_sign, hashfunc=hashlib.sha256) + r = int.from_bytes(signature[:32], "big") + s = int.from_bytes(signature[32:], "big") + der_sig = encode_dss_signature(r, s) + verify.verify(der_sig, to_sign, ec.ECDSA(hashes.SHA256())) if indexes[x] in used: print(f"Slot #{x + 1} signature: DUPLICATE", signature.hex()) @@ -150,16 +157,15 @@ def modify(data, slot, index, signature): def sign(data, slot, secexp): - key = ecdsa.SigningKey.from_secret_exponent( - secexp=int(secexp, 16), - curve=ecdsa.curves.SECP256k1, - hashfunc=hashlib.sha256, - ) + key = ec.derive_private_key(int(secexp, 16), ec.SECP256K1()) to_sign = get_header(data, zero_signatures=True) # Locate proper index of current signing key - pubkey = "04" + key.get_verifying_key().to_string().hex() + pubkey = key.public_key().public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ).hex() index = None pubkeys = pubkeys_dev @@ -171,7 +177,9 @@ def sign(data, slot, secexp): if index is None: raise Exception("Unable to find private key index. Unknown private key?") - signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256) + der_sig = key.sign(to_sign, ec.ECDSA(hashes.SHA256())) + r, s = decode_dss_signature(der_sig) + signature = r.to_bytes(32, "big") + s.to_bytes(32, "big") return modify(data, slot, index, signature) @@ -223,14 +231,16 @@ def check_signatures_old(data): else: pubkeys = pubkeys_dev pk = pubkeys[indexes[x]] - verify = ecdsa.VerifyingKey.from_string( - bytes.fromhex(pk)[1:], - curve=ecdsa.curves.SECP256k1, - hashfunc=hashlib.sha256, + verify = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256K1(), + bytes.fromhex(pk), ) try: - verify.verify(signature, to_sign, hashfunc=hashlib.sha256) + r = int.from_bytes(signature[:32], "big") + s = int.from_bytes(signature[32:], "big") + der_sig = encode_dss_signature(r, s) + verify.verify(der_sig, to_sign, ec.ECDSA(hashes.SHA256())) if indexes[x] in used: print("Slot #%d signature: DUPLICATE" % (x + 1), signature.hex()) @@ -238,7 +248,7 @@ def check_signatures_old(data): used.append(indexes[x]) print("Slot #%d signature: VALID" % (x + 1), signature.hex()) - except BadSignatureError: + except InvalidSignature: print("Slot #%d signature: INVALID" % (x + 1), signature.hex()) @@ -261,16 +271,15 @@ def modify_old(data, slot, index, signature): def sign_old(data, slot, secexp): - key = ecdsa.SigningKey.from_secret_exponent( - secexp=int(secexp, 16), - curve=ecdsa.curves.SECP256k1, - hashfunc=hashlib.sha256, - ) + key = ec.derive_private_key(int(secexp, 16), ec.SECP256K1()) to_sign = prepare_old(data)[256:] # without meta # Locate proper index of current signing key - pubkey = "04" + key.get_verifying_key().to_string().hex() + pubkey = key.public_key().public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ).hex() index = None pubkeys = pubkeys_dev @@ -283,7 +292,9 @@ def sign_old(data, slot, secexp): if index is None: raise Exception("Unable to find private key index. Unknown private key?") - signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256) + der_sig = key.sign(to_sign, ec.ECDSA(hashes.SHA256())) + r, s = decode_dss_signature(der_sig) + signature = r.to_bytes(32, "big") + s.to_bytes(32, "big") return modify_old(data, slot, index, signature) diff --git a/legacy/bootloader/firmware_sign_split.py b/legacy/bootloader/firmware_sign_split.py index ea034dc475..f01ea7b1de 100755 --- a/legacy/bootloader/firmware_sign_split.py +++ b/legacy/bootloader/firmware_sign_split.py @@ -4,7 +4,9 @@ import os import subprocess from binascii import hexlify, unhexlify -import ecdsa +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature print("master secret:", end="") h = input() @@ -20,16 +22,25 @@ print() for i in range(1, 6): se = hashlib.sha256(h + chr(i).encode("ascii")).hexdigest() print("seckey", i, ":", se) - sk = ecdsa.SigningKey.from_secret_exponent( - secexp=int(se, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256 + sk = ec.derive_private_key(int(se, 16), ec.SECP256K1()) + pk = sk.public_key() + pk_bytes = pk.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, ) print( "pubkey", i, ":", - (b"04" + hexlify(sk.get_verifying_key().to_string())).decode("ascii"), + hexlify(pk_bytes).decode("ascii"), + ) + print( + sk.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption(), + ).decode("ascii") ) - print(sk.to_pem().decode("ascii")) p = subprocess.Popen("ssss-split -t 3 -n 5 -x".split(" "), stdin=subprocess.PIPE) p.communicate(input=hexlify(h) + "\n") diff --git a/legacy/debug_signing/firmware_hash_verify.py b/legacy/debug_signing/firmware_hash_verify.py index 185746f0ad..07a1e25066 100755 --- a/legacy/debug_signing/firmware_hash_verify.py +++ b/legacy/debug_signing/firmware_hash_verify.py @@ -2,7 +2,9 @@ import sys from hashlib import sha256 -import ecdsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, encode_dss_signature # arg 1 - hex digest of firmware header with zeroed sigslots # arg 2 - public key (compressed or uncompressed) @@ -17,6 +19,10 @@ prefix = b"\x18Bitcoin Signed Message:\n\x20" message_predigest = prefix + digest message = sha256(message_predigest).digest() -vk = ecdsa.VerifyingKey.from_string(public_key, curve=ecdsa.SECP256k1, hashfunc=sha256) -result = vk.verify(sig, message) +vk = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key) +r = int.from_bytes(sig[:32], "big") +s = int.from_bytes(sig[32:], "big") +der_sig = encode_dss_signature(r, s) +vk.verify(der_sig, message, ec.ECDSA(Prehashed(hashes.SHA256()))) +result = True print("Signature verification result", result) diff --git a/legacy/debug_signing/sign_firmware_v2_signature.py b/legacy/debug_signing/sign_firmware_v2_signature.py index 8f3b74a77e..6006e14689 100755 --- a/legacy/debug_signing/sign_firmware_v2_signature.py +++ b/legacy/debug_signing/sign_firmware_v2_signature.py @@ -5,7 +5,14 @@ import pprint import sys from hashlib import sha1, sha256 -import ecdsa +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature, + encode_dss_signature, +) +from cryptography.exceptions import InvalidSignature + from fill_t1_fw_signatures import Signatures secret_keys_hex = [ @@ -17,13 +24,19 @@ secret_keys_hex = [ ] secret_keys = [ - ecdsa.SigningKey.from_string( - bytes.fromhex(sk), curve=ecdsa.SECP256k1, hashfunc=sha256 + ec.derive_private_key( + int.from_bytes(bytes.fromhex(sk), "big"), ec.SECP256K1() ) for sk in secret_keys_hex ] -public_keys = [sk.get_verifying_key() for sk in secret_keys] -public_keys_hex = [pk.to_string("compressed").hex() for pk in public_keys] +public_keys = [sk.public_key() for sk in secret_keys] +public_keys_hex = [ + pk.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint, + ).hex() + for pk in public_keys +] # arg1 is input trezor.bin filename to be signed # arg2 is output filename, if omitted, will use input file + ".signed" @@ -54,21 +67,21 @@ assert public_keys_hex == [ print("Sanity check") for sk, pk_hex in zip(secret_keys, public_keys_hex): - pk = ecdsa.VerifyingKey.from_string( - bytes.fromhex(pk_hex), curve=ecdsa.SECP256k1, hashfunc=sha256 + pk = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256K1(), bytes.fromhex(pk_hex) ) message = bytes(os.urandom(64)) # These should work - sig = sk.sign_deterministic(message, hashfunc=sha256) - pk.verify(sig, message, hashfunc=sha256) # throws exception if wrong + der_sig = sk.sign(message, ec.ECDSA(hashes.SHA256())) + pk.verify(der_sig, message, ec.ECDSA(hashes.SHA256())) # throws exception if wrong # These should fail try: - sig = sk.sign_deterministic(message, hashfunc=sha1) - pk.verify(sig, message, hashfunc=sha256) # should throw + der_sig = sk.sign(message, ec.ECDSA(hashes.SHA1())) + pk.verify(der_sig, message, ec.ECDSA(hashes.SHA256())) # should throw raise RuntimeError("These should not have matched!") - except ecdsa.keys.BadSignatureError: + except InvalidSignature: # print("Bad sig check fail test ok") pass # fine, should have failed @@ -87,13 +100,16 @@ for i in sig_indices: index = i - 1 # in FW indices are indexed from 1, 0 means none print(f"--- Key {index}, sigindex {i}") sk = secret_keys[index] - sig_64bytes = sk.sign_deterministic(header, hashfunc=sha256) + der_sig = sk.sign(header, ec.ECDSA(hashes.SHA256())) + r, s = decode_dss_signature(der_sig) + sig_64bytes = r.to_bytes(32, "big") + s.to_bytes(32, "big") assert len(sig_64bytes) == 64 print("Signature:", sig_64bytes.hex()) - pk = ecdsa.VerifyingKey.from_string( - bytes.fromhex(public_keys_hex[index]), curve=ecdsa.SECP256k1, hashfunc=sha256 + pk = ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256K1(), bytes.fromhex(public_keys_hex[index]) ) - pk.verify(sig_64bytes, header, hashfunc=sha256) # throws exception if wrong + der_sig_verify = encode_dss_signature(r, s) + pk.verify(der_sig_verify, header, ec.ECDSA(hashes.SHA256())) # throws exception if wrong print(f"Public key {public_keys_hex[index]}") print("Verified created sig with public key") print("=================================") diff --git a/legacy/debug_signing/sign_firmware_v3_signature.py b/legacy/debug_signing/sign_firmware_v3_signature.py index 2689ae87b5..f862396fee 100644 --- a/legacy/debug_signing/sign_firmware_v3_signature.py +++ b/legacy/debug_signing/sign_firmware_v3_signature.py @@ -5,13 +5,20 @@ from hashlib import sha256 from pathlib import Path import click -import ecdsa +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import ( + Prehashed, + decode_dss_signature, +) from trezorlib.firmware.legacy import LegacyV2Firmware from trezorlib.firmware.models import LEGACY_V3_DEV SECRET_KEYS = [ - ecdsa.SigningKey.from_string(bytes.fromhex(sk), curve=ecdsa.SECP256k1) + ec.derive_private_key( + int.from_bytes(bytes.fromhex(sk), "big"), ec.SECP256K1() + ) for sk in ( "ca8de06e1e93d101136fa6fbc41432c52b6530299dfe32808030ee8e679702f1", "dde47dd393f7d76f9b522bfa9760bc4543d2c3654491393774f54e066461fccb", @@ -19,17 +26,25 @@ SECRET_KEYS = [ ) ] -PUBLIC_KEYS: list[ecdsa.VerifyingKey] = [sk.get_verifying_key() for sk in SECRET_KEYS] +PUBLIC_KEYS = [sk.public_key() for sk in SECRET_KEYS] # Should be these public keys -assert [pk.to_string("compressed") for pk in PUBLIC_KEYS] == LEGACY_V3_DEV.firmware_keys +assert [ + pk.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.CompressedPoint, + ) + for pk in PUBLIC_KEYS +] == LEGACY_V3_DEV.firmware_keys -def signmessage(digest: bytes, key: ecdsa.SigningKey) -> bytes: +def signmessage(digest: bytes, key: ec.EllipticCurvePrivateKey) -> bytes: """Sign via SignMessage""" btc_digest = b"\x18Bitcoin Signed Message:\n\x20" + digest final_digest = sha256(sha256(btc_digest).digest()).digest() - return key.sign_digest_deterministic(final_digest, hashfunc=sha256) + der_sig = key.sign(final_digest, ec.ECDSA(Prehashed(hashes.SHA256()))) + r, s = decode_dss_signature(der_sig) + return r.to_bytes(32, "big") + s.to_bytes(32, "big") @click.command() diff --git a/pyproject.toml b/pyproject.toml index 567e4ee769..2b18455771 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "requests~=2.32", "termcolor", "Pillow>=11", - "ecdsa>=0.16,<0.17", + "pyasn1", "noiseprotocol>=0.3.1,<0.4", "west>=1.4.0,<2", diff --git a/tests/device_tests/evolu/common.py b/tests/device_tests/evolu/common.py index f63b073113..e09ef9e0fc 100644 --- a/tests/device_tests/evolu/common.py +++ b/tests/device_tests/evolu/common.py @@ -2,7 +2,9 @@ import os from hashlib import sha256 from typing import List -from ecdsa import NIST256p, SigningKey +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, decode_dss_signature from trezorlib import evolu from trezorlib.debuglink import DebugSession as Session @@ -19,7 +21,9 @@ TEST_host_static_public_key = curve25519.get_public_key(TEST_host_static_private def get_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes: private_key = get_delegated_identity_key(client) - signing_key = SigningKey.from_string(private_key, curve=NIST256p) + signing_key = ec.derive_private_key( + int.from_bytes(private_key, "big"), ec.SECP256R1() + ) ctx = sha256() ctx.update(compact_size(len(header))) @@ -27,7 +31,9 @@ def get_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes: for arg in arguments: ctx.update(compact_size(len(arg))) ctx.update(arg) - return signing_key.sign_digest(ctx.digest()) + der_sig = signing_key.sign(ctx.digest(), ec.ECDSA(Prehashed(hashes.SHA256()))) + r, s = decode_dss_signature(der_sig) + return r.to_bytes(32, "big") + s.to_bytes(32, "big") def get_invalid_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes: diff --git a/tests/device_tests/evolu/test_sign_registration.py b/tests/device_tests/evolu/test_sign_registration.py index 35b076b602..c8dd1590d7 100644 --- a/tests/device_tests/evolu/test_sign_registration.py +++ b/tests/device_tests/evolu/test_sign_registration.py @@ -1,5 +1,6 @@ import pytest -from ecdsa import NIST256p, SigningKey, VerifyingKey +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec from trezorlib import evolu from trezorlib.debuglink import TrezorTestContext as Client @@ -13,10 +14,14 @@ pytestmark = pytest.mark.models("core") def signing_buffer(private_key: bytes, challenge: bytes, size: int) -> bytes: - public_key: VerifyingKey = SigningKey.from_string(private_key, curve=NIST256p).get_verifying_key() # type: ignore + sk = ec.derive_private_key(int.from_bytes(private_key, "big"), ec.SECP256R1()) + public_key_bytes = sk.public_key().public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ) components = [ b"EvoluSignRegistrationRequestV1:", - public_key.to_string("uncompressed"), + public_key_bytes, challenge, size.to_bytes(4, "big"), ] diff --git a/tests/device_tests/payment_req.py b/tests/device_tests/payment_req.py index e1c1443542..2542237085 100644 --- a/tests/device_tests/payment_req.py +++ b/tests/device_tests/payment_req.py @@ -1,7 +1,9 @@ from dataclasses import dataclass from hashlib import sha256 -from ecdsa import NIST256p, SigningKey +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed, decode_dss_signature +from cryptography.hazmat.primitives import hashes from trezorlib import messages from trezorlib.client import Session @@ -37,9 +39,12 @@ class CoinPurchaseMemo: address_resp: messages.Address | messages.EthereumAddress | None = None -payment_req_signer = SigningKey.from_string( - b"\x05\x62\x35\xb0\x47\x6f\x05\x7f\x27\x65\x21\x97\x24\xf7\xf1\x80\x7d\x58\x80\x2b\x55\x0e\xd5\xbf\x6f\x73\x05\x0a\xf5\x45\x63\x00", - curve=NIST256p, +payment_req_signer = ec.derive_private_key( + int.from_bytes( + b"\x05\x62\x35\xb0\x47\x6f\x05\x7f\x27\x65\x21\x97\x24\xf7\xf1\x80\x7d\x58\x80\x2b\x55\x0e\xd5\xbf\x6f\x73\x05\x0a\xf5\x45\x63\x00", + "big", + ), + ec.SECP256R1(), ) @@ -135,6 +140,13 @@ def make_payment_request( else None ) + der_sig = payment_req_signer.sign( + h_pr.digest(), + ec.ECDSA(Prehashed(hashes.SHA256())), + ) + r, s = decode_dss_signature(der_sig) + raw_sig = r.to_bytes(32, "big") + s.to_bytes(32, "big") + return messages.PaymentRequest( recipient_name=recipient_name, amount=( @@ -142,5 +154,5 @@ def make_payment_request( ), memos=msg_memos, nonce=nonce, - signature=payment_req_signer.sign_digest_deterministic(h_pr.digest()), + signature=raw_sig, )