Compare commits

...

10 Commits

Author SHA1 Message Date
Michael Greene
30a757181a Merge pull request #429 from dkg/cleanup-tox
Some checks failed
Tox Tests / unit-tests (macos-10.15, 3.10) (push) Has been cancelled
Tox Tests / unit-tests (macos-10.15, 3.6) (push) Has been cancelled
Tox Tests / unit-tests (macos-10.15, 3.7) (push) Has been cancelled
Tox Tests / unit-tests (macos-10.15, 3.8) (push) Has been cancelled
Tox Tests / unit-tests (macos-10.15, 3.9) (push) Has been cancelled
Tox Tests / unit-tests (ubuntu-18.04, 3.10) (push) Has been cancelled
Tox Tests / unit-tests (ubuntu-18.04, 3.6) (push) Has been cancelled
Tox Tests / unit-tests (ubuntu-18.04, 3.7) (push) Has been cancelled
Tox Tests / unit-tests (ubuntu-18.04, 3.8) (push) Has been cancelled
Tox Tests / unit-tests (ubuntu-18.04, 3.9) (push) Has been cancelled
Tox Tests / c-locale-test (push) Has been cancelled
Tox Tests / test-setup (py310-setup, 3.10) (push) Has been cancelled
Tox Tests / test-setup (py36-setup, 3.6) (push) Has been cancelled
Tox Tests / test-setup (py37-setup, 3.7) (push) Has been cancelled
Tox Tests / test-setup (py38-setup, 3.8) (push) Has been cancelled
Tox Tests / test-setup (py39-setup, 3.9) (push) Has been cancelled
Tox Tests / pep8 (push) Has been cancelled
Tox Tests / finish-coveralls (push) Has been cancelled
Try to avoid tox test setup failures in unit-tests and test-setup CI jobs
2023-03-13 20:58:49 -07:00
Daniel Kahn Gillmor
33e193253c Try to avoid tox test setup failures in unit-tests and test-setup CI jobs
I'm seeing errors like this in the test-setup and unit-tests CI jobs:

py: failed with pass_env values cannot contain whitespace, use comma to have multiple values in a single line, invalid values found 'HOME ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH PATH'

py39-setup: failed with pass_env values cannot contain whitespace, use comma to have multiple values in a single line, invalid values found 'HOME ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH PATH'

Despite the fact that [tox
documentation](https://tox.wiki/en/3.1.2/config.html) says that
"passenv" is "SPACE-SEPARATED-GLOBNAMES", the setup seems to think
that "pass_env" (which is not "passenv", note the lack of underscore)
is somehow bad.  Maybe putting each value on its own line will satisfy
this check?
2023-02-06 09:57:55 -05:00
Michael Greene
a0e56ff79d v0.6.0 2022-11-23 18:30:42 -08:00
Michael Greene
15c3fbd475 Merge pull request #424 from SecurityInnovation/bump-cryptography-version
adjust cryptography requirement
2022-11-23 18:13:00 -08:00
Michael Greene
b23d0832c5 yep 2022-11-23 17:55:41 -08:00
Michael Greene
94c749846b Merge pull request #423 from SecurityInnovation/cleanup
get stuff actually working again, and cleanup
2022-11-23 17:35:13 -08:00
Michael Greene
8e3b7b2e3f almost missed this lol 2022-11-23 17:19:56 -08:00
Michael Greene
791c9bd860 more test definition tweaks 2022-11-23 17:17:19 -08:00
Michael Greene
eb99007b3a big PEP8 pass 2022-11-23 17:15:45 -08:00
Michael Greene
d80d273921 update PR tests 2022-11-23 16:43:36 -08:00
17 changed files with 182 additions and 135 deletions

View File

@@ -6,7 +6,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-18.04, macos-10.15] os: [ubuntu-18.04, macos-10.15]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10'] python-version: [3.6, 3.7, 3.8, 3.9, '3.10']
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -59,8 +59,6 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- python-version: 3.5
env: py35-setup
- python-version: 3.6 - python-version: 3.6
env: py36-setup env: py36-setup
- python-version: 3.7 - python-version: 3.7
@@ -69,6 +67,8 @@ jobs:
env: py38-setup env: py38-setup
- python-version: 3.9 - python-version: 3.9
env: py39-setup env: py39-setup
- python-version: '3.10'
env: py310-setup
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@@ -20,15 +20,15 @@ Exceptions
.. autoexception:: PGPDecryptionError .. autoexception:: PGPDecryptionError
:py:class:`PGPOpenSSLCipherNotSupported` :py:class:`PGPOpenSSLCipherNotSupportedError`
---------------------------------------- ----------------------------------------
.. autoexception:: PGPOpenSSLCipherNotSupported .. autoexception:: PGPOpenSSLCipherNotSupportedError
:py:class:`PGPInsecureCipher` :py:class:`PGPInsecureCipherError`
----------------------------- -----------------------------
.. autoexception:: PGPInsecureCipher .. autoexception:: PGPInsecureCipherError
:py:class:`WontImplementError` :py:class:`WontImplementError`
------------------------------ ------------------------------

View File

