diff --git a/securesystemslib/gpg/eddsa.py b/securesystemslib/gpg/eddsa.py index 2d5eafc3..eed54f08 100644 --- a/securesystemslib/gpg/eddsa.py +++ b/securesystemslib/gpg/eddsa.py @@ -37,6 +37,8 @@ # EdDSA Point Format (see RFC4880-bis8 13.3.) ED25519_PUBLIC_KEY_LENGTH = 33 ED25519_PUBLIC_KEY_PREFIX = 0x40 +# EdDSA signature byte length (see RFC 8032 5.1.6. (6)) +ED25519_SIG_LENGTH = 64 @@ -140,6 +142,12 @@ def get_signature_params(data): ptr += 2 s = data[ptr:ptr + s_length] + # Left-zero-pad 'r' and 's' values that are shorter than required by RFC 8032 + # (5.1.6.), to make up for omitted leading zeros in RFC 4880 (3.2.) MPIs. + # This is especially important for 's', which is little-endian. + r = r.rjust(ED25519_SIG_LENGTH // 2, b"\x00") + s = s.rjust(ED25519_SIG_LENGTH // 2, b"\x00") + return r + s diff --git a/tests/gpg_keyrings/eddsa/short.sig b/tests/gpg_keyrings/eddsa/short.sig new file mode 100644 index 00000000..54941717 Binary files /dev/null and b/tests/gpg_keyrings/eddsa/short.sig differ diff --git a/tests/test_gpg.py b/tests/test_gpg.py index bffe065e..7a5e548b 100644 --- a/tests/test_gpg.py +++ b/tests/test_gpg.py @@ -46,6 +46,7 @@ from securesystemslib.gpg.rsa import create_pubkey as rsa_create_pubkey from securesystemslib.gpg.dsa import create_pubkey as dsa_create_pubkey from securesystemslib.gpg.eddsa import create_pubkey as eddsa_create_pubkey +from securesystemslib.gpg.eddsa import ED25519_SIG_LENGTH from securesystemslib.gpg.common import (parse_pubkey_payload, parse_pubkey_bundle, get_pubkey_bundle, _assign_certified_key_info, _get_verified_subkeys, parse_signature_packet) @@ -778,5 +779,25 @@ def test_gpg_sign_and_verify_object_with_specific_key(self): self.assertFalse(verify_signature(signature, key_data, wrong_data)) + def test_verify_short_signature(self): + """Correctly verify a special-crafted short signature. """ + + test_data = b"hello" + signature_path = os.path.join(self.gnupg_home, "short.sig") + + # Read special-crafted raw gpg signature that is one byte too short + with open(signature_path, "rb") as f: + signature_data = f.read() + + # Check that the signature is padded upon parsing + # NOTE: The returned signature is a hex string and thus twice as long + signature = parse_signature_packet(signature_data) + self.assertTrue(len(signature["signature"]) == (ED25519_SIG_LENGTH * 2)) + + # Check that the signature can be successfully verified + key = export_pubkey(self.default_keyid, homedir=self.gnupg_home) + self.assertTrue(verify_signature(signature, key, test_data)) + + if __name__ == "__main__": unittest.main()