SecurityInnovation / PGPy

Pretty Good Privacy for Python
BSD 3-Clause "New" or "Revised" License
314 stars 98 forks source link

Armored key with comment headers fails to parse #395

Closed bitfehler closed 1 year ago

bitfehler commented 2 years ago

Given the following key:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: User-ID:   Hamcha <hamcha@<redacted>>
Comment: Created:   09/09/2021 15:19
Comment: Expires:   09/09/2025 12:00
Comment: Type:  256-bit EdDSA (secret key available)
Comment: Usage: Signing, Encryption, Certifying User-IDs, SSH Authentication
Comment: Fingerprint:   21FC21B51DE174BD41BF2FDA1669C533B8CF6D89

mDMEYToJzhYJKwYBBAHaRw8BAQdAREp4tSfFbLTvRDf54KwW+hIuK7WfbFmyke2I
F4bUn7m0HUhhbWNoYSA8aGFtY2hhQGNydW5jaHkucm9ja3M+iJYEExYIAD4WIQQh
/CG1HeF0vUG/L9oWacUzuM9tiQUCYToJzgIbIwUJB4Xw0gULCQgHAgYVCgkICwIE
FgIDAQIeAQIXgAAKCRAWacUzuM9tiQpPAP4yk/88CmEqz3vmTztXk70hDYefScOg
gFyYyx4LCGg6AwD+L0o423dq1yMnFchj5cRMG6Prud2xfVnz0NMag+BwAgi4OARh
OgnOEgorBgEEAZdVAQUBAQdAl7zONuTCOZTccZPqnoZtLOUjnqWoNDYcE1zcT/91
SFQDAQgHiH4EGBYIACYWIQQh/CG1HeF0vUG/L9oWacUzuM9tiQUCYToJzgIbDAUJ
B4Xw0gAKCRAWacUzuM9tiRkhAQDkaEDFNhmEjqigWqbwDKv9ndWbl81AFN7zi7EQ
sg2jnAEAhy+n7KaJ6nM/BG+vuArxdfuD2HnKhino6q96MJOc/gU=
=wlZE
-----END PGP PUBLIC KEY BLOCK-----

and this minimal script:

import pgpy

key, _ = pgpy.PGPKey.from_file('hamcha.pgp')
message = pgpy.PGPMessage.new("42 is quite a pleasant number")
print("%r" % key)
encrypted_message = key.encrypt(message)
print("Done...")

The script fails:

/usr/lib/python3.10/site-packages/pgpy/types.py:189: UserWarning: Warning: Orphaned packet detected! <LiteralData [tag 11] at 0x7fc82fed7580>
  po = obj.parse(data)
<PGPKey [unknown] at 0x7FC830F25A20>
Traceback (most recent call last):
  File "/home/conrad/hack/test/bug/test.py", line 6, in <module>
    encrypted_message = key.encrypt(message)
  File "/usr/lib/python3.10/site-packages/pgpy/decorators.py", line 119, in _action
    raise PGPError("No key!")
pgpy.errors.PGPError: No key!

However, simply removing all the armor headers (and one of the blank the lines) from the input key makes it work just fine:

<PGPKey [PubKeyV4][0x1669C533B8CF6D89] at 0x7F4445725A20>
/usr/lib/python3.10/site-packages/pgpy/constants.py:189: CryptographyDeprecationWarning: IDEA has been deprecated
  bs = {SymmetricKeyAlgorithm.IDEA: algorithms.IDEA,
/usr/lib/python3.10/site-packages/pgpy/constants.py:191: CryptographyDeprecationWarning: CAST5 has been deprecated
  SymmetricKeyAlgorithm.CAST5: algorithms.CAST5,
/usr/lib/python3.10/site-packages/pgpy/constants.py:192: CryptographyDeprecationWarning: Blowfish has been deprecated
  SymmetricKeyAlgorithm.Blowfish: algorithms.Blowfish,
Done...

I also tried removing all but one headers or such, but to no avail.

Both GnuPG and go-openpgp handle the key file with headers just fine.

bitfehler commented 2 years ago

Digging a little deeper, I see that that the unarmoring is done via a regex, which seems to fail here:

script:

import pgpy

with open('hamcha.pgp', "rb") as f:
    key = f.read()

print("%r" % pgpy.PGPKey.ascii_unarmor(key))

output:

{'magic': None, 'headers': None, 'body': bytearray(b'-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nComment: User-ID:\tHamcha <hamcha@<redacted>>\r\nComment: Created:\t09/09/2021 15:19\r\nComment: Expires:\t09/09/2025 12:00\r\nComment: Type:\t256-bit EdDSA (secret key available)\r\nComment: Usage:\tSigning, Encryption, Certifying User-IDs, SSH Authentication\r\nComment: Fingerprint:\t21FC21B51DE174BD41BF2FDA1669C533B8CF6D89\r\n\r\n\r\nmDMEYToJzhYJKwYBBAHaRw8BAQdAREp4tSfFbLTvRDf54KwW+hIuK7WfbFmyke2I\r\nF4bUn7m0HUhhbWNoYSA8aGFtY2hhQGNydW5jaHkucm9ja3M+iJYEExYIAD4WIQQh\r\n/CG1HeF0vUG/L9oWacUzuM9tiQUCYToJzgIbIwUJB4Xw0gULCQgHAgYVCgkICwIE\r\nFgIDAQIeAQIXgAAKCRAWacUzuM9tiQpPAP4yk/88CmEqz3vmTztXk70hDYefScOg\r\ngFyYyx4LCGg6AwD+L0o423dq1yMnFchj5cRMG6Prud2xfVnz0NMag+BwAgi4OARh\r\nOgnOEgorBgEEAZdVAQUBAQdAl7zONuTCOZTccZPqnoZtLOUjnqWoNDYcE1zcT/91\r\nSFQDAQgHiH4EGBYIACYWIQQh/CG1HeF0vUG/L9oWacUzuM9tiQUCYToJzgIbDAUJ\r\nB4Xw0gAKCRAWacUzuM9tiRkhAQDkaEDFNhmEjqigWqbwDKv9ndWbl81AFN7zi7EQ\r\nsg2jnAEAhy+n7KaJ6nM/BG+vuArxdfuD2HnKhino6q96MJOc/gU=\r\n=wlZE\r\n-----END PGP PUBLIC KEY BLOCK-----\n'), 'crc': None}

which certainly doesn't seem right. I'll keep digging, but if anyone more knowledgeable has any hints...

bitfehler commented 2 years ago

Okay, so it turns out this is because the regular expression that checks if the input is ASCII does not allow tabs.