@@ -4,6 +4,34 @@
Changelog Changelog
********* *********
v0.6.0
======
Released: Nov 23, 2022
.. note::
New Features
------------
* added the ability to ignore usage flags
Bugs Fixed
----------
* accept passphrases formatted as ``bytes``
* default to 3DES when no preferred algorithms are supported
* generate TZ-aware datetime objects
* works with Cryptography 38
Other Changes
-------------
* dropped support for Python 2 and Python 3 <= 3.5
* renamed ``PGPOpenSSLCipherNotSupported`` to ``PGPOpenSSLCipherNotSupportedError``
* renamed ``PGPOpenSSLCipherNotSupported`` to ``PGPOpenSSLCipherNotSupportedError``
* renamed ``PGPInsecureCipher`` to ``PGPInsecureCipherError``
* fixed a bunch of typos
* improve code style, increase consistency
v0.5.4 v0.5.4
====== ======

View File

@@ -49,25 +49,25 @@ if use_legacy_cryptography_decorator():
key_size = 256 key_size = 256
@utils.register_interface(ec.EllipticCurve) @utils.register_interface(ec.EllipticCurve) # noqa: E303
class BrainpoolP384R1(object): class BrainpoolP384R1(object):
name = 'brainpoolP384r1' name = 'brainpoolP384r1'
key_size = 384 key_size = 384
@utils.register_interface(ec.EllipticCurve) @utils.register_interface(ec.EllipticCurve) # noqa: E303
class BrainpoolP512R1(object): class BrainpoolP512R1(object):
name = 'brainpoolP512r1' name = 'brainpoolP512r1'
key_size = 512 key_size = 512
@utils.register_interface(ec.EllipticCurve) @utils.register_interface(ec.EllipticCurve) # noqa: E303
class X25519(object): class X25519(object):
name = 'X25519' name = 'X25519'
key_size = 256 key_size = 256
@utils.register_interface(ec.EllipticCurve) @utils.register_interface(ec.EllipticCurve) # noqa: E303
class Ed25519(object): class Ed25519(object):
name = 'ed25519' name = 'ed25519'
key_size = 256 key_size = 256
@@ -77,22 +77,22 @@ else:
key_size = 256 key_size = 256
class BrainpoolP384R1(ec.EllipticCurve): class BrainpoolP384R1(ec.EllipticCurve): # noqa: E303
name = 'brainpoolP384r1' name = 'brainpoolP384r1'
key_size = 384 key_size = 384
class BrainpoolP512R1(ec.EllipticCurve): class BrainpoolP512R1(ec.EllipticCurve): # noqa: E303
name = 'brainpoolP512r1' name = 'brainpoolP512r1'
key_size = 512 key_size = 512
class X25519(ec.EllipticCurve): class X25519(ec.EllipticCurve): # noqa: E303
name = 'X25519' name = 'X25519'
key_size = 256 key_size = 256
class Ed25519(ec.EllipticCurve): class Ed25519(ec.EllipticCurve): # noqa: E303
name = 'ed25519' name = 'ed25519'
key_size = 256 key_size = 256

View File

