Skip to content

Commit

Permalink
API change: Drop FlagEnum, use enum.IntFlag
Browse files Browse the repository at this point in the history
For features, keyserverprefs, and key_flags, we also now
return a simple IntFlags object (not a set), and None when
no corresponding subpacket is present.
  • Loading branch information
dkg committed Jun 15, 2023
1 parent 0cb07d6 commit 8fc878f
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 70 deletions.
8 changes: 8 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ string in that case):
* signer
* signer_fingerprint

And the following properties of PGPSignature now return an
enum.IntFlag object instead of a set of custom FlagEnum objects. When
the corresponding subpacket is not present at all, they return None:

* key_flags
* keyserverprefs
* features

PGPKey.subkeys now returns an OrderedDict indexed by Fingerprint
instead of KeyID. When accessing this property via subscript (i.e.,
key.subkeys[x]), you can *also* index it by KeyID, but using a full
Expand Down
13 changes: 6 additions & 7 deletions pgpy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from cryptography.hazmat.primitives.asymmetric import ec, x25519, ed25519
from cryptography.hazmat.primitives.ciphers import algorithms

from .types import FlagEnum
from .decorators import classproperty

__all__ = [
Expand Down Expand Up @@ -624,7 +623,7 @@ class SignatureType(IntEnum):
ThirdParty_Confirmation = 0x50


class KeyServerPreferences(FlagEnum):
class KeyServerPreferences(IntFlag):
NoModify = 0x80


Expand Down Expand Up @@ -658,7 +657,7 @@ class TrustLevel(IntEnum):
Ultimate = 6


class KeyFlags(FlagEnum):
class KeyFlags(IntFlag):
"""Flags that determine a key's capabilities."""
#: Signifies that a key may be used to certify keys and user ids. Primary keys always have this, even if it is not specified.
Certify = 0x01
Expand All @@ -677,7 +676,7 @@ class KeyFlags(FlagEnum):
MultiPerson = 0x80


class Features(FlagEnum):
class Features(IntFlag):
ModificationDetection = 0x01
UnknownFeature02 = 0x02
UnknownFeature04 = 0x04
Expand All @@ -692,16 +691,16 @@ def pgpy_features(cls):
return Features.ModificationDetection


class RevocationKeyClass(FlagEnum):
class RevocationKeyClass(IntFlag):
Sensitive = 0x40
Normal = 0x80


class NotationDataFlags(FlagEnum):
class NotationDataFlags(IntFlag):
HumanReadable = 0x80


class TrustFlags(FlagEnum):
class TrustFlags(IntFlag):
Revoked = 0x20
SubRevoked = 0x40
Disabled = 0x80
Expand Down
10 changes: 6 additions & 4 deletions pgpy/packet/packets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1332,11 +1332,13 @@ def trustflags(self):

@trustflags.register(list)
def trustflags_list(self, val):
self._trustflags = val
self._trustflags = TrustFlags(sum(val))

@trustflags.register(int)
def trustflags_int(self, val):
self._trustflags = TrustFlags & val
@trustflags.register
def trustflags_int(self, val: Union[int, TrustFlags]):
if not isinstance(val, TrustFlags):
val = TrustFlags(val)
self._trustflags = val

def __init__(self):
super().__init__()
Expand Down
52 changes: 27 additions & 25 deletions pgpy/packet/subpackets/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from datetime import timedelta
from datetime import timezone

from typing import Optional, Type, Union
from enum import IntFlag

from typing import Optional, Type, Union, List, Set

from .types import EmbeddedSignatureHeader
from .types import Signature
Expand Down Expand Up @@ -141,19 +143,17 @@ class ByteFlag(Signature):
def flags(self):
return self._flags

@flags.register(set)
@flags.register(list)
def flags_seq(self, val):
self._flags = set(val)

@flags.register(int)
@flags.register(_KeyFlags)
@flags.register(_Features)
def flags_int(self, val):
@flags.register
def flags_seq(self, val: Union[set, list]):
if self.__flags__ is None: # pragma: no cover
raise AttributeError("Error: __flags__ not set!")
self._flags = self.__flags__(sum(val))

self._flags |= (self.__flags__ & val)
@flags.register
def flags_int(self, val: int):
if self.__flags__ is None: # pragma: no cover
raise AttributeError("Error: __flags__ not set!")
self._flags |= self.__flags__(val)

@flags.register(bytearray)
def flags_bytearray(self, val):
Expand Down Expand Up @@ -514,14 +514,15 @@ class RevocationKey(Signature):
def keyclass(self):
return self._keyclass

@keyclass.register(list)
def keyclass_list(self, val):
self._keyclass = val
@keyclass.register
def keyclass_list(self, val: Union[list, set]):
self._keyclass = RevocationKeyClass(sum(val))

@keyclass.register(int)
@keyclass.register(RevocationKeyClass)
def keyclass_int(self, val):
self._keyclass += RevocationKeyClass & val
@keyclass.register
def keyclass_int(self, val: int):
if not isinstance(val, RevocationKeyClass):
val = RevocationKeyClass(val)
self._keyclass |= val

@keyclass.register(bytearray)
def keyclass_bytearray(self, val):
Expand Down Expand Up @@ -609,14 +610,15 @@ class NotationData(Signature):
def flags(self):
return self._flags

@flags.register(list)
def flags_list(self, val):
self._flags = val
@flags.register
def flags_list(self, val: Union[set, list]):
self._flags = NotationDataFlags(sum(val))

@flags.register(int)
@flags.register(NotationDataFlags)
def flags_int(self, val):
self.flags += NotationDataFlags & val
@flags.register
def flags_int(self, val: int):
if not isinstance(val, NotationDataFlags):
val = NotationDataFlags(val)
self._flags |= val

@flags.register(bytearray)
def flags_bytearray(self, val):
Expand Down
23 changes: 12 additions & 11 deletions pgpy/pgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .constants import HashAlgorithm
from .constants import ImageEncoding
from .constants import KeyFlags
from .constants import KeyServerPreferences
from .constants import NotationDataFlags
from .constants import PacketTag
from .constants import PubKeyAlgorithm
Expand Down Expand Up @@ -146,13 +147,13 @@ def exportable(self) -> bool:
return True

@property
def features(self):
def features(self) -> Optional[Features]:
"""
A ``set`` of implementation features specified in this signature, if any. Otherwise, an empty ``set``.
The implementation Features specified in this signature, if any. Otherwise, None
"""
if 'Features' in self._signature.subpackets:
return next(iter(self._signature.subpackets['Features'])).flags
return set()
return None

@property
def hash2(self):
Expand Down Expand Up @@ -205,13 +206,13 @@ def key_expiration(self):
return None

@property
def key_flags(self):
def key_flags(self) -> Optional[KeyFlags]:
"""
A ``set`` of :py:obj:`~constants.KeyFlags` specified in this signature, if any. Otherwise, an empty ``set``.
The KeyFlags specified in this signature, if any. Otherwise, None.
"""
if 'KeyFlags' in self._signature.subpackets:
return next(iter(self._signature.subpackets['h_KeyFlags'])).flags
return set()
return None

@property
def keyserver(self) -> Optional[str]:
Expand All @@ -223,13 +224,13 @@ def keyserver(self) -> Optional[str]:
return None

@property
def keyserverprefs(self):
def keyserverprefs(self) -> Optional[KeyServerPreferences]:
"""
A ``list`` of :py:obj:`~constants.KeyServerPreferences` in this signature, if any. Otherwise, an empty ``list``.
The KeyServerPreferences` in this signature, if any. Otherwise, None.
"""
if 'KeyServerPreferences' in self._signature.subpackets:
return next(iter(self._signature.subpackets['h_KeyServerPreferences'])).flags
return []
return None

@property
def magic(self):
Expand Down Expand Up @@ -1973,7 +1974,7 @@ def _get_key_flags(self, user=None):
user = next(iter(self.userids))

# RFC 4880 says that primary keys *must* be capable of certification
return {KeyFlags.Certify} | (user.selfsig.key_flags if user.selfsig else set())
return KeyFlags.Certify | (user.selfsig.key_flags if user.selfsig and user.selfsig.key_flags is not None else KeyFlags(0))

return next(self.self_signatures).key_flags

Expand Down Expand Up @@ -2033,7 +2034,7 @@ def _sign(self, subject, sig, **prefs):
# mark all notations as human readable unless value is a bytearray
flags = NotationDataFlags.HumanReadable
if isinstance(value, bytearray):
flags = 0x00
flags = NotationDataFlags(0)

sig._signature.subpackets.addnew('NotationData', hashed=True, flags=flags, name=name, value=value)

Expand Down
17 changes: 0 additions & 17 deletions pgpy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
import warnings
import weakref

from enum import EnumMeta
from enum import IntEnum

from typing import Optional, Dict, List, Set, Tuple, Type, Union, OrderedDict, TypeVar, Generic

from .decorators import sdproperty
Expand All @@ -29,8 +26,6 @@
'Fingerprint',
'FingerprintDict',
'FingerprintValue',
'FlagEnum',
'FlagEnumMeta',
'Header',
'KeyID',
'MetaDispatchable',
Expand Down Expand Up @@ -664,18 +659,6 @@ def add_sigsubj(self, signature, by, subject=None, issues=None):
self._subjects.append(self.sigsubj(issues, by, signature, subject))


class FlagEnumMeta(EnumMeta):
def __and__(self, other):
return { f for f in iter(self) if f.value & other }

def __rand__(self, other): # pragma: no cover
return self & other


namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)


