1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-21 23:18:13 +00:00

python/cosi: improve API

cosi.verify was renamed to verify_combined, because it is pretty much
ed25519.verify, and the new name implies what it does in terms of the
CoSi scheme: verify a signature with already-combined public keys.

cosi.verify_m_of_n signature was simplified by not requiring the `n`
parameter, which is not important for verification. The updated function
was renamed to cosi.verify, because this is the standard CoSi
verification operation: given signature, digest, required number of
signatures, sigmask, and a list of public keys, verify that enough
signatures are indicated and that they sign the digest.
This commit is contained in:
matejcik 2019-12-20 13:45:41 +01:00 committed by Pavol Rusnak
parent 941087179f
commit 15bd35824b
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D
7 changed files with 64 additions and 46 deletions

View File

@ -311,15 +311,19 @@ def binopen(filename):
firmware = FirmwareImage(data, vheader.hdrlen)
# check signatures against signing keys in the vendor header
if firmware.sigmask > 0:
pk = [vheader.vpub[i] for i in range(8) if firmware.sigmask & (1 << i)]
global_pk = cosi.combine_keys(pk)
hdr = (
subdata[: IMAGE_HEADER_SIZE - IMAGE_SIG_SIZE]
+ IMAGE_SIG_SIZE * b"\x00"
)
digest = pyblake2.blake2s(hdr).digest()
try:
cosi.verify(firmware.sig, digest, global_pk)
cosi.verify(
firmware.sig,
digest,
vheader.vsig_m,
vheader.vpub,
firmware.sigmask,
)
print("Firmware signature OK")
except ValueError:
print("Firmware signature INCORRECT")

View File

@ -55,7 +55,7 @@ def sign(index, filename, seckeys):
sigs.append(sig)
# compute global signature
sig = cosi.combine_sig(global_R, sigs)
cosi.verify(sig, digest, global_pk)
cosi.verify_combined(sig, digest, global_pk)
print(binascii.hexlify(sig).decode())

View File

@ -66,7 +66,7 @@ def sign(index, filename, participants):
print("collected signature #%d from %s" % (i, p._pyroUri.host))
# compute global signature
sig = cosi.combine_sig(global_R, sigs)
cosi.verify(sig, digest, global_pk)
cosi.verify_combined(sig, digest, global_pk)
print("global signature:")
print(binascii.hexlify(sig).decode())

View File