@@ -4,7 +4,6 @@ import bz2
import hashlib import hashlib
import imghdr import imghdr
import os import os
import time
import zlib import zlib
import warnings import warnings
@@ -23,27 +22,29 @@ from .types import FlagEnum
from .decorators import classproperty from .decorators import classproperty
from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1, X25519, Ed25519 from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1, X25519, Ed25519
__all__ = ['Backend', __all__ = [
'EllipticCurveOID', 'Backend',
'ECPointFormat', 'EllipticCurveOID',
'PacketTag', 'ECPointFormat',
'SymmetricKeyAlgorithm', 'PacketTag',
'PubKeyAlgorithm', 'SymmetricKeyAlgorithm',
'CompressionAlgorithm', 'PubKeyAlgorithm',
'HashAlgorithm', 'CompressionAlgorithm',
'RevocationReason', 'HashAlgorithm',
'ImageEncoding', 'RevocationReason',
'SignatureType', 'ImageEncoding',
'KeyServerPreferences', 'SignatureType',
'S2KGNUExtension', 'KeyServerPreferences',
'SecurityIssues', 'S2KGNUExtension',
'String2KeyType', 'SecurityIssues',
'TrustLevel', 'String2KeyType',
'KeyFlags', 'TrustLevel',
'Features', 'KeyFlags',
'RevocationKeyClass', 'Features',
'NotationDataFlags', 'RevocationKeyClass',
'TrustFlags',] 'NotationDataFlags',
'TrustFlags',
]
# this is 50 KiB # this is 50 KiB
@@ -621,7 +622,7 @@ MINIMUM_ASYMMETRIC_KEY_LENGTHS = {
PubKeyAlgorithm.RSASign: 2048, PubKeyAlgorithm.RSASign: 2048,
PubKeyAlgorithm.ElGamal: 2048, PubKeyAlgorithm.ElGamal: 2048,
PubKeyAlgorithm.DSA: 2048, PubKeyAlgorithm.DSA: 2048,
##
PubKeyAlgorithm.ECDSA: SAFE_CURVES, PubKeyAlgorithm.ECDSA: SAFE_CURVES,
PubKeyAlgorithm.EdDSA: SAFE_CURVES, PubKeyAlgorithm.EdDSA: SAFE_CURVES,
PubKeyAlgorithm.ECDH: SAFE_CURVES, PubKeyAlgorithm.ECDH: SAFE_CURVES,

View File

@@ -4,9 +4,9 @@
__all__ = ('PGPError', __all__ = ('PGPError',
'PGPEncryptionError', 'PGPEncryptionError',
'PGPDecryptionError', 'PGPDecryptionError',
'PGPIncompatibleECPointFormat', 'PGPIncompatibleECPointFormatError',
'PGPOpenSSLCipherNotSupported', 'PGPOpenSSLCipherNotSupportedError',
'PGPInsecureCipher', 'PGPInsecureCipherError',
'WontImplementError',) 'WontImplementError',)
@@ -25,17 +25,17 @@ class PGPDecryptionError(Exception):
pass pass
class PGPIncompatibleECPointFormat(Exception): class PGPIncompatibleECPointFormatError(Exception):
"""Raised when the point format is incompatible with the elliptic curve""" """Raised when the point format is incompatible with the elliptic curve"""
pass pass
class PGPOpenSSLCipherNotSupported(Exception): class PGPOpenSSLCipherNotSupportedError(Exception):
"""Raised when OpenSSL does not support the requested cipher""" """Raised when OpenSSL does not support the requested cipher"""
pass pass
class PGPInsecureCipher(Exception): class PGPInsecureCipherError(Exception):
"""Raised when a cipher known to be insecure is attempted to be used to encrypt data""" """Raised when a cipher known to be insecure is attempted to be used to encrypt data"""
pass pass

View File

@@ -63,7 +63,7 @@ from ..decorators import sdproperty
from ..errors import PGPDecryptionError from ..errors import PGPDecryptionError
from ..errors import PGPError from ..errors import PGPError
from ..errors import PGPIncompatibleECPointFormat from ..errors import PGPIncompatibleECPointFormatError
from ..symenc import _decrypt from ..symenc import _decrypt
from ..symenc import _encrypt from ..symenc import _encrypt
@@ -371,8 +371,8 @@ class EdDSASignature(DSASignature):
def __sig__(self): def __sig__(self):
# TODO: change this length when EdDSA can be used with another curve (Ed448) # TODO: change this length when EdDSA can be used with another curve (Ed448)
l = (EllipticCurveOID.Ed25519.key_size + 7) // 8 siglen = (EllipticCurveOID.Ed25519.key_size + 7) // 8
return self.int_to_bytes(self.r, l) + self.int_to_bytes(self.s, l) return self.int_to_bytes(self.r, siglen) + self.int_to_bytes(self.s, siglen)
class PubKey(MPIs): class PubKey(MPIs):
@@ -500,7 +500,7 @@ class ECPoint:
self.x = MPI(MPIs.bytes_to_int(xy[:self.bytelen])) self.x = MPI(MPIs.bytes_to_int(xy[:self.bytelen]))
self.y = MPI(MPIs.bytes_to_int(xy[self.bytelen:])) self.y = MPI(MPIs.bytes_to_int(xy[self.bytelen:]))
elif self.format == ECPointFormat.Native: elif self.format == ECPointFormat.Native:
self.bytelen = 0 # dummy value for copy self.bytelen = 0 # dummy value for copy
self.x = bytes(xy) self.x = bytes(xy)
self.y = None self.y = None
else: else:
@@ -592,7 +592,7 @@ class ECDSAPub(PubKey):
self.p = ECPoint(packet) self.p = ECPoint(packet)
if self.p.format != ECPointFormat.Standard: if self.p.format != ECPointFormat.Standard:
raise PGPIncompatibleECPointFormat("Only Standard format is valid for ECDSA") raise PGPIncompatibleECPointFormatError("Only Standard format is valid for ECDSA")
class EdDSAPub(PubKey): class EdDSAPub(PubKey):
@@ -643,7 +643,7 @@ class EdDSAPub(PubKey):
self.p = ECPoint(packet) self.p = ECPoint(packet)
if self.p.format != ECPointFormat.Native: if self.p.format != ECPointFormat.Native:
raise PGPIncompatibleECPointFormat("Only Native format is valid for EdDSA") raise PGPIncompatibleECPointFormatError("Only Native format is valid for EdDSA")
class ECDHPub(PubKey): class ECDHPub(PubKey):
@@ -718,9 +718,9 @@ class ECDHPub(PubKey):
self.p = ECPoint(packet) self.p = ECPoint(packet)
if self.oid == EllipticCurveOID.Curve25519: if self.oid == EllipticCurveOID.Curve25519:
if self.p.format != ECPointFormat.Native: if self.p.format != ECPointFormat.Native:
raise PGPIncompatibleECPointFormat("Only Native format is valid for Curve25519") raise PGPIncompatibleECPointFormatError("Only Native format is valid for Curve25519")
elif self.p.format != ECPointFormat.Standard: elif self.p.format != ECPointFormat.Standard:
raise PGPIncompatibleECPointFormat("Only Standard format is valid for this curve") raise PGPIncompatibleECPointFormatError("Only Standard format is valid for this curve")
self.kdf.parse(packet) self.kdf.parse(packet)
@@ -1177,14 +1177,14 @@ class PrivKey(PubKey):
return _bytes return _bytes
def __len__(self): def __len__(self):
l = super(PrivKey, self).__len__() + len(self.s2k) + len(self.chksum) nbytes = super(PrivKey, self).__len__() + len(self.s2k) + len(self.chksum)
if self.s2k: if self.s2k:
l += len(self.encbytes) nbytes += len(self.encbytes)
else: else:
l += sum(len(getattr(self, i)) for i in self.__privfields__) nbytes += sum(len(getattr(self, i)) for i in self.__privfields__)
return l return nbytes
def __copy__(self): def __copy__(self):
pk = super(PrivKey, self).__copy__() pk = super(PrivKey, self).__copy__()
@@ -1582,12 +1582,12 @@ class ECDHPriv(ECDSAPriv, ECDHPub):
return _b return _b
def __len__(self): def __len__(self):
l = ECDHPub.__len__(self) + len(self.s2k) + len(self.chksum) nbytes = ECDHPub.__len__(self) + len(self.s2k) + len(self.chksum)
if self.s2k: if self.s2k:
l += len(self.encbytes) nbytes += len(self.encbytes)
else: else:
l += sum(len(getattr(self, i)) for i in self.__privfields__) nbytes += sum(len(getattr(self, i)) for i in self.__privfields__)
return l return nbytes
def __privkey__(self): def __privkey__(self):
if self.oid == EllipticCurveOID.Curve25519: if self.oid == EllipticCurveOID.Curve25519:

View File

@@ -6,7 +6,6 @@ import calendar
import copy import copy
import hashlib import hashlib
import os import os
import re
import warnings import warnings
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -359,12 +358,14 @@ class SignatureV4(Signature):
def pubalg_int(self, val): def pubalg_int(self, val):
self._pubalg = PubKeyAlgorithm(val) self._pubalg = PubKeyAlgorithm(val)
sigs = {PubKeyAlgorithm.RSAEncryptOrSign: RSASignature, sigs = {
PubKeyAlgorithm.RSAEncrypt: RSASignature, PubKeyAlgorithm.RSAEncryptOrSign: RSASignature,
PubKeyAlgorithm.RSASign: RSASignature, PubKeyAlgorithm.RSAEncrypt: RSASignature,
PubKeyAlgorithm.DSA: DSASignature, PubKeyAlgorithm.RSASign: RSASignature,
PubKeyAlgorithm.ECDSA: ECDSASignature, PubKeyAlgorithm.DSA: DSASignature,
PubKeyAlgorithm.EdDSA: EdDSASignature,} PubKeyAlgorithm.ECDSA: ECDSASignature,
PubKeyAlgorithm.EdDSA: EdDSASignature,
}
self.signature = sigs.get(self.pubalg, OpaqueSignature)() self.signature = sigs.get(self.pubalg, OpaqueSignature)()
@@ -427,7 +428,6 @@ class SignatureV4(Signature):
with the length-of-length set to zero.) The unhashed subpacket data with the length-of-length set to zero.) The unhashed subpacket data
of the Signature packet being hashed is not included in the hash, and of the Signature packet being hashed is not included in the hash, and
the unhashed subpacket data length value is set to zero. the unhashed subpacket data length value is set to zero.
''' '''
_body = bytearray() _body = bytearray()
_body += self.int_to_bytes(self.header.version) _body += self.int_to_bytes(self.header.version)
@@ -435,7 +435,7 @@ class SignatureV4(Signature):
_body += self.int_to_bytes(self.pubalg) _body += self.int_to_bytes(self.pubalg)
_body += self.int_to_bytes(self.halg) _body += self.int_to_bytes(self.halg)
_body += self.subpackets.__hashbytearray__() _body += self.subpackets.__hashbytearray__()
_body += self.int_to_bytes(0, minlen=2) # empty unhashed subpackets _body += self.int_to_bytes(0, minlen=2) # empty unhashed subpackets
_body += self.hash2 _body += self.hash2
_body += self.signature.__bytearray__() _body += self.signature.__bytearray__()
@@ -443,7 +443,7 @@ class SignatureV4(Signature):
_hdr += b'\x88' _hdr += b'\x88'
_hdr += self.int_to_bytes(len(_body), minlen=4) _hdr += self.int_to_bytes(len(_body), minlen=4)
return _hdr + _body return _hdr + _body
def __copy__(self): def __copy__(self):
spkt = SignatureV4() spkt = SignatureV4()
spkt.header = copy.copy(self.header) spkt.header = copy.copy(self.header)