class KeyID(str):
'''
This class represents an 8-octet key ID, which is used on the wire in a v3 PKESK packet.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_00_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

def get_module_objs(module):
# return a set of strings that represent the names of objects defined in that module
return { n for n, o in inspect.getmembers(module, lambda m: inspect.getmodule(m) is module) } | ({'FlagEnum',} if module is importlib.import_module('pgpy.types') else set()) # dirty workaround until six fixes metaclass stuff to support EnumMeta in Python >= 3.6
return { n for n, o in inspect.getmembers(module, lambda m: inspect.getmodule(m) is module) }


def get_module_all(module):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_03_armor.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@
('created', datetime.fromtimestamp(1402615373, timezone.utc)),
('embedded', False),
('exportable', True),
('features', set()),
('features', None),
('hash2', b'\xc4\x24'),
('hashprefs', []),
('hash_algorithm', HashAlgorithm.SHA512),
('is_expired', False),
('key_algorithm', PubKeyAlgorithm.RSAEncryptOrSign),
('key_flags', set()),
('keyserverprefs', []),
('key_flags', None),
('keyserver', None),
('keyserverprefs', None),
('magic', "SIGNATURE"),
('notation', {}),
('policy_uri', None),
Expand Down
4 changes: 2 additions & 2 deletions tests/test_05_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,10 @@ def test_add_altuid(self, pkspec):
assert sig.cipherprefs == [SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.Camellia256]
assert sig.hashprefs == [HashAlgorithm.SHA384]
assert sig.compprefs == [CompressionAlgorithm.ZLIB]
assert sig.features == {Features.ModificationDetection}
assert sig.features == Features.ModificationDetection
assert sig.key_expiration == expiration - key.created
assert sig.keyserver == 'about:none'
assert sig.keyserverprefs == {KeyServerPreferences.NoModify}
assert sig.keyserverprefs == KeyServerPreferences.NoModify

assert uid.is_primary is False

Expand Down

0 comments on commit 8fc878f

Please sign in to comment.