From a3941e07580e1d350b882a69e767617a759d3bda Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 10 Jun 2023 23:37:58 -0400 Subject: [PATCH 01/16] Tests: Check on forward-compatibility failures These tests are pulled from the OpenPGP Interoperability Test Suite, in particular from those tests tagged "forward-compat": https://tests.sequoia-pgp.org/?q=forward-compat These failures make it difficult to use PGPy in an OpenPGP ecosystem that can evolve, because PGPy will complain about OpenPGP objects that contain something it doesn't understand, even though the rest of the message is otherwise comprehensible and usable. See Justus Winter's message to openpgp@ietf.org describing his concerns: https://mailarchive.ietf.org/arch/msg/openpgp/QUiEKx3PQeJOXnkcvvnuHpv739M I've selected specific examples that PGPy is known to currently fail with. --- tests/test_06_compatibility.py | 58 +++++++++++++ tests/testdata/compatibility/bob-key.pgp | 82 ++++++++++++++++++ tests/testdata/compatibility/bob.pgp | 42 +++++++++ .../bob_with_unknown_alg_certification.pgp | 50 +++++++++++ .../bob_with_unknown_ecdh_curve.pgp | 40 +++++++++ .../bob_with_unknown_ecdsa_curve.pgp | 40 +++++++++ .../bob_with_unknown_eddsa_curve.pgp | 40 +++++++++ .../bob_with_unknown_subkey_algorithm.pgp | 45 ++++++++++ ...ncrypted_signed_with_unknown_algorithm.msg | 23 +++++ ..._signed_with_unknown_signature_version.msg | 25 ++++++ .../compatibility/pkesk_unknown_pkalg.msg | 20 +++++ .../compatibility/pkesk_unknown_version.msg | 16 ++++ tests/testdata/compatibility/ricarda.pgp | 86 +++++++++++++++++++ tests/testdata/compatibility/sig23_sig4.sig | 26 ++++++ .../testdata/compatibility/sig4_b-sig4_r.sig | 26 ++++++ .../testdata/compatibility/sig4_r-sig4_b.sig | 26 ++++++ tests/testdata/compatibility/sig4_sig23.sig | 26 ++++++ .../compatibility/skesk_unknown_s2k_algo.msg | 16 ++++ .../compatibility/skesk_unknown_version.msg | 16 ++++ 19 files changed, 703 insertions(+) create mode 100644 tests/test_06_compatibility.py create mode 100644 tests/testdata/compatibility/bob-key.pgp create mode 100644 tests/testdata/compatibility/bob.pgp create mode 100644 tests/testdata/compatibility/bob_with_unknown_alg_certification.pgp create mode 100644 tests/testdata/compatibility/bob_with_unknown_ecdh_curve.pgp create mode 100644 tests/testdata/compatibility/bob_with_unknown_ecdsa_curve.pgp create mode 100644 tests/testdata/compatibility/bob_with_unknown_eddsa_curve.pgp create mode 100644 tests/testdata/compatibility/bob_with_unknown_subkey_algorithm.pgp create mode 100644 tests/testdata/compatibility/encrypted_signed_with_unknown_algorithm.msg create mode 100644 tests/testdata/compatibility/encrypted_signed_with_unknown_signature_version.msg create mode 100644 tests/testdata/compatibility/pkesk_unknown_pkalg.msg create mode 100644 tests/testdata/compatibility/pkesk_unknown_version.msg create mode 100644 tests/testdata/compatibility/ricarda.pgp create mode 100644 tests/testdata/compatibility/sig23_sig4.sig create mode 100644 tests/testdata/compatibility/sig4_b-sig4_r.sig create mode 100644 tests/testdata/compatibility/sig4_r-sig4_b.sig create mode 100644 tests/testdata/compatibility/sig4_sig23.sig create mode 100644 tests/testdata/compatibility/skesk_unknown_s2k_algo.msg create mode 100644 tests/testdata/compatibility/skesk_unknown_version.msg diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py new file mode 100644 index 00000000..e6e9aaee --- /dev/null +++ b/tests/test_06_compatibility.py @@ -0,0 +1,58 @@ +# coding=utf-8 +""" ensure that we don't crash on surprising messages +""" +import pytest + +from pgpy import PGPKey, PGPMessage +from pgpy.constants import SecurityIssues +import glob + +class TestPGP_Compatibility(object): + # test compatibility: + # - Armored object with (non-ASCII) UTF-8 comments + # - signatures with unknown pubkey algorithms + # - certs with certifications from unknown algorithms + # - certs with subkeys with unknown algorithms + # - certs with ECC subkeys with unknown curves + # - encrypted messages with surprising parts + def test_import_unicode_armored_cert(self) -> None: + k:PGPKey + (k, _) = PGPKey.from_file('tests/testdata/compatibility/ricarda.pgp') + pytest.xfail('Cannot handle UTF-8 (non-ASCII) comments in armored certificate') + assert k.check_soundness() == SecurityIssues.OK + + @pytest.mark.parametrize('sig', glob.glob('*.sig', root_dir='tests/testdata/compatibility')) + def test_bob_sig_from_multisig(self, sig:str)-> None: + k:PGPKey + (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob.pgp') + msg = 'Hello World :)' + pytest.xfail(f'Cannot handle detached signature objects with more than one signature present (see https://github.com/SecurityInnovation/PGPy/issues/197)') + + def test_cert_unknown_algo(self) -> None: + k:PGPKey + pytest.xfail('cannot handle certificates containing certifications made using unknown pubkey algorithms') + (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob_with_unknown_alg_certification.pgp') + assert k.check_soundness() == SecurityIssues.OK + + def test_cert_unknown_subkey_algo(self) -> None: + k:PGPKey + pytest.xfail('cannot handle certificates containing subkeys with unknown pubkey algorithms') + (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob_with_unknown_subkey_algorithm.pgp') + assert k.check_soundness() == SecurityIssues.OK + + @pytest.mark.parametrize('flavor', ['ecdsa', 'eddsa', 'ecdh']) + def test_cert_unknown_curve(self, flavor:str) -> None: + k:PGPKey + pytest.xfail(f'cannot handle certificates containing subkeys with unknown OIDs for {flavor}') + (k, _) = PGPKey.from_file(f'tests/testdata/compatibility/bob_with_unknown_{flavor}_curve.pgp') + assert k.check_soundness() == SecurityIssues.OK + + @pytest.mark.parametrize('msg', glob.glob('*.msg', root_dir='tests/testdata/compatibility')) + def test_unknown_message(self, msg:str)-> None: + k:PGPKey + (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob-key.pgp') + pytest.xfail('cannot handle unknowns in message formats') + msg:PGPMessage = PGPMessage.from_file(f'tests/testdata/compatibility/{msg}') + cleartext:PGPMessage = k.decrypt(msg) + assert not cleartext.is_encrypted + assert cleartext.message.startswith(b'Encrypted') diff --git a/tests/testdata/compatibility/bob-key.pgp b/tests/testdata/compatibility/bob-key.pgp new file mode 100644 index 00000000..59e981b7 --- /dev/null +++ b/tests/testdata/compatibility/bob-key.pgp @@ -0,0 +1,82 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Bob's OpenPGP Transferable Secret Key + +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 +=miES +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/testdata/compatibility/bob.pgp b/tests/testdata/compatibility/bob.pgp new file mode 100644 index 00000000..b58b8c3a --- /dev/null +++ b/tests/testdata/compatibility/bob.pgp @@ -0,0 +1,42 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Bob's OpenPGP certificate + +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 +NEJd3XZRzaXZE2aAMQ== +=NXei +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/bob_with_unknown_alg_certification.pgp b/tests/testdata/compatibility/bob_with_unknown_alg_certification.pgp new file mode 100644 index 00000000..3707582d --- /dev/null +++ b/tests/testdata/compatibility/bob_with_unknown_alg_certification.pgp @@ -0,0 +1,50 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +5KjhX2PVNEJd3XZRzaXZE2aAMQ== +=WSi3 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/bob_with_unknown_ecdh_curve.pgp b/tests/testdata/compatibility/bob_with_unknown_ecdh_curve.pgp new file mode 100644 index 00000000..4d61f469 --- /dev/null +++ b/tests/testdata/compatibility/bob_with_unknown_ecdh_curve.pgp @@ -0,0 +1,40 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +MF4/9Ikup96bHiodjzRzjg== +=4wyr +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/bob_with_unknown_ecdsa_curve.pgp b/tests/testdata/compatibility/bob_with_unknown_ecdsa_curve.pgp new file mode 100644 index 00000000..baabb928 --- /dev/null +++ b/tests/testdata/compatibility/bob_with_unknown_ecdsa_curve.pgp @@ -0,0 +1,40 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +8U1sStGbL6xCqoC7 +=F3Jy +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/bob_with_unknown_eddsa_curve.pgp b/tests/testdata/compatibility/bob_with_unknown_eddsa_curve.pgp new file mode 100644 index 00000000..1885531b --- /dev/null +++ b/tests/testdata/compatibility/bob_with_unknown_eddsa_curve.pgp @@ -0,0 +1,40 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv +/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz +/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ +5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 +X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv +9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 +qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb +SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb +vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w +bGU+wsEOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx +gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz +XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO +ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g +9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF +DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c +ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1 +6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ +ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo +zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGzsBUBF2lnPIWCwYJ +KwYBBAGColpjB/0RWUyaRh7Ih2+VvD4TXboI3UCdkWKxvQyzS+KHL9RSaPhTlYKd +Oxgj/9VGMAcK31+ORMQrJNlKEL1bhqIZ5KhxmjLjPZnSZCWQ3V3WRcALyvbr/v5q +IGIEsnZMifZZDYQflsu83Fiy22Vv2VKq90SCCqa8eSU1Asm9tyvC2oBEiqZsgkpi +1Gjcq+KtWQNFTGEKKEWvcLSWhCXt1xG4rVa7QTRKDQsw1VGKPoh90OE2eWTMp2bm +A71oPa+LvaTiTXPsgZ7ZZ0cMywqid9U+n6xom+7sLc+izxtQe5TSV782RT4h5anO +gN08ywp4M2lhJzGgy4bXyR2LyU5u+vAEny5cwsE+BBgBCgByBYJdpZzyCRD7/Mgq +AV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfKHYzE +8uay50LpLy+PQFxb/Shyo2hSCVpHN5wJwZiPkAKbIBYhBNGmbhojsYLJmA94jPv8 +yCoBXnMwAADTkwv/aTr8lQnQRyqrpc9fUaxXvEeM5teTKmTFNzaWihTxYxLPTsrZ +tKUTR3nT2c19VKwZVY0fjamZyiabd2OYSVJQiC9BYoLcYan5NKnqJNzHikd/AwzZ +2Pxco8pUsLXu0sHmsldJz+WpPXTEdVauiai6tSMCN7tY977m4XDa0pbPpSF5CjGh +ZyAV+P79NTTRj92NryWCdkfoDw3JNKc4u6/YsjIg93BMjb4iDseb/NzluMgg/WVv +Cxnlfu/DqBbhHR9pYxBAe8knes+B2F2W+LH3nJEhJWaQvafU0EobK3fi77JDluHS +0m6NoO4mhddcWOZ88xyqpyLa79uJAVK7zBfEV6mE29tqrNRtc948gpbkCFQaS0Tu +RSOeb6SZRp1Je9cSuRKtZaSakoAYfwmvQ5+EK7PKH5UNnGQHZMiY/+xV9x/C9Tlf +eg/OYJnjZwzqDLCRjmNJCEoipvq2+ecuoQRWHeb9B2k7zkzvvdczZJHNHrDzbMUT +rGbptmnWtUt/BkSu +=rsWG +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/bob_with_unknown_subkey_algorithm.pgp b/tests/testdata/compatibility/bob_with_unknown_subkey_algorithm.pgp new file mode 100644 index 00000000..4802a8ce --- /dev/null +++ b/tests/testdata/compatibility/bob_with_unknown_subkey_algorithm.pgp @@ -0,0 +1,45 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +GayI21FEA+d9JQ+3rFmecxxZ +=z8mg +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/encrypted_signed_with_unknown_algorithm.msg b/tests/testdata/compatibility/encrypted_signed_with_unknown_algorithm.msg new file mode 100644 index 00000000..0c789821 --- /dev/null +++ b/tests/testdata/compatibility/encrypted_signed_with_unknown_algorithm.msg @@ -0,0 +1,23 @@ +-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv+OGc6ftXom/H7P7G/pg6XUaDdAJ4aWqvKprWyoJ+nlqC2 +ZTjhRab15GGKDPNgcCVVB8X+Q45fsWx5QojlcTlshwB9pSd0/JlbBsYQQYNIJZCb +YiiNky+TA/7o4k2QXJI11qj4Vk9AAj7g2QxmmQFvL0CwGCpFMLsYVewbZySrLLA8 +AJ09iwDt/vldyPHeLc0rU3H+kAO+VbFgV97uOnR/8zymjoI42oWxfs7GIhEkq/Md +2x+ssvFvBejxXWS3dJ8DH3HW1hc0gTp2aBjoPb9oJP8UCGjw0vVG7PUWsnpKuKOX +6x6xFXiqgJZzDVj/lgJjUopvlRb0iXa0K7aDb2dClFV8pyOM+YBr7cmVZrD/cMya +aTqmgVBN4bIF9B89Bguma9hMpIeODeI8ZmImhNy60QE767UteosIakh+HhdpmVMz +9ZkeALj+MjXZLZnCCRcMcxsCLvX0rkGQFPAEzxbli+POj2Kln6mIntcu5d0IZjMi +NQwnKHm4IurwOuYsfXw00sEWAWIMiWBTtRsBrjQT5WZkTHFLAObXDPJJ9tKC/znX +ooTA1IKzpyoejDMZ1BC5899UaZR/WhoKHAKrLz9SnDC2fy1JhQFRXJsv+XdFSaSl +bzG2nQoCyZ+BFeSIJSdUxeARLDH/w4lgPeMTaqfs4P0V2Han483sjlVeJQ9BkCrx +2VwaxzdTvpvqb7BbZLXm4Fi+AlXZNHGjXZVf5PxfTBnQAqor1DWT0YVs1W3MAGHe +mtEdxex0kdZeSvI2JQJGuWdQR+q/hiiv9P0tymMPlEYhJ3oLsKoBrMZCvFvMXen3 +LpGEzUuYBH9A8PpjLYF7LeUDYCpQlM+2tnw3q68S0L6fEAt2kwI9XvLZmbOeLwDs +rrjyZ8tDb43xeBxMF3f2YRGUmuDEQyXgLWMBkUlN8C+MIJaenVwWUt7okt9Bzrrs +HoUozWdL7/f61utrkLrEcvuyBdm8f756+Tp9Pgwk3x8v69BET8d6ExYzq84sxLq/ +YR4YOzAuYzxwThjZo9kKD+C19vq05nz0BLblDK370y0sx4dJfCQiW/XDcTSEdTuh +1y3tFvtbOhBe7W7WxyBM8NrnbGyT169Ynd8MHZcjfT4rdg3vEMK8iJ5wUHiXYO3q +r6OGxt5iiVg= +=tj4l +-----END PGP MESSAGE----- diff --git a/tests/testdata/compatibility/encrypted_signed_with_unknown_signature_version.msg b/tests/testdata/compatibility/encrypted_signed_with_unknown_signature_version.msg new file mode 100644 index 00000000..4f8ee703 --- /dev/null +++ b/tests/testdata/compatibility/encrypted_signed_with_unknown_signature_version.msg @@ -0,0 +1,25 @@ +-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv9FLXg7nalbJA17Pbd4otaD1beLSJ1tf9zWv6aKGoMclPS +sWJMvfW4ojBH2jJuH7HUenyPWMsnJIzeP8YC8jWF+vnVn6CVB0Hnef8+YNOF0K5T +jj+/lIKOxGPgUUo9hL7h+UI0a/W1noggmwgG2D5SgfQ7enyDk0uw/vpk5wRwqRzF +765O97izaUyBhkx7snls9dwrFPduwuvAYTnt2NbeaSJeivj+0N+YYPGqiXL5pWqn +WdPkCY0EPMIRDgco1rHOP2qUDKXN224OeJiCWE/sHlpmwyPE6P2L9WvazPD/pmBo +AI27TjejVvcmSDx45XNVu5ONRZ7pHerrURMKXb27nscfx4dj6MO4nSFqXiNN4uqy +mt1vF4ELEhu6OtQBXg3AlSj8EYQPkHI0nzEkOmWZrWMJU+at7Ph1BblhEQFiidOy +528BciZIvrLdvcWhgNiJ2eYxH3MXrnl/LKmeRtr1Zj1oJMxlpo47owq2vXc1fBIW +z88dDoN/PJgImGGzeHCY0sGYAUTo/Bd+MIiH/lTJUxUU4UfmYRTsD+R18CLLVhB9 +cIysmxlXbbvQAiSTY2t409rELyfJQU1A7D9Y0JwIou/njkIsrufOUGF936ZFN9T1 +WQUzbnyHqEeOsA6No/qk2SNVNgdGPv9emE5TX/rUQkonC0/zpPIedEY2fiBVNxkj +mvALMs97sIWcle/Vt3qPHZCV9xNtajg0qOZvmPtS+dnipqoe+1T7GEjO9xOwEHjZ +LGrSfbGAmvzNEJnfiX9rl2qAe7NaXiIDHVHQkY31DLihrjlX+6mJtHeqKVOQW1Ru +yvl4yBAuu0mxOQahRGmNRdYlUHnyYXJHLoXljJvNUCsH43r2KGeaDk9i4kDQA4pG +qqEyqOCUk94nKA/FhSf8247nubHRGyf3Sa0OXa7h8QvEtVqS+cBV3vjDhFVkFTLu +/pEDimiy267XDF5nLwwVHrLavT4VeGc9w7SoACBYvDspvt8RgCT3gnG2S3vtxgvw +8OzoDR8TATk/wKwEav5WI35ElfrnM60Erde5XZH5R1YiWFKOMqDe16YU4c4tZa7Y +dWcfaSFfOUm7ekAuCkOQx024L91c2wVdJY18ZGvR6I3XhaAbj0+a7ksT4UU1eNLX +qfGJjafpwNqPyKdSSVc7jxxsY4v8B24rtrWDOgBbRp1j8JSmtmSVB3rz39Gq4aLn +yuy/kZPgsNuaLzDvZ06/iWdGgHPcLAhz7orU++epxLK6xKgs/mjSanp0UYWZjx2d +AO1qJiiABKSJn1krOid7pezsOLxkN4rKNJ2rpuFZKuUeChVzDqZF7S/f +=V8p/ +-----END PGP MESSAGE----- diff --git a/tests/testdata/compatibility/pkesk_unknown_pkalg.msg b/tests/testdata/compatibility/pkesk_unknown_pkalg.msg new file mode 100644 index 00000000..04d70f8b --- /dev/null +++ b/tests/testdata/compatibility/pkesk_unknown_pkalg.msg @@ -0,0 +1,20 @@ +-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv+PkvYwtAPmEyGugGzZ3FvqGzxQ9kVm0N2+VjJ0VBMoWpz +jE6UB6rXXnBbaCCeiXiTKGTNOueyXB09xZVUxa1Kub4yTA90d+NxU7YFhUvcEzt+ +LgP9oWe3MrKB+WOFA7z9RAwCgfvBzwYZ9AFO/v4UHQVrDL7KLLnglPtcXnrvMf7G +ecqcoHAbyj/Jn4ZQqL1/fVF3Dqkfrv+M307IJtfh/SebMxAnYNDi/5U9xnZo/Bvf +JVGa2UtxrvyMkIbZZuy82ZzkDK1HI+NHQhBlksS00AKA/vIwTHKNsdITE+ToY7Pi +jjRU92Sl1KyKdt8e00DTPoDRWxxUFSVMJB4eSP/39HBcAVGW5tHYkMGYXhwRoxPD +qbxWawGfrLxCNga0n5cM3Z1I4m8jel1Xj0RvjWptgHYGB64oWte9hfh3GHT7o3X6 +40zJnsCrp6BFyY+lp7DHTsyOGCh6qxA+jvcu0UBMn+mSEkWmyP318KEN2ohJZzWt +rWzU709sTOgZDI1qqMbLwcBKAwEjRWeJq83vYxoaGhoaGhoaGhoaGhoaGhoaGhoa +GhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoa +GhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoa +GhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoa +GhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoa +GhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhrSTAHK +7+Hdple30q0bt73f41f61wdFRUYmyREN1zmOt8NWLUIdQFkeY1fw25Nil2yH3h+/ +spp41fwEYgEStCN2jv7rb4AvPlNcCb929/c= +=Gt9v +-----END PGP MESSAGE----- diff --git a/tests/testdata/compatibility/pkesk_unknown_version.msg b/tests/testdata/compatibility/pkesk_unknown_version.msg new file mode 100644 index 00000000..75b9f7fe --- /dev/null +++ b/tests/testdata/compatibility/pkesk_unknown_version.msg @@ -0,0 +1,16 @@ +-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv+PkvYwtAPmEyGugGzZ3FvqGzxQ9kVm0N2+VjJ0VBMoWpz +jE6UB6rXXnBbaCCeiXiTKGTNOueyXB09xZVUxa1Kub4yTA90d+NxU7YFhUvcEzt+ +LgP9oWe3MrKB+WOFA7z9RAwCgfvBzwYZ9AFO/v4UHQVrDL7KLLnglPtcXnrvMf7G +ecqcoHAbyj/Jn4ZQqL1/fVF3Dqkfrv+M307IJtfh/SebMxAnYNDi/5U9xnZo/Bvf +JVGa2UtxrvyMkIbZZuy82ZzkDK1HI+NHQhBlksS00AKA/vIwTHKNsdITE+ToY7Pi +jjRU92Sl1KyKdt8e00DTPoDRWxxUFSVMJB4eSP/39HBcAVGW5tHYkMGYXhwRoxPD +qbxWawGfrLxCNga0n5cM3Z1I4m8jel1Xj0RvjWptgHYGB64oWte9hfh3GHT7o3X6 +40zJnsCrp6BFyY+lp7DHTsyOGCh6qxA+jvcu0UBMn+mSEkWmyP318KEN2ohJZzWt +rWzU709sTOgZDI1qqMbLwUoXQUFBQUFBQUEJYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYdJMAcrv +4d2mV7fSrRu3vd/jV/rXB0VFRibJEQ3XOY63w1YtQh1AWR5jV/Dbk2KXbIfeH7+y +mnjV/ARiARK0I3aO/utvgC8+U1wJv3b39w== +=DN9X +-----END PGP MESSAGE----- diff --git a/tests/testdata/compatibility/ricarda.pgp b/tests/testdata/compatibility/ricarda.pgp new file mode 100644 index 00000000..e0a5fb25 --- /dev/null +++ b/tests/testdata/compatibility/ricarda.pgp @@ -0,0 +1,86 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 2ADE 0F8A A059 6BC9 4E50 D2AD 9162 53AB 652E F195 +Comment: Ricarda S. Álvarez + +xsDNBGJvrDABDACrZUY7VU1uZ/uzntlAKEHVF4mb3eYSdW1rE3hVke0HoDvtQbzq +KQ9qgfPaNwdkxRexgrNGSeKkQJcgR7gMWxFxM/FwddQIXfVL43nRlN+iGvFDYR+9 +dn5gSOD9EvZUYLN9p6yR3Umyglt4NpdjYM0J+Rn2DVyfGHCtS+z1fdym1h1zdImo +rArBpWMEdvNN/6dR8BN67WSBs5pVsvnDPdjbeU+GPJVoRH4CWe/LdJnJDICPmlva +gAyeJeK+KitkxD8IIc9d18x5dV1Z/LL2o1B0Psort8+az2Z+NbkP2cUv0DDRyZIM +Ww1A6KMSSNuvewXGCU+gEFlGIA83zfq7XE3WNp9EPy9xCs610+KxSS9cftKdrMmU +cl37Gb1lZyLFIBUCwPdIyTvhs6r6rMgu4GQ46sPl8qeLbtaoXnPWj9V1Fn9RoMgz +Kq8ZItFCJfCbf+gamx/0S/0mE47giuAtymw8Hf53e3jsSANK28GpcsEUNqWT4djb +YgcWl2iXR7n0i9cAEQEAAcLBSQQfAQoAfQWCYm+sMAMLCQcJEJFiU6tlLvGVRxQA +AAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ6O6vv9v4H1aAR9Z +aEksrL/2rQGm4Omv2SCubCC1pk/DAxUKCAKbAQIeARYhBCreD4qgWWvJTlDSrZFi +U6tlLvGVAACp1wv+MEkIs8x2Wk6e+i4rbclnkBjR2tSwv3N66jcz09QeDt5xwX1Q +jq1TP+XGFJfFA+LUugbLWNzWqwh7CypSc6IAB82+Ha+lCMjY3SZLfTe6crUgDWOZ +wDprVND6z2g9UlgJ6rfFf329LEUziknjKFBbreONH12tOzvCxrxipp84J0DZEqO3 +0VB2b2B7uO1X5V1aQMFj4JEafQ6Or6uXtR3Fwcs/JALwhqydy3LI4Y0s/y91qdPP +69DgugVDbG/Kkp5W2CNtGyd51cnEoFEbh9nV0hHR+rERU2ZcyoNfZQCyPuHMIT1L +PndcWLsT6Ga3TvXLUEhOOl6CPpox0aV7bsGpip0u54yGkhgR5YYLchcTVt15q5+/ +M5Z03et1LcSZKk1ijSFXNsOWdD2ojneN3vhlKbJoDosT10WyCsXKL8JNP4F4q4nU +22+/N1sttbgzSatwPxyiC7kKymQmDtg8NCyyCtpDoRkEIzaMv+4DqoMVkvLRcF1K +vX+rNEg1OxneqMSEzS1SaWNhcmRhIFMuIMOBbHZhcmV6IDxyaWNhcmRhQG9wZW5w +Z3AuZXhhbXBsZT7CwUwEEwEKAIAFgmJvrDADCwkHCRCRYlOrZS7xlUcUAAAAAAAe +ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfo22n7d9n+Nv3PxzMP93f7 +rejDV4BFsobH6T5cZGvj8AMVCggCmQECmwECHgEWIQQq3g+KoFlryU5Q0q2RYlOr +ZS7xlQAAu6EL/iJ91h1dwcQMZpbKYnBbH/1U/ncbIlhkQf8YzjrpzVprU0sr4cKu +mx42EylcteODVoDN8ITGROUC8Prw5tu+NS2mjkKWUlj56eLkpS3A3D6NqPIFEzU3 +KYztp+Y0hq2ZmsGsPgL1AbVPc2+ngUSL4pXgQ5hmVoD3B6VGqBReyKXhZNzH42S5 +fMuOsY8+/xgQ0WDwSCVeUW7Otql24iFji9U4eL036AzOO1hXkbOesrw4E7GmU2mR +kkX/aDZ9bGYT3yVZ/CJkU4wrUHJWW1IJl/bl//becL8Vnqr92vQkYQnUh19zIi2n +6ualROjcoITjjRemvkBfM9zXTd6kVFKD+ySDnGtNq62Ukz0/COg81tAnnnXeNhlP +1M8yA6zfrY17tAFYLmALUrPYjVy4ZJuaHScnIcM5lHIKYn1ijetGQSjI4sT/mSi4 +8fwNHFRufR7ta9XWv5b4+m8SAyrJ/FqnRoOwtoph0ZxSItjpv+qLPX2N4M8/JOqh +3QhI5ZDd+ScGJc7AzQRib6wwAQwArMv2IdO1f8a/brBc/+LeCqyqR+qLKzZRLAvF ++c6+qG7D9OzZCJfbPltnQ5BgRzIiSKgzDugDDt0m3fdWBxOQG5Ojo53Xu1ZTgRUD +0KWI4kH5Cs0gp3Kl62TdNAxNUlHdWqJ6G6WYVMlMhLmBVPjo65pT1+OON2v/O/qA +8ZJRlK3RyKym4Kcr8JbtX1roSRtVPRpi++Y1CGuhnK3UM68putqfgZOnTZxln2m2 +BUrL8fWgme2rTJyrrN8fSM61dZZ+5E0SWDMBxEp5bimMrRjJc7V0JMUx2HiCDe/l +panzleAugfEVUeAYsEe9C+7k3p2r00roL0dWnrMgfhGnJy5ccmPyzEQpoA6ARq1a +EBk2Syr/QNO93GfDFcK9pTcDcQdUgq93x5s9LShLQ7fuvVXXxkl4QkdKJn+Vb2yP +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 +=i+jK +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/testdata/compatibility/sig23_sig4.sig b/tests/testdata/compatibility/sig23_sig4.sig new file mode 100644 index 00000000..4710b31b --- /dev/null +++ b/tests/testdata/compatibility/sig23_sig4.sig @@ -0,0 +1,26 @@ +-----BEGIN PGP SIGNATURE----- + +wsE7FwABCgBvBYJkf2S3CRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmeLydr/KwuIg2juO15I6oucMhsgNCcuFLmwzmRjN7WP +XxYhBNGmbhojsYLJmA94jPv8yCoBXnMwAACzwgwAsa09M/8lZf3EfRfcDzzwYKUq +kKkeyOdTGYZEw4Wr9q3bW+Ihl8EECzntzPkTObJjNfRjlB8VW+XGgSY4CoNVbihD +b/LN/9K35qqaJgp0Vw1w9KC0NcmWqykn+b3mmWcbHnxE9uEnG4QhCXIM9/u+O13o +eKoteXc+bSYa7eA6JJpO2cBxq/ZqG1CHp5x8+0QC2kiZllsmkZqgqCleF9M70Al/ +tk3OdnodsST5c2bwPkThnYJazEMBhHv2YPGLhwj6j0N+I+HTqloEZxd7gTcFsIDJ +haiBrRjQ9D5ePdpzNLPtemG79vxUeVpHniQAgkXHZqCct0S788kbMJC0hAOYWD/2 +AvmeI254FJqrd2ORdWoMFK1raAzRLWetj3ts4dcU/6Y36/AjMhN+zqPlxDrd3hXW +WadP9HQqgld8H3UG8cLDM7XdZRBqr5v2zwH9dgJTg/bVysKsg2LWm0m1k6rbd6EQ +m6ow6tOrgzvG7bsTbgvsWeOjlc55TrCONOK1+qpIwsE7BAABCgBvBYJkf2S3CRD7 +/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeL +ydr/KwuIg2juO15I6oucMhsgNCcuFLmwzmRjN7WPXxYhBNGmbhojsYLJmA94jPv8 +yCoBXnMwAACzwgwAsa09M/8lZf3EfRfcDzzwYKUqkKkeyOdTGYZEw4Wr9q3bW+Ih +l8EECzntzPkTObJjNfRjlB8VW+XGgSY4CoNVbihDb/LN/9K35qqaJgp0Vw1w9KC0 +NcmWqykn+b3mmWcbHnxE9uEnG4QhCXIM9/u+O13oeKoteXc+bSYa7eA6JJpO2cBx +q/ZqG1CHp5x8+0QC2kiZllsmkZqgqCleF9M70Al/tk3OdnodsST5c2bwPkThnYJa +zEMBhHv2YPGLhwj6j0N+I+HTqloEZxd7gTcFsIDJhaiBrRjQ9D5ePdpzNLPtemG7 +9vxUeVpHniQAgkXHZqCct0S788kbMJC0hAOYWD/2AvmeI254FJqrd2ORdWoMFK1r +aAzRLWetj3ts4dcU/6Y36/AjMhN+zqPlxDrd3hXWWadP9HQqgld8H3UG8cLDM7Xd +ZRBqr5v2zwH9dgJTg/bVysKsg2LWm0m1k6rbd6EQm6ow6tOrgzvG7bsTbgvsWeOj +lc55TrCONOK1+qpI +=idy8 +-----END PGP SIGNATURE----- diff --git a/tests/testdata/compatibility/sig4_b-sig4_r.sig b/tests/testdata/compatibility/sig4_b-sig4_r.sig new file mode 100644 index 00000000..281dc097 --- /dev/null +++ b/tests/testdata/compatibility/sig4_b-sig4_r.sig @@ -0,0 +1,26 @@ +-----BEGIN PGP SIGNATURE----- + +wsE7BAABCgBvBYJkf2S3CRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmeLydr/KwuIg2juO15I6oucMhsgNCcuFLmwzmRjN7WP +XxYhBNGmbhojsYLJmA94jPv8yCoBXnMwAACzwgwAsa09M/8lZf3EfRfcDzzwYKUq +kKkeyOdTGYZEw4Wr9q3bW+Ihl8EECzntzPkTObJjNfRjlB8VW+XGgSY4CoNVbihD +b/LN/9K35qqaJgp0Vw1w9KC0NcmWqykn+b3mmWcbHnxE9uEnG4QhCXIM9/u+O13o +eKoteXc+bSYa7eA6JJpO2cBxq/ZqG1CHp5x8+0QC2kiZllsmkZqgqCleF9M70Al/ +tk3OdnodsST5c2bwPkThnYJazEMBhHv2YPGLhwj6j0N+I+HTqloEZxd7gTcFsIDJ +haiBrRjQ9D5ePdpzNLPtemG79vxUeVpHniQAgkXHZqCct0S788kbMJC0hAOYWD/2 +AvmeI254FJqrd2ORdWoMFK1raAzRLWetj3ts4dcU/6Y36/AjMhN+zqPlxDrd3hXW +WadP9HQqgld8H3UG8cLDM7XdZRBqr5v2zwH9dgJTg/bVysKsg2LWm0m1k6rbd6EQ +m6ow6tOrgzvG7bsTbgvsWeOjlc55TrCONOK1+qpIwsE7BAABCgBvBYJkf2S3CRCz ++fFdlxFkrkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfZ +kjsbm6GUzqohKl3WhKgzzGl51pJBsREOkFBHTNhpeBYhBN9WS6uCrGY2n74Fz7P5 +8V2XEWSuAABShwwAo41bVBEuZPowTEL5uvvBL8eEPuS/+11ygXF/hfWayEAvIW+y +tUSLdQ2xF/SJLDGPgkwRcStpX2qIHK0qB4of3fG9OX95upcldKgmdrq38OWuti95 +IuZnuWQ75w0Ka8E/er/lvLmI6fW8istAvUkX1fSthQ1IYeEAPH4CEy0m5lwJfzK+ +XEF6Ne1rEBig6+LC+5/VyBI0jPWhW99g7kH5PiusSFdllO2Ewsfe7GXbWpqwJwCs +DqrdyCgh9z9lt7rTKwpbg2aSPKqKk8DGz2FPjmFgz5WyYWiCG28oTmBDHXhN5i5X ++pPXOlwSvUiGmrvZswewbA4qfBX8jLwqmH/5VaNAiegS05MjTsYEtgAjogCAW3kv +9ka2bOnw6r6GWBiKebE2JU8my+B0H07o9NJ561r7qfyrclF/610z5b6Jh/v6jbCj +2ZCkDTCxyXu4z7IbMY2bPW09QfaW4bYlsfehcwJRuDtxekFbS73mmo9nhVXPhG9s +5I3wawEahPoXGOVn +=QY9Z +-----END PGP SIGNATURE----- diff --git a/tests/testdata/compatibility/sig4_r-sig4_b.sig b/tests/testdata/compatibility/sig4_r-sig4_b.sig new file mode 100644 index 00000000..b8240c63 --- /dev/null +++ b/tests/testdata/compatibility/sig4_r-sig4_b.sig @@ -0,0 +1,26 @@ +-----BEGIN PGP SIGNATURE----- + +wsE7BAABCgBvBYJkf2S3CRCz+fFdlxFkrkcUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmfZkjsbm6GUzqohKl3WhKgzzGl51pJBsREOkFBHTNhp +eBYhBN9WS6uCrGY2n74Fz7P58V2XEWSuAABShwwAo41bVBEuZPowTEL5uvvBL8eE +PuS/+11ygXF/hfWayEAvIW+ytUSLdQ2xF/SJLDGPgkwRcStpX2qIHK0qB4of3fG9 +OX95upcldKgmdrq38OWuti95IuZnuWQ75w0Ka8E/er/lvLmI6fW8istAvUkX1fSt +hQ1IYeEAPH4CEy0m5lwJfzK+XEF6Ne1rEBig6+LC+5/VyBI0jPWhW99g7kH5Pius +SFdllO2Ewsfe7GXbWpqwJwCsDqrdyCgh9z9lt7rTKwpbg2aSPKqKk8DGz2FPjmFg +z5WyYWiCG28oTmBDHXhN5i5X+pPXOlwSvUiGmrvZswewbA4qfBX8jLwqmH/5VaNA +iegS05MjTsYEtgAjogCAW3kv9ka2bOnw6r6GWBiKebE2JU8my+B0H07o9NJ561r7 +qfyrclF/610z5b6Jh/v6jbCj2ZCkDTCxyXu4z7IbMY2bPW09QfaW4bYlsfehcwJR +uDtxekFbS73mmo9nhVXPhG9s5I3wawEahPoXGOVnwsE7BAABCgBvBYJkf2S3CRD7 +/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeL +ydr/KwuIg2juO15I6oucMhsgNCcuFLmwzmRjN7WPXxYhBNGmbhojsYLJmA94jPv8 +yCoBXnMwAACzwgwAsa09M/8lZf3EfRfcDzzwYKUqkKkeyOdTGYZEw4Wr9q3bW+Ih +l8EECzntzPkTObJjNfRjlB8VW+XGgSY4CoNVbihDb/LN/9K35qqaJgp0Vw1w9KC0 +NcmWqykn+b3mmWcbHnxE9uEnG4QhCXIM9/u+O13oeKoteXc+bSYa7eA6JJpO2cBx +q/ZqG1CHp5x8+0QC2kiZllsmkZqgqCleF9M70Al/tk3OdnodsST5c2bwPkThnYJa +zEMBhHv2YPGLhwj6j0N+I+HTqloEZxd7gTcFsIDJhaiBrRjQ9D5ePdpzNLPtemG7 +9vxUeVpHniQAgkXHZqCct0S788kbMJC0hAOYWD/2AvmeI254FJqrd2ORdWoMFK1r +aAzRLWetj3ts4dcU/6Y36/AjMhN+zqPlxDrd3hXWWadP9HQqgld8H3UG8cLDM7Xd +ZRBqr5v2zwH9dgJTg/bVysKsg2LWm0m1k6rbd6EQm6ow6tOrgzvG7bsTbgvsWeOj +lc55TrCONOK1+qpI +=peEs +-----END PGP SIGNATURE----- diff --git a/tests/testdata/compatibility/sig4_sig23.sig b/tests/testdata/compatibility/sig4_sig23.sig new file mode 100644 index 00000000..90d7282e --- /dev/null +++ b/tests/testdata/compatibility/sig4_sig23.sig @@ -0,0 +1,26 @@ +-----BEGIN PGP SIGNATURE----- + +wsE7BAABCgBvBYJkf2S3CRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmeLydr/KwuIg2juO15I6oucMhsgNCcuFLmwzmRjN7WP +XxYhBNGmbhojsYLJmA94jPv8yCoBXnMwAACzwgwAsa09M/8lZf3EfRfcDzzwYKUq +kKkeyOdTGYZEw4Wr9q3bW+Ihl8EECzntzPkTObJjNfRjlB8VW+XGgSY4CoNVbihD +b/LN/9K35qqaJgp0Vw1w9KC0NcmWqykn+b3mmWcbHnxE9uEnG4QhCXIM9/u+O13o +eKoteXc+bSYa7eA6JJpO2cBxq/ZqG1CHp5x8+0QC2kiZllsmkZqgqCleF9M70Al/ +tk3OdnodsST5c2bwPkThnYJazEMBhHv2YPGLhwj6j0N+I+HTqloEZxd7gTcFsIDJ +haiBrRjQ9D5ePdpzNLPtemG79vxUeVpHniQAgkXHZqCct0S788kbMJC0hAOYWD/2 +AvmeI254FJqrd2ORdWoMFK1raAzRLWetj3ts4dcU/6Y36/AjMhN+zqPlxDrd3hXW +WadP9HQqgld8H3UG8cLDM7XdZRBqr5v2zwH9dgJTg/bVysKsg2LWm0m1k6rbd6EQ +m6ow6tOrgzvG7bsTbgvsWeOjlc55TrCONOK1+qpIwsE7FwABCgBvBYJkf2S3CRD7 +/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeL +ydr/KwuIg2juO15I6oucMhsgNCcuFLmwzmRjN7WPXxYhBNGmbhojsYLJmA94jPv8 +yCoBXnMwAACzwgwAsa09M/8lZf3EfRfcDzzwYKUqkKkeyOdTGYZEw4Wr9q3bW+Ih +l8EECzntzPkTObJjNfRjlB8VW+XGgSY4CoNVbihDb/LN/9K35qqaJgp0Vw1w9KC0 +NcmWqykn+b3mmWcbHnxE9uEnG4QhCXIM9/u+O13oeKoteXc+bSYa7eA6JJpO2cBx +q/ZqG1CHp5x8+0QC2kiZllsmkZqgqCleF9M70Al/tk3OdnodsST5c2bwPkThnYJa +zEMBhHv2YPGLhwj6j0N+I+HTqloEZxd7gTcFsIDJhaiBrRjQ9D5ePdpzNLPtemG7 +9vxUeVpHniQAgkXHZqCct0S788kbMJC0hAOYWD/2AvmeI254FJqrd2ORdWoMFK1r +aAzRLWetj3ts4dcU/6Y36/AjMhN+zqPlxDrd3hXWWadP9HQqgld8H3UG8cLDM7Xd +ZRBqr5v2zwH9dgJTg/bVysKsg2LWm0m1k6rbd6EQm6ow6tOrgzvG7bsTbgvsWeOj +lc55TrCONOK1+qpI +=FIFU +-----END PGP SIGNATURE----- diff --git a/tests/testdata/compatibility/skesk_unknown_s2k_algo.msg b/tests/testdata/compatibility/skesk_unknown_s2k_algo.msg new file mode 100644 index 00000000..c359159a --- /dev/null +++ b/tests/testdata/compatibility/skesk_unknown_s2k_algo.msg @@ -0,0 +1,16 @@ +-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv+PkvYwtAPmEyGugGzZ3FvqGzxQ9kVm0N2+VjJ0VBMoWpz +jE6UB6rXXnBbaCCeiXiTKGTNOueyXB09xZVUxa1Kub4yTA90d+NxU7YFhUvcEzt+ +LgP9oWe3MrKB+WOFA7z9RAwCgfvBzwYZ9AFO/v4UHQVrDL7KLLnglPtcXnrvMf7G +ecqcoHAbyj/Jn4ZQqL1/fVF3Dqkfrv+M307IJtfh/SebMxAnYNDi/5U9xnZo/Bvf +JVGa2UtxrvyMkIbZZuy82ZzkDK1HI+NHQhBlksS00AKA/vIwTHKNsdITE+ToY7Pi +jjRU92Sl1KyKdt8e00DTPoDRWxxUFSVMJB4eSP/39HBcAVGW5tHYkMGYXhwRoxPD +qbxWawGfrLxCNga0n5cM3Z1I4m8jel1Xj0RvjWptgHYGB64oWte9hfh3GHT7o3X6 +40zJnsCrp6BFyY+lp7DHTsyOGCh6qxA+jvcu0UBMn+mSEkWmyP318KEN2ohJZzWt +rWzU709sTOgZDI1qqMbLw1AECRcIYWFhYWFhYWFBQUFBYWFhYWFhYWFhYWFhYWFh +YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh +YdJMAcrv4d2mV7fSrRu3vd/jV/rXB0VFRibJEQ3XOY63w1YtQh1AWR5jV/Dbk2KX +bIfeH7+ymnjV/ARiARK0I3aO/utvgC8+U1wJv3b39w== +=74qf +-----END PGP MESSAGE----- diff --git a/tests/testdata/compatibility/skesk_unknown_version.msg b/tests/testdata/compatibility/skesk_unknown_version.msg new file mode 100644 index 00000000..490b1a91 --- /dev/null +++ b/tests/testdata/compatibility/skesk_unknown_version.msg @@ -0,0 +1,16 @@ +-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv+PkvYwtAPmEyGugGzZ3FvqGzxQ9kVm0N2+VjJ0VBMoWpz +jE6UB6rXXnBbaCCeiXiTKGTNOueyXB09xZVUxa1Kub4yTA90d+NxU7YFhUvcEzt+ +LgP9oWe3MrKB+WOFA7z9RAwCgfvBzwYZ9AFO/v4UHQVrDL7KLLnglPtcXnrvMf7G +ecqcoHAbyj/Jn4ZQqL1/fVF3Dqkfrv+M307IJtfh/SebMxAnYNDi/5U9xnZo/Bvf +JVGa2UtxrvyMkIbZZuy82ZzkDK1HI+NHQhBlksS00AKA/vIwTHKNsdITE+ToY7Pi +jjRU92Sl1KyKdt8e00DTPoDRWxxUFSVMJB4eSP/39HBcAVGW5tHYkMGYXhwRoxPD +qbxWawGfrLxCNga0n5cM3Z1I4m8jel1Xj0RvjWptgHYGB64oWte9hfh3GHT7o3X6 +40zJnsCrp6BFyY+lp7DHTsyOGCh6qxA+jvcu0UBMn+mSEkWmyP318KEN2ohJZzWt +rWzU709sTOgZDI1qqMbLw00XCQMIMiFs6BH34GH/YWFhYWFhYWFhYWFhYWFhYWFh +YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYdJM +Acrv4d2mV7fSrRu3vd/jV/rXB0VFRibJEQ3XOY63w1YtQh1AWR5jV/Dbk2KXbIfe +H7+ymnjV/ARiARK0I3aO/utvgC8+U1wJv3b39w== +=e3i1 +-----END PGP MESSAGE----- From edd7e4fa3b8c49053c4cb8845a75dd6618d75955 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 10 Jun 2023 23:41:57 -0400 Subject: [PATCH 02/16] handle Armored text with UTF-8 comments --- docs/source/changelog.rst | 13 +++++++++++++ pgpy/pgp.py | 7 ++----- pgpy/types.py | 31 ++++++++++++++++++++++++------- tests/test_06_compatibility.py | 1 - 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 6454614e..a0573cba 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -4,6 +4,19 @@ Changelog ********* +v0.7.0 +====== + +(not yet released) + +API changes +----------- + +Armorable.is_ascii() is deprecated. You probably want +Armorable.is_utf8() instead, since OpenPGP assumes that all text is +UTF-8. + + v0.6.0 ====== diff --git a/pgpy/pgp.py b/pgpy/pgp.py index f34a25fb..b45c4b5f 100644 --- a/pgpy/pgp.py +++ b/pgpy/pgp.py @@ -1153,9 +1153,9 @@ def new(cls, message, **kwargs): # message is definitely UTF-8 already format = 'u' - elif cls.is_ascii(message): + elif cls.is_utf8(message): # message is probably text - format = 't' + format = 'u' else: # message is probably binary @@ -1176,9 +1176,6 @@ def new(cls, message, **kwargs): lit.mtime = mtime lit.format = format - # if cls.is_ascii(message): - # lit.format = 't' - lit.update_hlen() msg |= lit diff --git a/pgpy/types.py b/pgpy/types.py index 187d3984..49c89ed6 100644 --- a/pgpy/types.py +++ b/pgpy/types.py @@ -17,6 +17,8 @@ from enum import EnumMeta from enum import IntEnum +from typing import Union, Optional, Dict + from .decorators import sdproperty from .errors import PGPError @@ -69,15 +71,27 @@ class Armorable(metaclass=abc.ABCMeta): """, flags=re.MULTILINE | re.VERBOSE) @property - def charset(self): + def charset(self) -> str: return self.ascii_headers.get('Charset', 'utf-8') @charset.setter - def charset(self, encoding): + def charset(self, encoding: str) -> None: self.ascii_headers['Charset'] = codecs.lookup(encoding).name @staticmethod - def is_ascii(text): + def is_utf8(text: Union[str, bytes, bytearray]) -> bool: + if isinstance(text, str): + return True + else: + try: + text.decode('utf-8') + return True + except UnicodeDecodeError: + return False + + @staticmethod + def is_ascii(text: Union[str, bytes, bytearray]) -> bool: + '''This is a deprecated, pointless method''' if isinstance(text, str): return bool(re.match(r'^[ -~\r\n\t]*$', text, flags=re.ASCII)) @@ -87,7 +101,7 @@ def is_ascii(text): raise TypeError("Expected: ASCII input of type str, bytes, or bytearray") # pragma: no cover @staticmethod - def is_armor(text): + def is_armor(text: Union[str, bytes, bytearray]) -> bool: """ Whether the ``text`` provided is an ASCII-armored PGP block. :param text: A possible ASCII-armored PGP block. @@ -95,12 +109,15 @@ def is_armor(text): :returns: Whether the text is ASCII-armored. """ if isinstance(text, (bytes, bytearray)): # pragma: no cover - text = text.decode('latin-1') + try: + text = text.decode('utf-8') + except UnicodeDecodeError: + return False return Armorable.__armor_regex.search(text) is not None @staticmethod - def ascii_unarmor(text): + def ascii_unarmor(text: Union[str, bytes, bytearray]) -> Dict[str, Optional[Union[str, bytes, bytearray]]]: """ Takes an ASCII-armored PGP block and returns the decoded byte value. @@ -111,7 +128,7 @@ def ascii_unarmor(text): It can contain the following keys: ``magic``, ``headers``, ``hashes``, ``cleartext``, ``body``, ``crc``. """ m = {'magic': None, 'headers': None, 'body': bytearray(), 'crc': None} - if not Armorable.is_ascii(text): + if not Armorable.is_utf8(text): m['body'] = bytearray(text) return m diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index e6e9aaee..a2f1d429 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -18,7 +18,6 @@ class TestPGP_Compatibility(object): def test_import_unicode_armored_cert(self) -> None: k:PGPKey (k, _) = PGPKey.from_file('tests/testdata/compatibility/ricarda.pgp') - pytest.xfail('Cannot handle UTF-8 (non-ASCII) comments in armored certificate') assert k.check_soundness() == SecurityIssues.OK @pytest.mark.parametrize('sig', glob.glob('*.sig', root_dir='tests/testdata/compatibility')) From a01c66c6f8726bf75294e7398d8c365e0aaa66f5 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 17:35:28 -0400 Subject: [PATCH 03/16] Add PubKeyAlgorithm.Unknown --- pgpy/constants.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pgpy/constants.py b/pgpy/constants.py index 28a4561a..8fcc6933 100644 --- a/pgpy/constants.py +++ b/pgpy/constants.py @@ -247,6 +247,7 @@ def gen_key(self): class PubKeyAlgorithm(IntEnum): """Supported public key algorithms.""" + Unknown = -1 Invalid = 0x00 #: Signifies that a key is an RSA key. RSAEncryptOrSign = 0x01 @@ -264,6 +265,12 @@ class PubKeyAlgorithm(IntEnum): DiffieHellman = 0x15 # X9.42 EdDSA = 0x16 # https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04 + @classmethod + def _missing_(cls, val: object) -> 'PubKeyAlgorithm': + if not isinstance(val, int): + raise TypeError(f"cannot look up PubKeyAlgorithm by non-int {type(val)}") + return cls.Unknown + @property def can_gen(self): return self in {PubKeyAlgorithm.RSAEncryptOrSign, From d38c06938cce7699f644a6c89c8966aa353363ba Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 12 Jun 2023 17:21:24 -0400 Subject: [PATCH 04/16] Handle signatures that use an unknown public key algorithm --- pgpy/packet/packets.py | 26 +++++++++++++++++++------- tests/test_06_compatibility.py | 1 - tests/test_10_exceptions.py | 4 ++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/pgpy/packet/packets.py b/pgpy/packet/packets.py index cd494137..93dbe70a 100644 --- a/pgpy/packet/packets.py +++ b/pgpy/packet/packets.py @@ -350,13 +350,17 @@ def sigtype_int(self, val): self._sigtype = SignatureType(val) @sdproperty - def pubalg(self): + def pubalg(self) -> PubKeyAlgorithm: return self._pubalg - @pubalg.register(int) - @pubalg.register(PubKeyAlgorithm) - def pubalg_int(self, val): - self._pubalg = PubKeyAlgorithm(val) + @pubalg.register + def pubalg_int(self, val: int) -> None: + if isinstance(val, PubKeyAlgorithm): + self._pubalg: PubKeyAlgorithm = val + else: + self._pubalg = PubKeyAlgorithm(val) + if self._pubalg is PubKeyAlgorithm.Unknown: + self._opaque_pubalg: int = val sigs = { PubKeyAlgorithm.RSAEncryptOrSign: RSASignature, @@ -407,7 +411,10 @@ def __bytearray__(self): _bytes = bytearray() _bytes += super(Signature, self).__bytearray__() _bytes += self.int_to_bytes(self.sigtype) - _bytes += self.int_to_bytes(self.pubalg) + if self.pubalg is PubKeyAlgorithm.Unknown: + _bytes.append(self._opaque_pubalg) + else: + _bytes.append(self.pubalg) _bytes += self.int_to_bytes(self.halg) _bytes += self.subpackets.__bytearray__() _bytes += self.hash2 @@ -432,7 +439,10 @@ def canonical_bytes(self): _body = bytearray() _body += self.int_to_bytes(self.header.version) _body += self.int_to_bytes(self.sigtype) - _body += self.int_to_bytes(self.pubalg) + if self.pubalg is PubKeyAlgorithm.Unknown: + _body.append(self._opaque_pubalg) + else: + _body.append(self.pubalg) _body += self.int_to_bytes(self.halg) _body += self.subpackets.__hashbytearray__() _body += self.int_to_bytes(0, minlen=2) # empty unhashed subpackets @@ -449,6 +459,8 @@ def __copy__(self): spkt.header = copy.copy(self.header) spkt._sigtype = self._sigtype spkt._pubalg = self._pubalg + if self._pubalg is PubKeyAlgorithm.Unknown: + spkt._opaque_pubalg = self._opaque_pubalg spkt._halg = self._halg spkt.subpackets = copy.copy(self.subpackets) diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index a2f1d429..b903c881 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -29,7 +29,6 @@ def test_bob_sig_from_multisig(self, sig:str)-> None: def test_cert_unknown_algo(self) -> None: k:PGPKey - pytest.xfail('cannot handle certificates containing certifications made using unknown pubkey algorithms') (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob_with_unknown_alg_certification.pgp') assert k.check_soundness() == SecurityIssues.OK diff --git a/tests/test_10_exceptions.py b/tests/test_10_exceptions.py index 299f3456..0e6cf182 100644 --- a/tests/test_10_exceptions.py +++ b/tests/test_10_exceptions.py @@ -139,8 +139,8 @@ class WhatException(Exception): pass # unexpected signature type fuzz_pkt(3, 0x7f, PGPError) - # unexpected pubkey algorithm - fuzz_pkt(4, 0x64, PGPError) + # unexpected pubkey algorithm -- does not raise an exception during parsing + fuzz_pkt(4, 0x64, None) # unexpected hash algorithm - does not raise an exception during parsing fuzz_pkt(5, 0x64, None) From 0e298a950e91faf636957ff1bbe530292afbe4f9 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 12 Jun 2023 20:27:00 -0400 Subject: [PATCH 05/16] Handle subkeys with unknown public key algorithms cleanly --- pgpy/packet/packets.py | 29 +++++++++++++++++++++-------- tests/test_06_compatibility.py | 1 - 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pgpy/packet/packets.py b/pgpy/packet/packets.py index 93dbe70a..258898eb 100644 --- a/pgpy/packet/packets.py +++ b/pgpy/packet/packets.py @@ -788,13 +788,17 @@ def created_bin(self, val): self.created = self.bytes_to_int(val) @sdproperty - def pkalg(self): + def pkalg(self) -> PubKeyAlgorithm: return self._pkalg - @pkalg.register(int) - @pkalg.register(PubKeyAlgorithm) - def pkalg_int(self, val): - self._pkalg = PubKeyAlgorithm(val) + @pkalg.register + def pkalg_int(self, val: int) -> None: + if isinstance(val, PubKeyAlgorithm): + self._pkalg: PubKeyAlgorithm = val + else: + self._pkalg = PubKeyAlgorithm(val) + if self._pkalg is PubKeyAlgorithm.Unknown: + self._opaque_pkalg: int = val _c = { # True means public @@ -850,7 +854,10 @@ def fingerprint(self): # c) timestamp of key creation (4 octets); fp.update(self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4)) # d) algorithm (1 octet): 17 = DSA (example); - fp.update(self.int_to_bytes(self.pkalg)) + if self.pkalg is PubKeyAlgorithm.Unknown: + fp.update(bytes([self._opaque_pkalg])) + else: + fp.update(self.int_to_bytes(self.pkalg)) # e) Algorithm-specific fields. fp.update(self.keymaterial.__bytearray__()[:plen]) @@ -867,7 +874,10 @@ def __bytearray__(self): _bytes = bytearray() _bytes += super(PubKeyV4, self).__bytearray__() _bytes += self.int_to_bytes(calendar.timegm(self.created.timetuple()), 4) - _bytes += self.int_to_bytes(self.pkalg) + if self.pkalg is PubKeyAlgorithm.Unknown: + _bytes.append(self._opaque_pkalg) + else: + _bytes.append(self.pkalg) _bytes += self.keymaterial.__bytearray__() return _bytes @@ -875,7 +885,10 @@ def __copy__(self): pk = self.__class__() pk.header = copy.copy(self.header) pk.created = self.created - pk.pkalg = self.pkalg + if self.pkalg is PubKeyAlgorithm.Unknown: + pk.pkalg = self._opaque_pkalg + else: + pk.pkalg = self.pkalg pk.keymaterial = copy.copy(self.keymaterial) return pk diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index b903c881..b9d64c1f 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -34,7 +34,6 @@ def test_cert_unknown_algo(self) -> None: def test_cert_unknown_subkey_algo(self) -> None: k:PGPKey - pytest.xfail('cannot handle certificates containing subkeys with unknown pubkey algorithms') (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob_with_unknown_subkey_algorithm.pgp') assert k.check_soundness() == SecurityIssues.OK From 692eafd6f1dc6d607d3703f5d8890931441c7c54 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 11 Jun 2023 09:37:02 -0400 Subject: [PATCH 06/16] Add PGPSignatures object, representing bundled detached signatures a PGPMessage object can contain more than one signature. Detached signatures should also be able to handle having more than one signature. https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-09.html#name-detached-signatures says: > These detached signatures are simply one or more Signature packets > stored separately from the data for which they are a signature. A PGPSignatures object makes the most sense to represent such a thing. Closes: #197 --- docs/source/changelog.rst | 7 +++++ pgpy/__init__.py | 2 ++ pgpy/pgp.py | 55 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index a0573cba..b39c2a82 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,13 @@ v0.7.0 (not yet released) +API additions +------------- + +PGPSignatures represents a detached signature, which can contain more +than a single signature. It is a simple sequence of individual +PGPSignature objects. + API changes ----------- diff --git a/pgpy/__init__.py b/pgpy/__init__.py index b4c30d13..472cf75f 100644 --- a/pgpy/__init__.py +++ b/pgpy/__init__.py @@ -5,6 +5,7 @@ from .pgp import PGPKeyring from .pgp import PGPMessage from .pgp import PGPSignature +from .pgp import PGPSignatures from .pgp import PGPUID __all__ = ['constants', @@ -13,4 +14,5 @@ 'PGPKeyring', 'PGPMessage', 'PGPSignature', + 'PGPSignatures', 'PGPUID', ] diff --git a/pgpy/pgp.py b/pgpy/pgp.py index b45c4b5f..b77480a1 100644 --- a/pgpy/pgp.py +++ b/pgpy/pgp.py @@ -20,6 +20,8 @@ from datetime import datetime, timezone +from typing import Any, List + from cryptography.hazmat.primitives import hashes from .constants import CompressionAlgorithm @@ -79,6 +81,7 @@ from .types import SorteDeque __all__ = ['PGPSignature', + 'PGPSignatures', 'PGPUID', 'PGPMessage', 'PGPKey', @@ -592,6 +595,58 @@ def parse(self, packet): raise ValueError('Expected: Signature. Got: {:s}'.format(pkt.__class__.__name__)) +class PGPSignatures(collections_abc.Container, collections_abc.Iterable, collections_abc.Sized, Armorable, PGPObject): + '''OpenPGP detached signatures can often contain more than one signature in them.''' + + def __init__(self, signatures: List[PGPSignature] = []) -> None: + super().__init__() + self._sigs: List[PGPSignature] = signatures + + def __contains__(self, thing: Any) -> bool: + if not isinstance(thing, PGPSignature): + raise TypeError(f'PGPSignatures only contains a PGPSignature, not {type(thing)}') + return thing in self._sigs + + def __len__(self) -> int: + return len(self._sigs) + + def __iter__(self) -> collections_abc.Iterator[PGPSignature]: + for sig in self._sigs: + yield sig + + @property + def magic(self) -> str: + return "SIGNATURE" + + def __bytearray__(self) -> bytearray: + b = bytearray() + for sig in self._sigs: + b += sig.__bytearray__() + return b + + def parse(self, packet: bytes) -> None: + unarmored = self.ascii_unarmor(packet) + data = unarmored['body'] + + if unarmored['magic'] is not None and unarmored['magic'] != 'SIGNATURE': + raise ValueError(f"Expected: SIGNATURE. Got: {format(str(unarmored['magic']))}") + + if unarmored['headers'] is not None: + self.ascii_headers = unarmored['headers'] + + while data: + pkt = Packet(data) + if pkt.header.tag == PacketTag.Signature: + if isinstance(pkt, Opaque): + # skip unrecognized version. + pass + else: + sig = PGPSignature() + sig._signature = pkt + self._sigs.append(sig) + else: + raise ValueError(f"Expected: Signature. Got: {format(pkt.__class__.__name__)}") + class PGPUID(ParentRef): @property def __sig__(self): From d9e19412b4236925e5dc9ac199c0eec6ca0ea8e7 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 10:48:50 -0400 Subject: [PATCH 07/16] use PGPSignatures to handle multi-sig detached signatures --- tests/test_06_compatibility.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index b9d64c1f..4a3b99ba 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -3,7 +3,7 @@ """ import pytest -from pgpy import PGPKey, PGPMessage +from pgpy import PGPKey, PGPMessage, PGPSignatures from pgpy.constants import SecurityIssues import glob @@ -25,7 +25,12 @@ def test_bob_sig_from_multisig(self, sig:str)-> None: k:PGPKey (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob.pgp') msg = 'Hello World :)' - pytest.xfail(f'Cannot handle detached signature objects with more than one signature present (see https://github.com/SecurityInnovation/PGPy/issues/197)') + sigs = PGPSignatures.from_file(f'tests/testdata/compatibility/{sig}') + verif:Optional[pgpy.SignatureVerification] = None + for sig in sigs: + if sig.signer == k.fingerprint.keyid: + verif = k.verify(msg, sig) + assert verif is not None def test_cert_unknown_algo(self) -> None: k:PGPKey From eb9310a3f5785915905d0ef9618dad3592a5ac36 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 13:18:48 -0400 Subject: [PATCH 08/16] PGPMessage: ignore opaque packets when assembling --- pgpy/pgp.py | 4 ++++ tests/test_06_compatibility.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pgpy/pgp.py b/pgpy/pgp.py index b77480a1..04929a7d 100644 --- a/pgpy/pgp.py +++ b/pgpy/pgp.py @@ -1132,6 +1132,10 @@ def __or__(self, other): self._signatures += other._signatures return self + if isinstance(other, Opaque): + # ignore opaque packets + return self + raise NotImplementedError(str(type(other))) def __copy__(self): diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index 4a3b99ba..03911100 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -53,7 +53,8 @@ def test_cert_unknown_curve(self, flavor:str) -> None: def test_unknown_message(self, msg:str)-> None: k:PGPKey (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob-key.pgp') - pytest.xfail('cannot handle unknowns in message formats') + if msg in {'pkesk_unknown_pkalg.msg', 'skesk_unknown_s2k_algo.msg'}: + pytest.xfail('cannot handle unknown algorithms in encrypted messages') msg:PGPMessage = PGPMessage.from_file(f'tests/testdata/compatibility/{msg}') cleartext:PGPMessage = k.decrypt(msg) assert not cleartext.is_encrypted From 9d26b2e830338f78391681f10e52094dabfa6d43 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 14:23:18 -0400 Subject: [PATCH 09/16] PKESKv3: correct packet length consumption when pubkey algorithm is unknown --- pgpy/packet/packets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpy/packet/packets.py b/pgpy/packet/packets.py index 258898eb..2a9a073d 100644 --- a/pgpy/packet/packets.py +++ b/pgpy/packet/packets.py @@ -281,7 +281,7 @@ def parse(self, packet): self.ct.parse(packet) else: # pragma: no cover - del packet[:(self.header.length - 18)] + del packet[:(self.header.length - 10)] class Signature(VersionedPacket): From cdb634727e2a2f686beac6cfdc4dc662459eaff6 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 14:22:37 -0400 Subject: [PATCH 10/16] PKESKv3: handle unknown pubkey algorithms cleanly --- pgpy/packet/packets.py | 19 ++++++++++++++----- tests/test_06_compatibility.py | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pgpy/packet/packets.py b/pgpy/packet/packets.py index 2a9a073d..ef9fba63 100644 --- a/pgpy/packet/packets.py +++ b/pgpy/packet/packets.py @@ -168,10 +168,14 @@ def encrypter_bin(self, val): def pkalg(self): return self._pkalg - @pkalg.register(int) - @pkalg.register(PubKeyAlgorithm) - def pkalg_int(self, val): - self._pkalg = PubKeyAlgorithm(val) + @pkalg.register + def pkalg_int(self, val: int) -> None: + if isinstance(val, PubKeyAlgorithm): + self._pkalg = val + else: + self._pkalg = PubKeyAlgorithm(val) + if self._pkalg is PubKeyAlgorithm.Invalid: + self._opaque_pkalg: int = val _c = {PubKeyAlgorithm.RSAEncryptOrSign: RSACipherText, PubKeyAlgorithm.RSAEncrypt: RSACipherText, @@ -192,7 +196,10 @@ def __bytearray__(self): _bytes = bytearray() _bytes += super(PKESessionKeyV3, self).__bytearray__() _bytes += binascii.unhexlify(self.encrypter.encode()) - _bytes += bytearray([self.pkalg]) + if self.pkalg == PubKeyAlgorithm.Invalid: + _bytes.append(self._opaque_pkalg) + else: + _bytes.append(self.pkalg) _bytes += self.ct.__bytearray__() if self.ct is not None else b'\x00' * (self.header.length - 10) return _bytes @@ -201,6 +208,8 @@ def __copy__(self): sk.header = copy.copy(self.header) sk._encrypter = self._encrypter sk.pkalg = self.pkalg + if self.pkalg == PubKeyAlgorithm.Invalid: + sk._opaque_pkalg = self._opaque_pkalg if self.ct is not None: sk.ct = copy.copy(self.ct) diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index 03911100..6e3eb983 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -53,8 +53,8 @@ def test_cert_unknown_curve(self, flavor:str) -> None: def test_unknown_message(self, msg:str)-> None: k:PGPKey (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob-key.pgp') - if msg in {'pkesk_unknown_pkalg.msg', 'skesk_unknown_s2k_algo.msg'}: - pytest.xfail('cannot handle unknown algorithms in encrypted messages') + if msg in {'skesk_unknown_s2k_algo.msg'}: + pytest.xfail('cannot handle unknown S2K algorithms in encrypted messages') msg:PGPMessage = PGPMessage.from_file(f'tests/testdata/compatibility/{msg}') cleartext:PGPMessage = k.decrypt(msg) assert not cleartext.is_encrypted From 6d78d1baddb0ab1f604afacb42251efacd12ede8 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 15:07:51 -0400 Subject: [PATCH 11/16] String2KeyType: introduce "Unknown" option, and note opaque values when discovered --- pgpy/constants.py | 6 ++++++ pgpy/packet/fields.py | 24 +++++++++++++++++------- tests/test_06_compatibility.py | 2 -- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pgpy/constants.py b/pgpy/constants.py index 8fcc6933..92cea473 100644 --- a/pgpy/constants.py +++ b/pgpy/constants.py @@ -528,12 +528,18 @@ class KeyServerPreferences(FlagEnum): class String2KeyType(IntEnum): + Unknown = -1 Simple = 0 Salted = 1 Reserved = 2 Iterated = 3 GNUExtension = 101 + @classmethod + def _missing_(cls, val: object) -> 'String2KeyType': + if not isinstance(val, int): + raise TypeError(f"cannot look up String2KeyType by non-int {type(val)}") + return cls.Unknown class S2KGNUExtension(IntEnum): NoSecret = 1 diff --git a/pgpy/packet/fields.py b/pgpy/packet/fields.py index c94021c6..a3002291 100644 --- a/pgpy/packet/fields.py +++ b/pgpy/packet/fields.py @@ -839,13 +839,17 @@ def encalg_int(self, val): self._encalg = SymmetricKeyAlgorithm(val) @sdproperty - def specifier(self): + def specifier(self) -> String2KeyType: return self._specifier - @specifier.register(int) - @specifier.register(String2KeyType) - def specifier_int(self, val): - self._specifier = String2KeyType(val) + @specifier.register + def specifier_int(self, val: int) -> None: + if isinstance(val, String2KeyType): + self._specifier = val + else: + self._specifier = String2KeyType(val) + if self._specifier is String2KeyType.Unknown: + self._opaque_specifier: int = val @sdproperty def gnuext(self): @@ -903,7 +907,10 @@ def __bytearray__(self): _bytes.append(self.usage) if bool(self): _bytes.append(self.encalg) - _bytes.append(self.specifier) + if self.specifier is String2KeyType.Unknown: + _bytes.append(self._opaque_specifier) + else: + _bytes.append(self.specifier) if self.specifier == String2KeyType.GNUExtension: return self._experimental_bytearray(_bytes) if self.specifier >= String2KeyType.Simple: @@ -938,7 +945,10 @@ def __copy__(self): s2k = String2Key() s2k.usage = self.usage s2k.encalg = self.encalg - s2k.specifier = self.specifier + if bool(self) and self.specifier is String2KeyType.Unknown: + s2k.specifier = self._opaque_specifier + else: + s2k.specifier = self.specifier s2k.gnuext = self.gnuext s2k.iv = self.iv s2k.halg = self.halg diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index 6e3eb983..8a51fa3b 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -53,8 +53,6 @@ def test_cert_unknown_curve(self, flavor:str) -> None: def test_unknown_message(self, msg:str)-> None: k:PGPKey (k, _) = PGPKey.from_file('tests/testdata/compatibility/bob-key.pgp') - if msg in {'skesk_unknown_s2k_algo.msg'}: - pytest.xfail('cannot handle unknown S2K algorithms in encrypted messages') msg:PGPMessage = PGPMessage.from_file(f'tests/testdata/compatibility/{msg}') cleartext:PGPMessage = k.decrypt(msg) assert not cleartext.is_encrypted From feb450615abb1982ac8854b7eec935ccbf754e2e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 12 May 2023 13:36:50 -0400 Subject: [PATCH 12/16] Drop the use of pyasn1 for DSA/ECDSA signature translation The cryptography module supports encoding and decoding these signatures directly, so no need for pyasn1 or the custom ASN1 decoder to translate between the OpenPGP format and the RFC 3279 format. --- pgpy/packet/fields.py | 66 ++++++++----------------------------------- 1 file changed, 11 insertions(+), 55 deletions(-) diff --git a/pgpy/packet/fields.py b/pgpy/packet/fields.py index a3002291..dfffb93c 100644 --- a/pgpy/packet/fields.py +++ b/pgpy/packet/fields.py @@ -18,9 +18,6 @@ from pyasn1.codec.der import decoder from pyasn1.codec.der import encoder -from pyasn1.type.univ import Integer -from pyasn1.type.univ import Sequence -from pyasn1.type.namedtype import NamedTypes, NamedType from cryptography.exceptions import InvalidSignature @@ -35,6 +32,7 @@ from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.hazmat.primitives.asymmetric import utils from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash @@ -299,65 +297,23 @@ def from_signer(self, sig): class DSASignature(Signature): __mpis__ = ('r', 's') - def __sig__(self): - # return the signature data into an ASN.1 sequence of integers in DER format - seq = Sequence(componentType=NamedTypes(*[NamedType(n, Integer()) for n in self.__mpis__])) - for n in self.__mpis__: - seq.setComponentByName(n, getattr(self, n)) - - return encoder.encode(seq) - - def from_signer(self, sig): - ##TODO: just use pyasn1 for this - def _der_intf(_asn): - if _asn[0] != 0x02: # pragma: no cover - raise ValueError("Expected: Integer (0x02). Got: 0x{:02X}".format(_asn[0])) - del _asn[0] - - if _asn[0] & 0x80: # pragma: no cover - llen = _asn[0] & 0x7F - del _asn[0] - - flen = self.bytes_to_int(_asn[:llen]) - del _asn[:llen] - - else: - flen = _asn[0] & 0x7F - del _asn[0] + def __sig__(self) -> bytes: + # return the RFC 3279 encoding: + return utils.encode_dss_signature(self.r, self.s) - i = self.bytes_to_int(_asn[:flen]) - del _asn[:flen] - return i + def from_signer(self, sig: bytes) -> None: + # set up from the RFC 3279 encoding: + (r, s) = utils.decode_dss_signature(sig) + self.r = MPI(r) + self.s = MPI(s) - if isinstance(sig, bytes): - sig = bytearray(sig) - - # this is a very limited asn1 decoder - it is only intended to decode a DER encoded sequence of integers - if not sig[0] == 0x30: - raise NotImplementedError("Expected: Sequence (0x30). Got: 0x{:02X}".format(sig[0])) - del sig[0] - - # skip the sequence length field - if sig[0] & 0x80: # pragma: no cover - llen = sig[0] & 0x7F - del sig[:llen + 1] - - else: - del sig[0] - - self.r = MPI(_der_intf(sig)) - self.s = MPI(_der_intf(sig)) - - def parse(self, packet): + def parse(self, packet: bytearray) -> None: self.r = MPI(packet) self.s = MPI(packet) class ECDSASignature(DSASignature): - def from_signer(self, sig): - seq, _ = decoder.decode(sig) - self.r = MPI(seq[0]) - self.s = MPI(seq[1]) + pass class EdDSASignature(DSASignature): From 23ea1606d3f481fe387fb9bfa86d3cc6d149e054 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 13 May 2023 02:21:50 -0400 Subject: [PATCH 13/16] Overhaul EllipticCurveOID (reduce dependencies) OpenSSL 1.0.2 is ancient at this point -- Brainpool is part of the standard distribution. At any rate, we need 1.1.0 for X25519 and 1.1.1 for Ed25519. And python's cryptography module has supported Brainpool since version 2.2 (also ancient). Registering subclasses with the cryptography module is complicated across versions (see https://github.com/pyca/cryptography/pull/7234 which removed register_interface), but we don't need any of that functionality as long as we depend on non-ancient modules. At the same time, we don't need pyasn1 any longer if we just treat the OID as a bytestring label. As this also drops all the shenanigans around cryptography.utils.register_interface, we can also say it Closes: #402 --- README.rst | 2 - docs/source/changelog.rst | 11 ++ pgpy/_curves.py | 103 ------------------ pgpy/constants.py | 212 ++++++++++++++++++++++++-------------- pgpy/packet/fields.py | 87 +++++++--------- pgpy/pgp.py | 7 +- requirements.txt | 1 - setup.cfg | 1 - tests/test_05_actions.py | 5 +- tox.ini | 1 - 10 files changed, 190 insertions(+), 240 deletions(-) delete mode 100644 pgpy/_curves.py diff --git a/README.rst b/README.rst index d800c14f..7b3bc9b1 100644 --- a/README.rst +++ b/README.rst @@ -56,8 +56,6 @@ Requirements - `Cryptography `_ -- `pyasn1 `_ - - `six `_ License diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index b39c2a82..2d3afce9 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,14 @@ v0.7.0 (not yet released) +Dependency changes +------------------ + +pyasn1 is no longer needed + +Now depends transitively (via the cryptography module) on OpenSSL +1.1.1 or later for Brainpool, X25519, Ed25519. + API additions ------------- @@ -24,6 +32,9 @@ Armorable.is_utf8() instead, since OpenPGP assumes that all text is UTF-8. +EllipticCurveOID.Invalid was removed -- EllipticCurveOID only +enumerates supported curves now. + v0.6.0 ====== diff --git a/pgpy/_curves.py b/pgpy/_curves.py deleted file mode 100644 index 14f25284..00000000 --- a/pgpy/_curves.py +++ /dev/null @@ -1,103 +0,0 @@ -""" _curves.py -specify some additional curves that OpenSSL provides but cryptography doesn't explicitly expose -""" - -from cryptography import utils - -from cryptography.hazmat.primitives.asymmetric import ec - -from cryptography.hazmat.bindings.openssl.binding import Binding - -__all__ = tuple() - -# TODO: investigate defining additional curves using EC_GROUP_new_curve -# https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography#Defining_Curves - - -def _openssl_get_supported_curves(): - if hasattr(_openssl_get_supported_curves, '_curves'): - return _openssl_get_supported_curves._curves - - # use cryptography's cffi bindings to get an array of curve names - b = Binding() - cn = b.lib.EC_get_builtin_curves(b.ffi.NULL, 0) - cs = b.ffi.new('EC_builtin_curve[]', cn) - b.lib.EC_get_builtin_curves(cs, cn) - - # store the result so we don't have to do all of this every time - curves = { b.ffi.string(b.lib.OBJ_nid2sn(c.nid)).decode('utf-8') for c in cs } - # Ed25519 and X25519 are always present in cryptography>=2.6 - # The python cryptography lib provides a different interface for these curves, - # so they are handled differently in the ECDHPriv/Pub and EdDSAPriv/Pub classes - curves |= {'X25519', 'ed25519'} - _openssl_get_supported_curves._curves = curves - return curves - - -def use_legacy_cryptography_decorator(): - """ - The decorator utils.register_interface was removed in version 38.0.0. Keep using it - if the decorator exists, inherit from `ec.EllipticCurve` otherwise. - """ - return hasattr(utils, "register_interface") and callable(utils.register_interface) - - -if use_legacy_cryptography_decorator(): - @utils.register_interface(ec.EllipticCurve) - class BrainpoolP256R1(object): - name = 'brainpoolP256r1' - key_size = 256 - - - @utils.register_interface(ec.EllipticCurve) # noqa: E303 - class BrainpoolP384R1(object): - name = 'brainpoolP384r1' - key_size = 384 - - - @utils.register_interface(ec.EllipticCurve) # noqa: E303 - class BrainpoolP512R1(object): - name = 'brainpoolP512r1' - key_size = 512 - - - @utils.register_interface(ec.EllipticCurve) # noqa: E303 - class X25519(object): - name = 'X25519' - key_size = 256 - - - @utils.register_interface(ec.EllipticCurve) # noqa: E303 - class Ed25519(object): - name = 'ed25519' - key_size = 256 -else: - class BrainpoolP256R1(ec.EllipticCurve): - name = 'brainpoolP256r1' - key_size = 256 - - - class BrainpoolP384R1(ec.EllipticCurve): # noqa: E303 - name = 'brainpoolP384r1' - key_size = 384 - - - class BrainpoolP512R1(ec.EllipticCurve): # noqa: E303 - name = 'brainpoolP512r1' - key_size = 512 - - - class X25519(ec.EllipticCurve): # noqa: E303 - name = 'X25519' - key_size = 256 - - - class Ed25519(ec.EllipticCurve): # noqa: E303 - name = 'ed25519' - key_size = 256 - - -# add these curves to the _CURVE_TYPES list -for curve in [BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1, X25519, Ed25519]: - if curve.name not in ec._CURVE_TYPES and curve.name in _openssl_get_supported_curves(): - ec._CURVE_TYPES[curve.name] = curve diff --git a/pgpy/constants.py b/pgpy/constants.py index 92cea473..2d76e607 100644 --- a/pgpy/constants.py +++ b/pgpy/constants.py @@ -12,18 +12,18 @@ from enum import IntEnum from enum import IntFlag -from pyasn1.type.univ import ObjectIdentifier +from typing import NamedTuple, Optional, Type, Union from cryptography.hazmat.backends import openssl -from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import ec, x25519, ed25519 from cryptography.hazmat.primitives.ciphers import algorithms from .types import FlagEnum from .decorators import classproperty -from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1, X25519, Ed25519 __all__ = [ 'Backend', + 'ECFields', 'EllipticCurveOID', 'ECPointFormat', 'PacketTag', @@ -55,83 +55,6 @@ class Backend(Enum): OpenSSL = openssl.backend -class EllipticCurveOID(Enum): - """OIDs for supported elliptic curves.""" - # these are specified as: - # id = (oid, curve) - Invalid = ('', ) - #: DJB's fast elliptic curve - Curve25519 = ('1.3.6.1.4.1.3029.1.5.1', X25519) - #: Twisted Edwards variant of Curve25519 - Ed25519 = ('1.3.6.1.4.1.11591.15.1', Ed25519) - #: NIST P-256, also known as SECG curve secp256r1 - NIST_P256 = ('1.2.840.10045.3.1.7', ec.SECP256R1) - #: NIST P-384, also known as SECG curve secp384r1 - NIST_P384 = ('1.3.132.0.34', ec.SECP384R1) - #: NIST P-521, also known as SECG curve secp521r1 - NIST_P521 = ('1.3.132.0.35', ec.SECP521R1) - #: Brainpool Standard Curve, 256-bit - #: - #: .. note:: - #: Requires OpenSSL >= 1.0.2 - Brainpool_P256 = ('1.3.36.3.3.2.8.1.1.7', BrainpoolP256R1) - #: Brainpool Standard Curve, 384-bit - #: - #: .. note:: - #: Requires OpenSSL >= 1.0.2 - Brainpool_P384 = ('1.3.36.3.3.2.8.1.1.11', BrainpoolP384R1) - #: Brainpool Standard Curve, 512-bit - #: - #: .. note:: - #: Requires OpenSSL >= 1.0.2 - Brainpool_P512 = ('1.3.36.3.3.2.8.1.1.13', BrainpoolP512R1) - #: SECG curve secp256k1 - SECP256K1 = ('1.3.132.0.10', ec.SECP256K1) - - def __new__(cls, oid, curve=None): - # preprocessing stage for enum members: - # - set enum_member.value to ObjectIdentifier(oid) - # - if curve is not None and curve.name is in ec._CURVE_TYPES, set enum_member.curve to curve - # - otherwise, set enum_member.curve to None - obj = object.__new__(cls) - obj._value_ = ObjectIdentifier(oid) - obj.curve = None - - if curve is not None and curve.name in ec._CURVE_TYPES: - obj.curve = curve - - return obj - - @property - def can_gen(self): - return self.curve is not None - - @property - def key_size(self): - if self.curve is not None: - return self.curve.key_size - - @property - def kdf_halg(self): - # return the hash algorithm to specify in the KDF fields when generating a key - algs = {256: HashAlgorithm.SHA256, - 384: HashAlgorithm.SHA384, - 512: HashAlgorithm.SHA512, - 521: HashAlgorithm.SHA512} - - return algs.get(self.key_size, None) - - @property - def kek_alg(self): - # return the AES algorithm to specify in the KDF fields when generating a key - algs = {256: SymmetricKeyAlgorithm.AES128, - 384: SymmetricKeyAlgorithm.AES192, - 512: SymmetricKeyAlgorithm.AES256, - 521: SymmetricKeyAlgorithm.AES256} - - return algs.get(self.key_size, None) - - class ECPointFormat(IntEnum): # https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#appendix-B Standard = 0x04 @@ -416,6 +339,135 @@ def is_considered_secure(self): return issues +class ECFields(NamedTuple): + name: str + OID: str + OID_der: bytes + key_size: int # in bits + kdf_halg: HashAlgorithm + kek_alg: SymmetricKeyAlgorithm + curve: Type + + def __repr__(self) -> str: + return f'' + + +class EllipticCurveOID(Enum): + """Supported elliptic curves.""" + + #: DJB's fast elliptic curve + Curve25519 = (x25519, '1.3.6.1.4.1.3029.1.5.1', + b'\x2b\x06\x01\x04\x01\x97\x55\x01\x05\x01', + 'X25519', 256) + #: Twisted Edwards variant of Curve25519 + Ed25519 = (ed25519, '1.3.6.1.4.1.11591.15.1', + b'\x2b\x06\x01\x04\x01\xda\x47\x0f\x01', + 'Ed25519', 256) + #: NIST P-256, also known as SECG curve secp256r1 + NIST_P256 = (ec.SECP256R1, '1.2.840.10045.3.1.7', + b'\x2a\x86\x48\xce\x3d\x03\x01\x07') + #: NIST P-384, also known as SECG curve secp384r1 + NIST_P384 = (ec.SECP384R1, '1.3.132.0.34', + b'\x2b\x81\x04\x00\x22') + #: NIST P-521, also known as SECG curve secp521r1 + NIST_P521 = (ec.SECP521R1, '1.3.132.0.35', + b'\x2b\x81\x04\x00\x23') + #: Brainpool Standard Curve, 256-bit + Brainpool_P256 = (ec.BrainpoolP256R1, '1.3.36.3.3.2.8.1.1.7', + b'\x2b\x24\x03\x03\x02\x08\x01\x01\x07') + #: Brainpool Standard Curve, 384-bit + Brainpool_P384 = (ec.BrainpoolP384R1, '1.3.36.3.3.2.8.1.1.11', + b'\x2b\x24\x03\x03\x02\x08\x01\x01\x0b') + #: Brainpool Standard Curve, 512-bit + Brainpool_P512 = (ec.BrainpoolP512R1, '1.3.36.3.3.2.8.1.1.13', + b'\x2b\x24\x03\x03\x02\x08\x01\x01\x0d') + #: SECG curve secp256k1 + SECP256K1 = (ec.SECP256K1, '1.3.132.0.10', + b'\x2b\x81\x04\x00\x0a') + + def __new__(cls, impl_cls: Type, oid: str, oid_der: bytes, name: Optional[str] = None, key_size_bits: Optional[int] = None) -> "EllipticCurveOID": + # preprocessing stage for enum members: + # - set enum_member.value to ObjectIdentifier(oid) + # - if curve is not None and curve.name is in ec._CURVE_TYPES, set enum_member.curve to curve + # - otherwise, set enum_member.curve to None + obj = object.__new__(cls) + if name is None: + newname = impl_cls.name + if not isinstance(newname, str): + raise TypeError(f"{impl_cls}.name is not string!") + name = newname + if key_size_bits is None: + newks = impl_cls.key_size + if not isinstance(newks, int): + raise TypeError(f"{impl_cls}.name is not string!") + key_size_bits = newks + + algs = {256: (HashAlgorithm.SHA256, SymmetricKeyAlgorithm.AES128), + 384: (HashAlgorithm.SHA384, SymmetricKeyAlgorithm.AES192), + 512: (HashAlgorithm.SHA512, SymmetricKeyAlgorithm.AES256), + 521: (HashAlgorithm.SHA512, SymmetricKeyAlgorithm.AES256)} + + (kdf_alg, kek_alg) = algs[key_size_bits] + + obj._value_ = ECFields(name, oid, oid_der, key_size_bits, kdf_alg, kek_alg, impl_cls) + + return obj + + @classmethod + def from_key_size(cls, key_size: int) -> Optional["EllipticCurveOID"]: + for c in EllipticCurveOID: + if c.value.key_size == key_size: + return c + warnings.warn(f"Cannot find any Elliptic curve of size: {key_size}") + return None + + @classmethod + def from_OID(cls, oid: bytes) -> Union["EllipticCurveOID", bytes]: + for c in EllipticCurveOID: + if c.value.OID_der == oid: + return c + warnings.warn(f"Unknown Elliptic curve OID: {oid!r}") + return oid + + @classmethod + def parse(cls, packet: bytearray) -> Union["EllipticCurveOID", bytes]: + oidlen = packet[0] + del packet[0] + ret = EllipticCurveOID.from_OID(bytes(packet[:oidlen])) + del packet[:oidlen] + return ret + + @property + def key_size(self) -> int: + return self.value.key_size + + @property + def oid(self) -> str: + return self.value.OID + + @property + def kdf_halg(self) -> HashAlgorithm: + return self.value.kdf_halg + + @property + def kek_alg(self) -> SymmetricKeyAlgorithm: + return self.value.kek_alg + + @property + def curve(self) -> Type: + return self.value.curve + + @property + def can_gen(self) -> bool: + return True + + def __bytes__(self) -> bytes: + return bytes([len(self.value.OID_der)]) + self.value.OID_der + + def __len__(self) -> int: + return len(self.value.OID_der) + 1 + + class RevocationReason(IntEnum): """Reasons explaining why a key or certificate was revoked.""" #: No reason was specified. This is the default reason. diff --git a/pgpy/packet/fields.py b/pgpy/packet/fields.py index dfffb93c..506cf404 100644 --- a/pgpy/packet/fields.py +++ b/pgpy/packet/fields.py @@ -16,8 +16,7 @@ except ImportError: collections_abc = collections -from pyasn1.codec.der import decoder -from pyasn1.codec.der import encoder +from typing import Union from cryptography.exceptions import InvalidSignature @@ -67,6 +66,7 @@ from ..symenc import _encrypt from ..types import Field +from ..types import Fingerprint __all__ = ['SubPackets', 'UserAttributeSubPackets', @@ -513,14 +513,14 @@ def __init__(self): self.oid = None def __len__(self): - return len(self.p) + len(encoder.encode(self.oid.value)) - 1 + return len(self.p) + len(self.oid) def __pubkey__(self): return ec.EllipticCurvePublicNumbers(self.p.x, self.p.y, self.oid.curve()).public_key(default_backend()) def __bytearray__(self): _b = bytearray() - _b += encoder.encode(self.oid.value)[1:] + _b += bytes(self.oid) _b += self.p.to_mpibytes() return _b @@ -537,14 +537,7 @@ def verify(self, subj, sigbytes, hash_alg): return True def parse(self, packet): - oidlen = packet[0] - del packet[0] - _oid = bytearray(b'\x06') - _oid.append(oidlen) - _oid += bytearray(packet[:oidlen]) - oid, _ = decoder.decode(bytes(_oid)) - self.oid = EllipticCurveOID(oid) - del packet[:oidlen] + self.oid = EllipticCurveOID.parse(packet) self.p = ECPoint(packet) if self.p.format != ECPointFormat.Standard: @@ -559,11 +552,11 @@ def __init__(self): self.oid = None def __len__(self): - return len(self.p) + len(encoder.encode(self.oid.value)) - 1 + return len(self.p) + len(self.oid) def __bytearray__(self): _b = bytearray() - _b += encoder.encode(self.oid.value)[1:] + _b += bytes(self.oid) _b += self.p.to_mpibytes() return _b @@ -588,14 +581,7 @@ def verify(self, subj, sigbytes, hash_alg): return True def parse(self, packet): - oidlen = packet[0] - del packet[0] - _oid = bytearray(b'\x06') - _oid.append(oidlen) - _oid += bytearray(packet[:oidlen]) - oid, _ = decoder.decode(bytes(_oid)) - self.oid = EllipticCurveOID(oid) - del packet[:oidlen] + self.oid = EllipticCurveOID.parse(packet) self.p = ECPoint(packet) if self.p.format != ECPointFormat.Native: @@ -611,7 +597,7 @@ def __init__(self): self.kdf = ECKDF() def __len__(self): - return len(self.p) + len(self.kdf) + len(encoder.encode(self.oid.value)) - 1 + return len(self.p) + len(self.kdf) + len(self.oid) def __pubkey__(self): if self.oid == EllipticCurveOID.Curve25519: @@ -619,9 +605,9 @@ def __pubkey__(self): else: return ec.EllipticCurvePublicNumbers(self.p.x, self.p.y, self.oid.curve()).public_key(default_backend()) - def __bytearray__(self): + def __bytearray__(self) -> bytearray: _b = bytearray() - _b += encoder.encode(self.oid.value)[1:] + _b += bytes(self.oid) _b += self.p.to_mpibytes() _b += self.kdf.__bytearray__() return _b @@ -661,15 +647,7 @@ def parse(self, packet): used to wrap the symmetric key used for the message encryption; see Section 8 for details """ - oidlen = packet[0] - del packet[0] - _oid = bytearray(b'\x06') - _oid.append(oidlen) - _oid += bytearray(packet[:oidlen]) - oid, _ = decoder.decode(bytes(_oid)) - - self.oid = EllipticCurveOID(oid) - del packet[:oidlen] + self.oid = EllipticCurveOID.parse(packet) self.p = ECPoint(packet) if self.oid == EllipticCurveOID.Curve25519: @@ -1087,12 +1065,12 @@ def parse(self, packet): self.encalg = packet[0] del packet[0] - def derive_key(self, s, curve, pkalg, fingerprint): + def derive_key(self, s: bytes, curve: EllipticCurveOID, pkalg: PubKeyAlgorithm, fingerprint: Fingerprint) -> bytes: # wrapper around the Concatenation KDF method provided by cryptography # assemble the additional data as defined in RFC 6637: # Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous data = bytearray() - data += encoder.encode(curve.value)[1:] + data += bytes(curve) data.append(pkalg) data += b'\x03\x01' data.append(self.halg) @@ -1440,14 +1418,17 @@ def _compute_chksum(self): chs = sum(bytearray(self.s.to_mpibytes())) % 65536 self.chksum = bytearray(self.int_to_bytes(chs, 2)) - def _generate(self, oid): + def _generate(self, params: Union[int, EllipticCurveOID]) -> None: if any(c != 0 for c in self): # pragma: no cover raise PGPError("Key is already populated!") - self.oid = EllipticCurveOID(oid) - - if not self.oid.can_gen: - raise ValueError("Curve not currently supported: {}".format(oid.name)) + if isinstance(params, int): + oid = EllipticCurveOID.from_key_size(params) + if oid is None: + raise ValueError("No supported Elliptic Curve of size {params}") + self.oid = oid + else: + self.oid = params pk = ec.generate_private_key(self.oid.curve(), default_backend()) pubn = pk.public_key().public_numbers() @@ -1489,14 +1470,20 @@ def _compute_chksum(self): chs = sum(bytearray(self.s.to_mpibytes())) % 65536 self.chksum = bytearray(self.int_to_bytes(chs, 2)) - def _generate(self, oid): + def _generate(self, params: Union[int, EllipticCurveOID]) -> None: if any(c != 0 for c in self): # pragma: no cover raise PGPError("Key is already populated!") - self.oid = EllipticCurveOID(oid) + if isinstance(params, int): + oid = EllipticCurveOID.from_key_size(params) + if oid is None: + raise ValueError("No supported Elliptic Curve of size {params}") + self.oid = oid + else: + self.oid = params if self.oid != EllipticCurveOID.Ed25519: - raise ValueError("EdDSA only supported with {}".format(EllipticCurveOID.Ed25519)) + raise ValueError(f"EdDSA only supported with {EllipticCurveOID.Ed25519}, not {self.oid}") pk = ed25519.Ed25519PrivateKey.generate() x = pk.public_key().public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) @@ -1563,8 +1550,14 @@ def __privkey__(self): else: return ECDSAPriv.__privkey__(self) - def _generate(self, oid): - _oid = EllipticCurveOID(oid) + def _generate(self, params: Union[int, EllipticCurveOID]) -> None: + if isinstance(params, int): + _oid = EllipticCurveOID.from_key_size(params) + if _oid is None: + raise ValueError("No supported Elliptic Curve of size {params}") + else: + _oid = params + if _oid == EllipticCurveOID.Curve25519: if any(c != 0 for c in self): # pragma: no cover raise PGPError("Key is already populated!") @@ -1580,7 +1573,7 @@ def _generate(self, oid): ), 'little')) self._compute_chksum() else: - ECDSAPriv._generate(self, oid) + ECDSAPriv._generate(self, _oid) self.kdf.halg = self.oid.kdf_halg self.kdf.encalg = self.oid.kek_alg diff --git a/pgpy/pgp.py b/pgpy/pgp.py index 04929a7d..cdd10b3d 100644 --- a/pgpy/pgp.py +++ b/pgpy/pgp.py @@ -20,11 +20,12 @@ from datetime import datetime, timezone -from typing import Any, List +from typing import Any, List, Optional, Union from cryptography.hazmat.primitives import hashes from .constants import CompressionAlgorithm +from .constants import EllipticCurveOID from .constants import Features from .constants import HashAlgorithm from .constants import ImageEncoding @@ -1516,12 +1517,14 @@ def key_algorithm(self): return self._key.pkalg @property - def key_size(self): + def key_size(self) -> Optional[Union[int, EllipticCurveOID]]: """ The size pertaining to this key. ``int`` for non-EC key algorithms; :py:obj:`constants.EllipticCurveOID` for EC keys. .. versionadded:: 0.4.1 """ + if self._key is None: + return None if self.key_algorithm in {PubKeyAlgorithm.ECDSA, PubKeyAlgorithm.ECDH, PubKeyAlgorithm.EdDSA}: return self._key.keymaterial.oid # check if keymaterial is not an Opaque class containing a bytearray diff --git a/requirements.txt b/requirements.txt index e2a25384..6bc0dd57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ cryptography>=3.3.2 -pyasn1 diff --git a/setup.cfg b/setup.cfg index af26cfdd..c7691a75 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,6 @@ packages = # TODO: fix support for cryptography >= 38.0.0 (https://github.com/SecurityInnovation/PGPy/issues/402) install_requires = cryptography>=3.3.2 - pyasn1 python_requires = >=3.6 # doc_requires = diff --git a/tests/test_05_actions.py b/tests/test_05_actions.py index 8a801c9c..186bc2f3 100644 --- a/tests/test_05_actions.py +++ b/tests/test_05_actions.py @@ -20,7 +20,6 @@ from pgpy import PGPMessage from pgpy import PGPSignature from pgpy import PGPUID -from pgpy._curves import _openssl_get_supported_curves from pgpy.constants import CompressionAlgorithm from pgpy.constants import EllipticCurveOID from pgpy.constants import Features @@ -288,7 +287,7 @@ def test_add_subkey(self, pkspec, skspec): if not alg.can_gen: pytest.xfail('Key algorithm {} not yet supported'.format(alg.name)) - if isinstance(size, EllipticCurveOID) and ((not size.can_gen) or size.curve.name not in _openssl_get_supported_curves()): + if isinstance(size, EllipticCurveOID) and (not size.can_gen): pytest.xfail('Curve {} not yet supported'.format(size.curve.name)) key = self.keys[pkspec] @@ -511,7 +510,7 @@ def test_revoke_subkey(self, pkspec, skspec): if not alg.can_gen: pytest.xfail('Key algorithm {} not yet supported'.format(alg.name)) - if isinstance(size, EllipticCurveOID) and ((not size.can_gen) or size.curve.name not in _openssl_get_supported_curves()): + if isinstance(size, EllipticCurveOID) and (not size.can_gen): pytest.xfail('Curve {} not yet supported'.format(size.curve.name)) # revoke the subkey diff --git a/tox.ini b/tox.ini index 0956986e..937511c8 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,6 @@ passenv = deps = cryptography>=2.6 gpg==1.10.0 - pyasn1 six>=1.9.0 pytest pytest-cov From a1b1947513333f70b0fd420e5b57310ad0b0762a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 13 Jun 2023 17:59:03 -0400 Subject: [PATCH 14/16] HashAlgorithm: treat Unknown explicitly, like PubKeyAlgorithm --- pgpy/constants.py | 7 ++++++ pgpy/packet/packets.py | 49 ++++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/pgpy/constants.py b/pgpy/constants.py index 2d76e607..a983fec6 100644 --- a/pgpy/constants.py +++ b/pgpy/constants.py @@ -281,6 +281,7 @@ def decompress(self, data): class HashAlgorithm(IntEnum): """Supported hash algorithms.""" + Unknown = -1 Invalid = 0x00 MD5 = 0x01 SHA1 = 0x02 @@ -297,6 +298,12 @@ class HashAlgorithm(IntEnum): #SHA3_384 = 14 #SHA3_512 = 15 + @classmethod + def _missing_(cls, val: object) -> 'HashAlgorithm': + if not isinstance(val, int): + raise TypeError(f"cannot look up HashAlgorithm by non-int {type(val)}") + return cls.Unknown + def __init__(self, *args): super(self.__class__, self).__init__() self._tuned_count = 255 diff --git a/pgpy/packet/packets.py b/pgpy/packet/packets.py index ef9fba63..08c3e8d5 100644 --- a/pgpy/packet/packets.py +++ b/pgpy/packet/packets.py @@ -383,17 +383,17 @@ def pubalg_int(self, val: int) -> None: self.signature = sigs.get(self.pubalg, OpaqueSignature)() @sdproperty - def halg(self): + def halg(self) -> HashAlgorithm: return self._halg - @halg.register(int) - @halg.register(HashAlgorithm) - def halg_int(self, val): - try: - self._halg = HashAlgorithm(val) - - except ValueError: # pragma: no cover + @halg.register + def halg_int(self, val: int) -> None: + if isinstance(val, HashAlgorithm): self._halg = val + else: + self._halg = HashAlgorithm(val) + if self._halg is HashAlgorithm.Unknown: + self._opaque_halg = val @property def signature(self): @@ -424,7 +424,10 @@ def __bytearray__(self): _bytes.append(self._opaque_pubalg) else: _bytes.append(self.pubalg) - _bytes += self.int_to_bytes(self.halg) + if self.halg is HashAlgorithm.Unknown: + _bytes.append(self._opaque_halg) + else: + _bytes.append(self.halg) _bytes += self.subpackets.__bytearray__() _bytes += self.hash2 _bytes += self.signature.__bytearray__() @@ -452,7 +455,10 @@ def canonical_bytes(self): _body.append(self._opaque_pubalg) else: _body.append(self.pubalg) - _body += self.int_to_bytes(self.halg) + if self.halg is HashAlgorithm.Unknown: + _body.append(self._opaque_halg) + else: + _body.append(self.halg) _body += self.subpackets.__hashbytearray__() _body += self.int_to_bytes(0, minlen=2) # empty unhashed subpackets _body += self.hash2 @@ -471,6 +477,8 @@ def __copy__(self): if self._pubalg is PubKeyAlgorithm.Unknown: spkt._opaque_pubalg = self._opaque_pubalg spkt._halg = self._halg + if self._halg is HashAlgorithm.Unknown: + spkt._opaque_halg = self._opaque_halg spkt.subpackets = copy.copy(self.subpackets) spkt.hash2 = copy.copy(self.hash2) @@ -699,17 +707,17 @@ def pubalg_int(self, val): self.signature = DSASignature() @sdproperty - def halg(self): + def halg(self) -> HashAlgorithm: return self._halg - @halg.register(int) - @halg.register(HashAlgorithm) - def halg_int(self, val): - try: - self._halg = HashAlgorithm(val) - - except ValueError: # pragma: no cover + @halg.register + def halg_int(self, val: int) -> None: + if isinstance(val, HashAlgorithm): self._halg = val + else: + self._halg = HashAlgorithm(val) + if self._halg is HashAlgorithm.Unknown: + self._opaque_halg: int = val @sdproperty def signer(self): @@ -736,7 +744,10 @@ def __bytearray__(self): _bytes = bytearray() _bytes += super(OnePassSignatureV3, self).__bytearray__() _bytes += bytearray([self.sigtype]) - _bytes += bytearray([self.halg]) + if self.halg is HashAlgorithm.Unknown: + _bytes.append(self._opaque_halg) + else: + _bytes.append(self.halg) _bytes += bytearray([self.pubalg]) _bytes += binascii.unhexlify(self.signer.encode("latin-1")) _bytes += bytearray([int(self.nested)]) From 522df3ccc82a2618eb27cc38913af1aeca426c22 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 14 Jun 2023 14:38:04 -0400 Subject: [PATCH 15/16] When a curve is unknown, treat the EC point as an opaque MPI --- pgpy/packet/fields.py | 34 ++++++++++++++++++++++------------ tests/test_06_compatibility.py | 1 - 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/pgpy/packet/fields.py b/pgpy/packet/fields.py index 506cf404..70d44b98 100644 --- a/pgpy/packet/fields.py +++ b/pgpy/packet/fields.py @@ -539,9 +539,12 @@ def verify(self, subj, sigbytes, hash_alg): def parse(self, packet): self.oid = EllipticCurveOID.parse(packet) - self.p = ECPoint(packet) - if self.p.format != ECPointFormat.Standard: - raise PGPIncompatibleECPointFormatError("Only Standard format is valid for ECDSA") + if isinstance(self.oid, EllipticCurveOID): + self.p = ECPoint(packet) + if self.p.format != ECPointFormat.Standard: + raise PGPIncompatibleECPointFormatError("Only Standard format is valid for ECDSA") + else: + self.p = MPI(packet) class EdDSAPub(PubKey): @@ -583,9 +586,12 @@ def verify(self, subj, sigbytes, hash_alg): def parse(self, packet): self.oid = EllipticCurveOID.parse(packet) - self.p = ECPoint(packet) - if self.p.format != ECPointFormat.Native: - raise PGPIncompatibleECPointFormatError("Only Native format is valid for EdDSA") + if isinstance(self.oid, EllipticCurveOID): + self.p = ECPoint(packet) + if self.p.format != ECPointFormat.Native: + raise PGPIncompatibleECPointFormatError("Only Native format is valid for EdDSA") + else: + self.p = MPI(packet) class ECDHPub(PubKey): @@ -649,12 +655,16 @@ def parse(self, packet): """ self.oid = EllipticCurveOID.parse(packet) - self.p = ECPoint(packet) - if self.oid == EllipticCurveOID.Curve25519: - if self.p.format != ECPointFormat.Native: - raise PGPIncompatibleECPointFormatError("Only Native format is valid for Curve25519") - elif self.p.format != ECPointFormat.Standard: - raise PGPIncompatibleECPointFormatError("Only Standard format is valid for this curve") + if isinstance(self.oid, EllipticCurveOID): + self.p = ECPoint(packet) + if self.oid == EllipticCurveOID.Curve25519: + if self.p.format != ECPointFormat.Native: + raise PGPIncompatibleECPointFormatError("Only Native format is valid for Curve25519") + elif self.p.format != ECPointFormat.Standard: + raise PGPIncompatibleECPointFormatError("Only Standard format is valid for this curve") + else: + self.p = MPI(packet) + self.kdf.parse(packet) diff --git a/tests/test_06_compatibility.py b/tests/test_06_compatibility.py index 8a51fa3b..8ffec7a1 100644 --- a/tests/test_06_compatibility.py +++ b/tests/test_06_compatibility.py @@ -45,7 +45,6 @@ def test_cert_unknown_subkey_algo(self) -> None: @pytest.mark.parametrize('flavor', ['ecdsa', 'eddsa', 'ecdh']) def test_cert_unknown_curve(self, flavor:str) -> None: k:PGPKey - pytest.xfail(f'cannot handle certificates containing subkeys with unknown OIDs for {flavor}') (k, _) = PGPKey.from_file(f'tests/testdata/compatibility/bob_with_unknown_{flavor}_curve.pgp') assert k.check_soundness() == SecurityIssues.OK From cee0467985819a4f8e37e2c928e91bfed0cd20f5 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 16 Jun 2023 17:35:20 -0400 Subject: [PATCH 16/16] PEP-8: whitespace tuneup --- pgpy/constants.py | 1 + pgpy/pgp.py | 1 + pgpy/types.py | 1 + 3 files changed, 3 insertions(+) diff --git a/pgpy/constants.py b/pgpy/constants.py index a983fec6..933c100b 100644 --- a/pgpy/constants.py +++ b/pgpy/constants.py @@ -600,6 +600,7 @@ def _missing_(cls, val: object) -> 'String2KeyType': raise TypeError(f"cannot look up String2KeyType by non-int {type(val)}") return cls.Unknown + class S2KGNUExtension(IntEnum): NoSecret = 1 Smartcard = 2 diff --git a/pgpy/pgp.py b/pgpy/pgp.py index cdd10b3d..457d43dd 100644 --- a/pgpy/pgp.py +++ b/pgpy/pgp.py @@ -648,6 +648,7 @@ def parse(self, packet: bytes) -> None: else: raise ValueError(f"Expected: Signature. Got: {format(pkt.__class__.__name__)}") + class PGPUID(ParentRef): @property def __sig__(self): diff --git a/pgpy/types.py b/pgpy/types.py index 49c89ed6..2270f7d5 100644 --- a/pgpy/types.py +++ b/pgpy/types.py @@ -748,6 +748,7 @@ def __repr__(self): class SorteDeque(collections.deque): """A deque subclass that tries to maintain sorted ordering using bisect""" + def insort(self, item): i = bisect.bisect_left(self, item) self.rotate(- i)