From 7024a5bcffdc26b224748ba190f131783077fc66 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Wed, 24 Feb 2021 13:12:53 +0100 Subject: [PATCH 1/4] Add 63 bytes short OpenPGP EdDSA test signature Add special crafted test signature, using GnuPG/MacGPG2 v2.2.24, where the DATA and TIMESTAMP to be hashed and signed are chosen such that the S value of the resulting signature starts with a 0-byte and is thus omitted as per the RFC 4880 MPI encoding, making the overall signature one byte shorter than required as per RFC 8032. Kudos to @jku for providing the following reproducer: ``` DATA="hello" TIMESTAMP=1613479847 HOMEDIR="tests/gpg_keyrings/eddsa/" echo -n $DATA | gpg --faked-system-time $TIMESTAMP \ --homedir $HOMEDIR \ --digest-algo SHA256 \ --local-user 4E630F84838BF6F7447B830B22692F5FEA9E2DD2 \ --detach-sign > short.sig gpg --list-packets --verbose short.sig echo -n $DATA | gpg --homedir $HOMEDIR --verify short.sig - ``` --- tests/gpg_keyrings/eddsa/short.sig | Bin 0 -> 118 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/gpg_keyrings/eddsa/short.sig diff --git a/tests/gpg_keyrings/eddsa/short.sig b/tests/gpg_keyrings/eddsa/short.sig new file mode 100644 index 0000000000000000000000000000000000000000..54941717244e227fcbbfac8ad108744a6329b583 GIT binary patch literal 118 zcmeAuVPO#CV2~A4WbsSpZ)xuS_T8nrnOiASKmOG`-Ak-Y3EKOYGjMSVz(nrYF*5Ak z%eF`Q<6-@&|JN;wv~fOL-OYR^b;|NRn`8GeWSDd)G5pvVd?Z3!m+zv>qXm_XWebEq UT~VK+^>$0ihe-Ez-xhEJ0A)flRsaA1 literal 0 HcmV?d00001 From 8a2e94d4ba7d1316ff75162af61f3a12eebd2f16 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Wed, 24 Feb 2021 13:13:36 +0100 Subject: [PATCH 2/4] Add EdDSA signature length constant EdDSA signatures must be 64 bytes long as per RFC 8032 5.1.6. (6). This commit adds a constant for this length which may be used to to pad shorter signatures. --- securesystemslib/gpg/eddsa.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/securesystemslib/gpg/eddsa.py b/securesystemslib/gpg/eddsa.py index 2d5eafc3..55df62d8 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 From 8dbbec70ce76c10a224ec75fd88872078966f751 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Wed, 24 Feb 2021 13:13:58 +0100 Subject: [PATCH 3/4] Optionally pad OpenPGP EdDSA signature parameters 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. Note that GnuPG seems to correctly parse EdDSA signatures with MPIs that omit leading zeros prior to validation. But in order to validate these signatures outside the context of RFC 4880, e.g. with pyca/cryptography's OpenSSL backend, we need to guarantee the correct length of the parsed signatures ourselves. --- securesystemslib/gpg/eddsa.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/securesystemslib/gpg/eddsa.py b/securesystemslib/gpg/eddsa.py index 55df62d8..eed54f08 100644 --- a/securesystemslib/gpg/eddsa.py +++ b/securesystemslib/gpg/eddsa.py @@ -142,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 From b55fa42d46ce4ba14234eff252ffa9d8ed843444 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Wed, 24 Feb 2021 13:14:09 +0100 Subject: [PATCH 4/4] Test padding of short OpenPGP EdDSA signature Test correct padding of OpenPGP EdDSA signature upon parsing, using a special-crafted short signature pre-generated with GnuPG. --- tests/test_gpg.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) 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()