View File

@@ -703,9 +703,11 @@ class KeyServerPreferences(ByteFlag):
class PreferredKeyServer(URI): class PreferredKeyServer(URI):
__typeid__ = 0x18 __typeid__ = 0x18
class SubkeyBindingSignature(Signature): class SubkeyBindingSignature(Signature):
__typeid__ = 0x18 __typeid__ = 0x18
class PrimaryUserID(SubkeyBindingSignature): class PrimaryUserID(SubkeyBindingSignature):
__typeid__ = 0x19 __typeid__ = 0x19
@@ -1037,7 +1039,7 @@ class IntendedRecipient(Signature):
self.intended_recipient = packet[:fpr_len] self.intended_recipient = packet[:fpr_len]
del packet[:fpr_len] del packet[:fpr_len]
class AttestedCertifications(Signature): class AttestedCertifications(Signature):
''' '''
(from RFC4880bis-08) (from RFC4880bis-08)
@@ -1118,7 +1120,7 @@ class AttestedCertifications(Signature):
def attested_certifications(self): def attested_certifications(self):
return self._attested_certifications return self._attested_certifications
@attested_certifications.register(bytearray) @attested_certifications.register(bytearray)
@attested_certifications.register(bytes) @attested_certifications.register(bytes)
def attested_certifications_bytearray(self, val): def attested_certifications_bytearray(self, val):
self._attested_certifications = val self._attested_certifications = val