@ -67,31 +67,45 @@ def get_nonce(
return r, Ed25519PublicPoint(_ed25519.encodepoint(R))
def verify(
def verify_combined(
signature: Ed25519Signature, digest: bytes, pub_key: Ed25519PublicPoint
) -> None:
"""Verify Ed25519 signature. Raise exception if the signature is invalid."""
"""Verify Ed25519 signature. Raise exception if the signature is invalid.
A CoSi combined signature is equivalent to a plain Ed25519 signature with a public
key that is a combination of the cosigners' public keys. This function takes the
combined public key and performs simple Ed25519 verification.
"""
# XXX this *might* change to bool function
_ed25519.checkvalid(signature, digest, pub_key)
def verify_m_of_n(
def verify(
signature: Ed25519Signature,
digest: bytes,
m: int,
n: int,
mask: int,
sigs_required: int,
keys: List[Ed25519PublicPoint],
mask: int,
) -> None:
if m < 1:
raise ValueError("At least 1 signer must be specified")
selected_keys = [keys[i] for i in range(n) if mask & (1 << i)]
if len(selected_keys) < m:
raise ValueError(
"Not enough signers ({} required, {} found)".format(m, len(selected_keys))
)
"""Verify a CoSi multi-signature. Raise exception if the signature is invalid.
This function verifies a M-of-N signature scheme. The arguments are:
- the minimum number M of signatures required
- public keys of all N possible cosigners
- a bitmask specifying which of the N cosigners have produced the signature.
The verification checks that the mask specifies at least M cosigners, then combines
the selected public keys and verifies the signature against the combined key.
"""
if sigs_required < 1:
raise ValueError("At least one signer must be specified.")
if mask.bit_length() > len(keys):
raise ValueError("Sigmask specifies more public keys than provided.")
selected_keys = [key for i, key in enumerate(keys) if mask & (1 << i)]
if len(selected_keys) < sigs_required:
raise _ed25519.SignatureMismatch("Insufficient number of signatures.")
global_pk = combine_keys(selected_keys)
return verify(signature, digest, global_pk)
return verify_combined(signature, digest, global_pk)
def pubkey_from_privkey(privkey: Ed25519PrivateKey) -> Ed25519PublicPoint:

View File

@ -49,8 +49,8 @@ V2_BOOTLOADER_KEYS = [
"b8307a71f552c60a4cbb317ff48b82cdbf6b6bb5f04c920fec7badf017883751",
)
]
V2_BOOTLOADER_M = 2
V2_BOOTLOADER_N = 3
V2_SIGS_REQUIRED = 2
ONEV2_CHUNK_SIZE = 1024 * 64
V2_CHUNK_SIZE = 1024 * 128
@ -408,13 +408,12 @@ def validate_v2(fw: c.Container, skip_vendor_header: bool = False) -> None:
try:
# if you want to validate a custom vendor header, you can modify
# the global variables to match your keys and m-of-n scheme
cosi.verify_m_of_n(
cosi.verify(
fw.vendor_header.signature,
vendor_fingerprint,
V2_BOOTLOADER_M,
V2_BOOTLOADER_N,
fw.vendor_header.sigmask,
V2_SIGS_REQUIRED,
V2_BOOTLOADER_KEYS,
fw.vendor_header.sigmask,
)
except Exception:
raise InvalidSignatureError("Invalid vendor header signature.")
@ -425,13 +424,12 @@ def validate_v2(fw: c.Container, skip_vendor_header: bool = False) -> None:
# raise ValueError("Vendor header expired.")
try:
cosi.verify_m_of_n(
cosi.verify(
fw.image.header.signature,
fingerprint,
fw.vendor_header.vendor_sigs_required,
fw.vendor_header.vendor_sigs_n,
fw.image.header.sigmask,
fw.vendor_header.pubkeys,
fw.image.header.sigmask,
)
except Exception:
raise InvalidSignatureError("Invalid firmware signature.")

View File

@ -104,13 +104,13 @@ def test_single_eddsa_vector(privkey, pubkey, message, signature):
my_pubkey = cosi.pubkey_from_privkey(privkey)
assert my_pubkey == pubkey
try:
cosi.verify(signature, message, pubkey)
cosi.verify_combined(signature, message, pubkey)
except ValueError:
pytest.fail("Signature does not verify.")
fake_signature = signature[:37] + b"\xf0" + signature[38:]
with pytest.raises(_ed25519.SignatureMismatch):
cosi.verify(fake_signature, message, pubkey)
cosi.verify_combined(fake_signature, message, pubkey)
def test_combine_keys():
@ -148,7 +148,7 @@ def test_cosi_combination(keyset):
global_sig = cosi.combine_sig(global_commit, signatures)
try:
cosi.verify(global_sig, message, global_pk)
cosi.verify_combined(global_sig, message, global_pk)
except Exception:
pytest.fail("Failed to validate global signature")
@ -175,25 +175,27 @@ def test_m_of_n():
try:
# this is what we are actually doing
cosi.verify_m_of_n(global_sig, message, 3, 4, sigmask, pubkeys)
cosi.verify(global_sig, message, 3, pubkeys, sigmask)
# we can require less signers too
cosi.verify_m_of_n(global_sig, message, 1, 4, sigmask, pubkeys)
cosi.verify(global_sig, message, 1, pubkeys, sigmask)
except Exception:
pytest.fail("Failed to validate by sigmask")
# and now for various ways that should fail
with pytest.raises(ValueError) as e:
cosi.verify_m_of_n(global_sig, message, 4, 4, sigmask, pubkeys)
assert "Not enough signers" in e.value.args[0]
cosi.verify(global_sig, message, 3, pubkeys[:2], sigmask)
assert "more public keys than provided" in e.value.args[0]
with pytest.raises(_ed25519.SignatureMismatch):
# when N < number of possible signers, the topmost signers will be ignored
cosi.verify_m_of_n(global_sig, message, 2, 3, sigmask, pubkeys)
with pytest.raises(ValueError) as e:
cosi.verify(global_sig, message, 0, pubkeys, 0)
assert "At least one signer" in e.value.args[0]
with pytest.raises(_ed25519.SignatureMismatch):
with pytest.raises(_ed25519.SignatureMismatch) as e:
# at least 5 signatures required
cosi.verify(global_sig, message, 5, pubkeys, sigmask)
assert "Insufficient number of signatures" in e.value.args[0]
with pytest.raises(_ed25519.SignatureMismatch) as e:
# wrong sigmask
cosi.verify_m_of_n(global_sig, message, 1, 4, 5, pubkeys)
with pytest.raises(ValueError):
# can't use "0 of N" scheme
cosi.verify_m_of_n(global_sig, message, 0, 4, sigmask, pubkeys)
cosi.verify(global_sig, message, 3, pubkeys, 7)
assert "signature does not pass verification" in e.value.args[0]

View File

@ -73,7 +73,7 @@ class TestCosi:
global_R, [sig0.signature, sig1.signature, sig2.signature]
)
cosi.verify(sig, digest, global_pk)
cosi.verify_combined(sig, digest, global_pk)
def test_cosi_compat(self, client):
digest = sha256(b"this is not a pipe").digest()
@ -94,4 +94,4 @@ class TestCosi:
)
sig = cosi.combine_sig(global_R, [remote_sig.signature, local_sig])
cosi.verify(sig, digest, global_pk)
cosi.verify_combined(sig, digest, global_pk)