View File

@@ -77,10 +77,11 @@ class SubPacket(Dispatchable):
super(SubPacket, self).__init__() super(SubPacket, self).__init__()
self.header = Header() self.header = Header()
# if self.__typeid__ not in [-1, None]: if (
if (self.header.typeid == -1 and self.header.typeid == -1
(not hasattr(self.__typeid__, '__abstractmethod__')) and and (not hasattr(self.__typeid__, '__abstractmethod__'))
(self.__typeid__ not in [-1, None])): and (self.__typeid__ not in {-1, None})
):
self.header.typeid = self.__typeid__ self.header.typeid = self.__typeid__
def __bytearray__(self): def __bytearray__(self):

View File

@@ -47,7 +47,6 @@ from .packet import Packet
from .packet import Primary from .packet import Primary
from .packet import Private from .packet import Private
from .packet import PubKeyV4 from .packet import PubKeyV4
from .packet import PubSubKeyV4
from .packet import PrivKeyV4 from .packet import PrivKeyV4
from .packet import PrivSubKeyV4 from .packet import PrivSubKeyV4
from .packet import Public from .packet import Public
@@ -88,6 +87,7 @@ __all__ = ['PGPSignature',
class PGPSignature(Armorable, ParentRef, PGPObject): class PGPSignature(Armorable, ParentRef, PGPObject):
_reason_for_revocation = collections.namedtuple('ReasonForRevocation', ['code', 'comment']) _reason_for_revocation = collections.namedtuple('ReasonForRevocation', ['code', 'comment'])
@property @property
def __sig__(self): def __sig__(self):
return self._signature.signature.__sig__() return self._signature.signature.__sig__()
@@ -173,7 +173,7 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
def check_primitives(self): def check_primitives(self):
return self.hash_algorithm.is_considered_secure return self.hash_algorithm.is_considered_secure
def check_soundness(self): def check_soundness(self):
return self.check_primitives() return self.check_primitives()
@@ -284,7 +284,7 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
for n in self._signature.subpackets['h_AttestedCertifications']: for n in self._signature.subpackets['h_AttestedCertifications']:
attestations = bytes(n.attested_certifications) attestations = bytes(n.attested_certifications)
for i in range(0, len(attestations), hlen): for i in range(0, len(attestations), hlen):
ret.add(attestations[i:i+hlen]) ret.add(attestations[i:i + hlen])
return ret return ret
@property @property
@@ -326,7 +326,7 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
sig = PGPSignature() sig = PGPSignature()
if created is None: if created is None:
created=datetime.now(timezone.utc) created = datetime.now(timezone.utc)
sigpkt = SignatureV4() sigpkt = SignatureV4()
sigpkt.header.tag = 2 sigpkt.header.tag = 2
sigpkt.header.version = 4 sigpkt.header.version = 4
@@ -600,9 +600,9 @@ class PGPUID(ParentRef):
def _splitstring(self): def _splitstring(self):
'''returns name, comment email from User ID string''' '''returns name, comment email from User ID string'''
if not isinstance(self._uid, UserID): if not isinstance(self._uid, UserID):
return ("", "", "") return "", "", ""
if self._uid.uid == "": if self._uid.uid == "":
return ("", "", "") return "", "", ""
rfc2822 = re.match(r"""^ rfc2822 = re.match(r"""^
# name should always match something # name should always match something
(?P<name>.+?) (?P<name>.+?)
@@ -616,9 +616,8 @@ class PGPUID(ParentRef):
(\ <(?P<email>.+)>)? (\ <(?P<email>.+)>)?
$ $
""", self._uid.uid, flags=re.VERBOSE).groupdict() """, self._uid.uid, flags=re.VERBOSE).groupdict()
return (rfc2822['name'], rfc2822['comment'] or "", rfc2822['email'] or "")
return (rfc2822['name'], rfc2822['comment'] or "", rfc2822['email'] or "")
@property @property
def name(self): def name(self):
@@ -633,7 +632,6 @@ class PGPUID(ParentRef):
""" """
return self._splitstring()[1] return self._splitstring()[1]
@property @property
def email(self): def email(self):
""" """
@@ -1699,8 +1697,10 @@ class PGPKey(Armorable, ParentRef, PGPObject):
self._uids.insort(other) self._uids.insort(other)
else: else:
raise TypeError("unsupported operand type(s) for |: '{:s}' and '{:s}'" raise TypeError(
"".format(self.__class__.__name__, other.__class__.__name__)) "unsupported operand type(s) for |: '{:s}' and '{:s}'"
"".format(self.__class__.__name__, other.__class__.__name__)
)
if isinstance(self._sibling, weakref.ref) and not from_sib: if isinstance(self._sibling, weakref.ref) and not from_sib:
sib = self._sibling() sib = self._sibling()
@@ -2095,7 +2095,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
the certificate holder wants to attest to for redistribution with the certificate. the certificate holder wants to attest to for redistribution with the certificate.
Alternatively, any element in the list can be a ``bytes`` or ``bytearray`` object Alternatively, any element in the list can be a ``bytes`` or ``bytearray`` object
of the appropriate length (the length of this certification's digest). of the appropriate length (the length of this certification's digest).
This keyword is only used for signatures of type Attestation. This keyword is only used for signatures of type Attestation.
:type attested_certifications: ``list`` :type attested_certifications: ``list``
:keyword keyserver: Specify the URI of the preferred key server of the user. :keyword keyserver: Specify the URI of the preferred key server of the user.
This keyword is ignored for non-self-certifications. This keyword is ignored for non-self-certifications.
@@ -2360,28 +2360,27 @@ class PGPKey(Armorable, ParentRef, PGPObject):
def is_considered_insecure(self, self_verifying=False): def is_considered_insecure(self, self_verifying=False):
res = self.check_soundness(self_verifying=self_verifying) res = self.check_soundness(self_verifying=self_verifying)
for sk in self.subkeys.values(): for sk in self.subkeys.values():
res |= sk.check_soundness(self_verifying=self_verifying) res |= sk.check_soundness(self_verifying=self_verifying)
return res return res
def self_verify(self): def self_verify(self):
selfSigs = list(self.self_signatures) self_sigs = list(self.self_signatures)
res = SecurityIssues.OK res = SecurityIssues.OK
if selfSigs: if self_sigs:
for s in selfSigs: for s in self_sigs:
if not self.verify(self, s): if not self.verify(self, s):
res |= SecurityIssues.Invalid res |= SecurityIssues.Invalid
break break
else: else:
return SecurityIssues.NoSelfSignature return SecurityIssues.NoSelfSignature
return res return res
def _do_self_signatures_verification(self): def _do_self_signatures_verification(self):
try: try:
self._self_verified = SecurityIssues.OK
self._self_verified = self.self_verify() self._self_verified = self.self_verify()
except: except Exception:
self._self_verified = None self._self_verified = None
raise raise
@@ -2389,10 +2388,10 @@ class PGPKey(Armorable, ParentRef, PGPObject):
def self_verified(self): def self_verified(self):
warnings.warn("TODO: Self-sigs verification is not yet working because self-sigs are not parsed!!!") warnings.warn("TODO: Self-sigs verification is not yet working because self-sigs are not parsed!!!")
return SecurityIssues.OK return SecurityIssues.OK
if self._self_verified is None: if self._self_verified is None:
self._do_self_signatures_verification() self._do_self_signatures_verification()
return self._self_verified return self._self_verified
def check_primitives(self): def check_primitives(self):
@@ -2403,7 +2402,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
if self.is_expired: if self.is_expired:
warnings.warn('Key {} has expired at {:s}'.format(repr(self), self.expires_at)) warnings.warn('Key {} has expired at {:s}'.format(repr(self), self.expires_at))
res |= SecurityIssues.Expired res |= SecurityIssues.Expired
warnings.warn("TODO: Revocation checks are not yet implemented!!!") warnings.warn("TODO: Revocation checks are not yet implemented!!!")
warnings.warn("TODO: Flags (s.a. `disabled`) checks are not yet implemented!!!") warnings.warn("TODO: Flags (s.a. `disabled`) checks are not yet implemented!!!")
res |= int(bool(list(self.revocation_signatures))) * SecurityIssues.Revoked res |= int(bool(list(self.revocation_signatures))) * SecurityIssues.Revoked
@@ -2454,7 +2453,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
for ua in subject.userattributes: for ua in subject.userattributes:
for sig in _filter_sigs(ua.__sig__): for sig in _filter_sigs(ua.__sig__):
sspairs.append((sig, ua)) sspairs.append((sig, ua))
# subkey binding signatures # subkey binding signatures
for subkey in subject.subkeys.values(): for subkey in subject.subkeys.values():
for sig in _filter_sigs(subkey.__sig__): for sig in _filter_sigs(subkey.__sig__):
@@ -2477,13 +2476,13 @@ class PGPKey(Armorable, ParentRef, PGPObject):
self_verifying = sig.signer == subj.fingerprint self_verifying = sig.signer == subj.fingerprint
else: else:
self_verifying = False self_verifying = False
subkey_issues = self.check_soundness(self_verifying) subkey_issues = self.check_soundness(self_verifying)
signature_issues = self.check_primitives() signature_issues = self.check_primitives()
if self_verifying: if self_verifying:
signature_issues &= ~SecurityIssues.HashFunctionNotCollisionResistant signature_issues &= ~SecurityIssues.HashFunctionNotCollisionResistant
issues = signature_issues | subkey_issues issues = signature_issues | subkey_issues
if issues and issues.causes_signature_verify_to_fail: if issues and issues.causes_signature_verify_to_fail:
sigv.add_sigsubj(sig, self, subj, issues) sigv.add_sigsubj(sig, self, subj, issues)
@@ -2614,7 +2613,8 @@ class PGPKey(Armorable, ParentRef, PGPObject):
# last holds the last non-signature thing processed # last holds the last non-signature thing processed
##TODO: see issue #141 and fix this better ##TODO: see issue #141 and fix this better
_getpkt = lambda d: (Packet(d) if d else None) # flake8: noqa def _getpkt(d):
return Packet(d) if d else None
# some packets are filtered out # some packets are filtered out
getpkt = filter(lambda p: p.header.tag != PacketTag.Trust, iter(functools.partial(_getpkt, data), None)) getpkt = filter(lambda p: p.header.tag != PacketTag.Trust, iter(functools.partial(_getpkt, data), None))

View File

@@ -9,7 +9,7 @@ from cryptography.hazmat.primitives.ciphers import modes
from .errors import PGPDecryptionError from .errors import PGPDecryptionError
from .errors import PGPEncryptionError from .errors import PGPEncryptionError
from .errors import PGPInsecureCipher from .errors import PGPInsecureCipherError
__all__ = ['_encrypt', __all__ = ['_encrypt',
'_decrypt'] '_decrypt']
@@ -20,7 +20,7 @@ def _encrypt(pt, key, alg, iv=None):
iv = b'\x00' * (alg.block_size // 8) iv = b'\x00' * (alg.block_size // 8)
if alg.is_insecure: if alg.is_insecure:
raise PGPInsecureCipher("{:s} is not secure. Do not use it for encryption!".format(alg.name)) raise PGPInsecureCipherError("{:s} is not secure. Do not use it for encryption!".format(alg.name))
if not alg.is_supported: if not alg.is_supported:
raise PGPEncryptionError("Cipher {:s} not supported".format(alg.name)) raise PGPEncryptionError("Cipher {:s} not supported".format(alg.name))

View File

@@ -316,21 +316,21 @@ class Field(PGPObject):
class Header(Field): class Header(Field):
@staticmethod @staticmethod
def encode_length(l, nhf=True, llen=1): def encode_length(length, nhf=True, llen=1):
def _new_length(l): def _new_length(nl):
if 192 > l: if 192 > nl:
return Header.int_to_bytes(l) return Header.int_to_bytes(nl)
elif 8384 > l: elif 8384 > nl:
elen = ((l & 0xFF00) + (192 << 8)) + ((l & 0xFF) - 192) elen = ((nl & 0xFF00) + (192 << 8)) + ((nl & 0xFF) - 192)
return Header.int_to_bytes(elen, 2) return Header.int_to_bytes(elen, 2)
return b'\xFF' + Header.int_to_bytes(l, 4) return b'\xFF' + Header.int_to_bytes(nl, 4)
def _old_length(l, llen): def _old_length(nl, llen):
return Header.int_to_bytes(l, llen) if llen > 0 else b'' return Header.int_to_bytes(nl, llen) if llen > 0 else b''
return _new_length(l) if nhf else _old_length(l, llen) return _new_length(length) if nhf else _old_length(length, llen)
@sdproperty @sdproperty
def length(self): def length(self):
@@ -390,12 +390,11 @@ class Header(Field):
@sdproperty @sdproperty
def llen(self): def llen(self):
l = self.length
lf = self._lenfmt lf = self._lenfmt
if lf == 1: if lf == 1:
# new-format length # new-format length
if 192 > l: if 192 > self.length:
return 1 return 1
elif 8384 > self.length: # >= 192 is implied elif 8384 > self.length: # >= 192 is implied
@@ -476,12 +475,15 @@ class MetaDispatchable(abc.ABCMeta):
MetaDispatchable._roots.add(ncls) MetaDispatchable._roots.add(ncls)
elif issubclass(ncls, tuple(MetaDispatchable._roots)) and ncls.__typeid__ != -1: elif issubclass(ncls, tuple(MetaDispatchable._roots)) and ncls.__typeid__ != -1:
for rcls in [ root for root in MetaDispatchable._roots if issubclass(ncls, root) ]: for rcls in (root for root in MetaDispatchable._roots if issubclass(ncls, root)):
if (rcls, ncls.__typeid__) not in MetaDispatchable._registry: if (rcls, ncls.__typeid__) not in MetaDispatchable._registry:
MetaDispatchable._registry[(rcls, ncls.__typeid__)] = ncls MetaDispatchable._registry[(rcls, ncls.__typeid__)] = ncls
if (ncls.__ver__ is not None and ncls.__ver__ > 0 and if (
(rcls, ncls.__typeid__, ncls.__ver__) not in MetaDispatchable._registry): ncls.__ver__ is not None
and ncls.__ver__ > 0
and (rcls, ncls.__typeid__, ncls.__ver__) not in MetaDispatchable._registry
):
MetaDispatchable._registry[(rcls, ncls.__typeid__, ncls.__ver__)] = ncls MetaDispatchable._registry[(rcls, ncls.__typeid__, ncls.__ver__)] = ncls
# finally, return the new class object # finally, return the new class object
@@ -637,7 +639,10 @@ class SignatureVerification(object):
return self return self
def __repr__(self): def __repr__(self):
return "<"+ self.__class__.__name__ + "({" + str(bool(self)) + "})>" return '<{classname}({val})>'.format(
classname=self.__class__.__name__,
val=bool(self)
)
def add_sigsubj(self, signature, by, subject=None, issues=None): def add_sigsubj(self, signature, by, subject=None, issues=None):
if issues is None: if issues is None:
@@ -705,20 +710,23 @@ class Fingerprint(str):
def __bytes__(self): def __bytes__(self):
return binascii.unhexlify(self.encode("latin-1")) return binascii.unhexlify(self.encode("latin-1"))
def __pretty__(self): def __pretty__(self):
content = self content = self
if not bool(re.match(r'^[A-F0-9]{40}$', content)): if not bool(re.match(r'^[A-F0-9]{40}$', content)):
raise ValueError("Expected: String of 40 hex digits") raise ValueError("Expected: String of 40 hex digits")
halves = [ halves = [
[content[i:i+4] for i in range(0, 20, 4)], [content[i:i + 4] for i in range(0, 20, 4)],
[content[i:i+4] for i in range(20, 40, 4)] [content[i:i + 4] for i in range(20, 40, 4)]
] ]
return ' '.join(' '.join(c for c in half) for half in halves) return ' '.join(' '.join(c for c in half) for half in halves)
def __repr__(self): def __repr__(self):
return self.__class__.__name__+"("+repr(self.__pretty__())+")" return '{classname}({fp})'.format(
classname=self.__class__.__name__,
fp=self.__pretty__()
)
class SorteDeque(collections.deque): class SorteDeque(collections.deque):

View File

@@ -1,2 +1,2 @@
cryptography>=2.6 cryptography>=3.3.2
pyasn1 pyasn1

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = PGPy name = PGPy
version = 0.6.0-dev version = 0.6.0
author = Michael Greene author = Michael Greene
author_email = mgreene@securityinnovation.com author_email = mgreene@securityinnovation.com
maintainer = Security Innovation maintainer = Security Innovation
@@ -41,7 +41,7 @@ packages =
pgpy.packet.subpackets pgpy.packet.subpackets
# TODO: fix support for cryptography >= 38.0.0 (https://github.com/SecurityInnovation/PGPy/issues/402) # TODO: fix support for cryptography >= 38.0.0 (https://github.com/SecurityInnovation/PGPy/issues/402)
install_requires = install_requires =
cryptography>=2.6,<38 cryptography>=3.3.2
pyasn1 pyasn1
python_requires = >=3.6 python_requires = >=3.6

View File

@@ -17,13 +17,12 @@ from pgpy.constants import PubKeyAlgorithm
from pgpy.constants import SymmetricKeyAlgorithm from pgpy.constants import SymmetricKeyAlgorithm
from pgpy.packet import Packet from pgpy.packet import Packet
from pgpy.types import Armorable from pgpy.types import Armorable
from pgpy.types import PGPObject
from pgpy.types import Fingerprint from pgpy.types import Fingerprint
from pgpy.types import SignatureVerification from pgpy.types import SignatureVerification
from pgpy.errors import PGPError from pgpy.errors import PGPError
from pgpy.errors import PGPDecryptionError from pgpy.errors import PGPDecryptionError
from pgpy.errors import PGPEncryptionError from pgpy.errors import PGPEncryptionError
from pgpy.errors import PGPInsecureCipher from pgpy.errors import PGPInsecureCipherError
def _read(f, mode='r'): def _read(f, mode='r'):
@@ -376,7 +375,7 @@ class TestPGPMessage(object):
def test_encrypt_insecure_cipher(self): def test_encrypt_insecure_cipher(self):
msg = PGPMessage.new('asdf') msg = PGPMessage.new('asdf')
with pytest.raises(PGPInsecureCipher): with pytest.raises(PGPInsecureCipherError):
msg.encrypt('QwertyUiop', cipher=SymmetricKeyAlgorithm.IDEA) msg.encrypt('QwertyUiop', cipher=SymmetricKeyAlgorithm.IDEA)
def test_encrypt_sessionkey_wrongtype(self): def test_encrypt_sessionkey_wrongtype(self):

14
tox.ini
View File

@@ -11,11 +11,19 @@ markers =
[flake8] [flake8]
exclude = .git,.idea,__pycache__,.tox,tests/*,docs/*,test_load_asc_bench.py exclude = .git,.idea,__pycache__,.tox,tests/*,docs/*,test_load_asc_bench.py
ignore = E201,E202,E221,E251,E265,F403,F821,N805 ignore = E201,E202,E221,E251,E265,F403,F821,N805,W503
max-line-length = 160 max-line-length = 160
[testenv] [testenv]
passenv = HOME ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH PATH passenv =
HOME
ARCHFLAGS
LDFLAGS
CFLAGS
INCLUDE
LIB
LD_LIBRARY_PATH
PATH
deps = deps =
cryptography>=2.6 cryptography>=2.6
gpg==1.10.0 gpg==1.10.0
@@ -30,7 +38,7 @@ install_command = pip install {opts} --no-cache-dir {packages}
commands = commands =
py.test --cov pgpy --cov-report term-missing tests/ py.test --cov pgpy --cov-report term-missing tests/
[testenv:py{36,37,38,39}-setup] [testenv:py{36,37,38,39,310}-setup]
recreate = True recreate = True
allowlist_externals = allowlist_externals =
/usr/bin/rm /usr/bin/rm