Skip to content

Commit

Permalink
primary/subkey binding now fully works - closes #104; removed FileLoa…
Browse files Browse the repository at this point in the history
…der class; renamed Exportable to Armorable; removed requests from requirements.txt; updated README.rst - closes #127; progress
  • Loading branch information
Commod0re committed Sep 29, 2014
1 parent 6ce75c6 commit d00010e
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 162 deletions.
6 changes: 3 additions & 3 deletions README.rst
Expand Up @@ -15,7 +15,7 @@ PGPy: Pretty Good Privacy for Python

Homepage: None yet.

`PGPy` is a Python (2 and 3) library for implementing Pretty Good Privacy into Python programs.
`PGPy` is a Python (2 and 3) library for implementing Pretty Good Privacy into Python programs, conforming to the OpenPGP specification per RFC 4880.

Features
--------
Expand Down Expand Up @@ -45,12 +45,12 @@ Requirements

Tested with: 3.4, 3.3, 3.2, 2.7

- `Requests <https://pypi.python.org/pypi/requests>`_

- `Cryptography <https://pypi.python.org/pypi/cryptography>`_

- `enum34 <https://pypi.python.org/pypi/enum34>`_

- `singledispatch <https://pypi.python.org/pypi/singledispatch>`_

- `six <https://pypi.python.org/pypi/six>`_

License
Expand Down
16 changes: 8 additions & 8 deletions docs/source/progress.rst
Expand Up @@ -77,16 +77,16 @@ PGPy is focused on eventually reaching complete OpenPGP implementation, adhering
- Sign, True, Certify User IDs using DSA
- Sign, True, Certify User Attribute packets using RSA
- Sign, True, Certify User Attribute packets using DSA
- Sign, False, Generate key binding signatures using RSA
- Sign, False, Generate key binding signatures using DSA
- Sign, False, Generate signatures directly on a key using RSA
- Sign, False, Generate signatures directly on a key using DSA
- Sign, True, Generate key binding signatures using RSA
- Sign, True, Generate key binding signatures using DSA
- Sign, True, Generate signatures directly on a key using RSA
- Sign, True, Generate signatures directly on a key using DSA
- Sign, True, Revoke certifications using RSA
- Sign, True, Revoke certifications using DSA
- Sign, False, Revoke key using RSA
- Sign, False, Revoke key using DSA
- Sign, False, Revoke subkey using RSA
- Sign, False, Revoke subkey using DSA
- Sign, True, Revoke key using RSA
- Sign, True, Revoke key using DSA
- Sign, True, Revoke subkey using RSA
- Sign, True, Revoke subkey using DSA
- Sign, True, Generate timestamp signatures using RSA
- Sign, True, Generate timestamp signatures using DSA
- Sign, False, Generate third party confirmation signatures using RSA
Expand Down
10 changes: 10 additions & 0 deletions pgpy/decorators.py
Expand Up @@ -90,6 +90,16 @@ def _preiter(first, iterable):
def __call__(self, action):
@functools.wraps(action)
def _action(key, *args, **kwargs):
ignore_usage = kwargs.pop('ignore_usage', False)
if ignore_usage:
for prop, expected in self.conditions.items():
if getattr(key, prop) != expected:
raise PGPError("Expected: {prop:s} == {eval:s}. Got: {got:s}"
"".format(prop=prop, eval=str(expected), got=str(getattr(key, prop))))

# do the thing
return action(key, *args, **kwargs)

with self.usage(key) as _key:
# check properties
for prop, expected in self.conditions.items():
Expand Down
4 changes: 3 additions & 1 deletion pgpy/packet/subpackets/signature.py
Expand Up @@ -826,7 +826,9 @@ def _sig(self):

@_sig.setter
def _sig(self, val):
self._sig = val
val.header = EmbeddedSignatureHeader()
val.update_hlen()
self._sigpkt = val

@property
def sigtype(self):
Expand Down
48 changes: 23 additions & 25 deletions pgpy/pgp.py
Expand Up @@ -64,7 +64,7 @@

from .packet.types import Opaque

from .types import Exportable
from .types import Armorable
from .types import PGPObject
from .types import SignatureVerification

Expand Down Expand Up @@ -113,7 +113,7 @@ def _deque_resort(seq, item):
raise ValueError


class PGPSignature(PGPObject, Exportable):
class PGPSignature(PGPObject, Armorable):
@property
def __sig__(self):
return self._signature.signature.__sig__()
Expand Down Expand Up @@ -296,8 +296,7 @@ def hashdata(self, subject):
For binary document signatures (type 0x00), the document data is
hashed directly.
"""
_s = self.load(subject)
_data += _s
_data += bytearray(subject)

if self.type == SignatureType.CanonicalDocument:
"""
Expand Down Expand Up @@ -410,7 +409,7 @@ def make_onepass(self):
return onepass

def parse(self, packet):
unarmored = self.ascii_unarmor(self.load(packet))
unarmored = self.ascii_unarmor(packet)
data = unarmored['body']

if unarmored['magic'] is not None and unarmored['magic'] != 'SIGNATURE':
Expand Down Expand Up @@ -546,7 +545,7 @@ def __add__(self, other):
"".format(self.__class__.__name__, other.__class__.__name__))


class PGPMessage(PGPObject, Exportable):
class PGPMessage(PGPObject, Armorable):
@staticmethod
def dash_unescape(text):
return re.subn(r'^- -', '-', text, flags=re.MULTILINE)[0]
Expand Down Expand Up @@ -722,12 +721,13 @@ def new(cls, message, **kwargs):
prefs.update(kwargs)

if prefs['cleartext']:
_m = cls.load(message).decode('latin-1')
# _m = cls.load(message).decode('latin-1')
_m = message

else:
# load literal data
lit = LiteralData()
lit._contents = cls.load(message)
lit._contents = bytearray(six.b(message))
lit.format = prefs['format']

if os.path.isfile(message):
Expand Down Expand Up @@ -805,7 +805,7 @@ def decrypt(self, passphrase):
return decmsg

def parse(self, packet):
unarmored = self.ascii_unarmor(self.load(packet))
unarmored = self.ascii_unarmor(packet)
data = unarmored['body']

if unarmored['magic'] is not None and unarmored['magic'] not in ['MESSAGE', 'SIGNATURE']:
Expand All @@ -831,7 +831,7 @@ def parse(self, packet):
self += Packet(data)


class PGPKey(PGPObject, Exportable):
class PGPKey(PGPObject, Armorable):
"""
11.1. Transferable Public Keys
Expand Down Expand Up @@ -1182,17 +1182,18 @@ def sign(self, subject, **prefs):
legal(id='revoke', types=PGPKey, criteria=[getattr(subject, 'is_primary', None) is False],
sigtypes={SignatureType.SubkeyRevocation}),
legal(id='selfcertify', types=(PGPUID, PGPKey),
criteria=[ (getattr(subject, 'fingerprint', None) if isinstance(subject, PGPKey)
else getattr(getattr(subject, '_parent', None), 'fingerprint', None)) == self.fingerprint],
criteria=[ (getattr(subject, 'fingerprint', None) if isinstance(subject, PGPKey) else
getattr(getattr(subject, '_parent', None), 'fingerprint', None)) == self.fingerprint],
sigtypes=SignatureType.certifications ^ {SignatureType.CertRevocation}),
legal(id='bind_sub', types=PGPKey,
criteria=[getattr(subject, 'is_primary', None) is False,
getattr(getattr(subject, '_parent', None), 'fingerprint', None) == self.fingerprint],
sigtypes={SignatureType.Subkey_Binding}),
# legal(id='bind_pri', types=PGPKey,
# criteria=[getattr(subject, 'is_primary', None) is True,
# getattr(self, '_parent', None) is not None,
# getattr(subject, 'fingerprint', None) == self._parent.fingerprint]),
legal(id='bind_pri', types=PGPKey,
criteria=[getattr(subject, 'is_primary', None) is True,
getattr(self, '_parent', None) is not None,
getattr(subject, 'fingerprint', None) == getattr(self._parent, 'fingerprint', False)],
sigtypes={SignatureType.PrimaryKey_Binding}),
legal(id='certify', types=PGPUID, criteria=[],
sigtypes=SignatureType.certifications ^ {SignatureType.CertRevocation}),
legal(id='directkey', types=PGPKey, criteria=[], sigtypes={SignatureType.DirectlyOnKey})]
Expand All @@ -1202,9 +1203,6 @@ def sign(self, subject, **prefs):
if combo is None:
raise PGPError('SignatureType.{:s} not supported on subject type {}'.format(sig.type.name, str(type(subject))))

if combo.id == 'load':
subject = self.load(subject)

if combo.id == 'msg':
subject = subject.message

Expand All @@ -1217,9 +1215,9 @@ def sign(self, subject, **prefs):
usage_flags = prefs.pop('usage', [])
sig._signature.subpackets.addnew('KeyFlags', hashed=True, flags=usage_flags)

# if combo.id == 'bind_sub' and subject.key_algorithm.can_sign:
# esig = self.subkeys[subject.fingerprint.keyid].sign(self, sigtype=SignatureType.PrimaryKey_Binding)
# sig._signature.subpackets.addnew('EmbeddedSignature', hashed=False, _sig=esig._signature)
if combo.id == 'bind_sub' and subject.key_algorithm.can_sign:
esig = self.subkeys[subject.fingerprint.keyid].sign(self, ignore_usage=True, sigtype=SignatureType.PrimaryKey_Binding)
sig._signature.subpackets.addnew('EmbeddedSignature', hashed=False, _sig=esig._signature)

if combo.id in ['selfcertify', 'directkey']:
flag_opts = [('cipherprefs', 'PreferredSymmetricAlgorithms'),
Expand Down Expand Up @@ -1279,8 +1277,8 @@ def verify(self, subject, signature=None):
raise ValueError("Unexpected signature value: {:s}".format(str(type(signature))))

# load the signature subject if necessary
if isinstance(subject, (six.string_types, bytes, bytearray)):
subject = self.load(subject)
# if isinstance(subject, (six.string_types, bytes, bytearray)):
# subject = self.load(subject)

def _filter_sigs(sigs):
_ids = {self.fingerprint.keyid} | set(self.subkeys)
Expand Down Expand Up @@ -1425,7 +1423,7 @@ def decrypt(self, message):
return decmsg

def parse(self, data):
unarmored = self.ascii_unarmor(self.load(data))
unarmored = self.ascii_unarmor(data)
data = unarmored['body']

if unarmored['magic'] is not None and 'KEY' not in unarmored['magic']:
Expand Down
99 changes: 12 additions & 87 deletions pgpy/types.py
Expand Up @@ -14,8 +14,6 @@
from enum import EnumMeta
from enum import IntEnum

import requests

import six

from ._author import __version__
Expand All @@ -28,7 +26,16 @@
re.ASCII = 0


class FileLoader(object):
class Armorable(six.with_metaclass(abc.ABCMeta)):
__crc24_init__ = 0x0B704CE
__crc24_poly__ = 0x1864CFB

__armor_fmt__ = '-----BEGIN PGP {block_type}-----\n' \
'{headers}\n' \
'{packet}\n' \
'={crc}\n' \
'-----END PGP {block_type}-----\n'

@staticmethod
def is_ascii(text):
if not isinstance(text, (str, bytes, bytearray)):
Expand All @@ -55,85 +62,13 @@ def is_path(ppath):

return False

@classmethod
def load(cls, lf):
_bytes = bytearray()

# None means nothing to load
if lf is None:
pass

# This is a file-like object
elif hasattr(lf, "readinto") and hasattr(lf, "fileno"):
_bytes = bytearray(os.stat(lf.fileno).st_size)
lf.readinto(_bytes)

elif hasattr(lf, "read"):
_bytes = bytearray(lf.read())

# this could be a path
elif cls.is_path(lf):
lf = os.path.expanduser(lf)
# this is a URI
if "://" in lf:
r = requests.get(lf, verify=True)

if not r.ok:
raise FileNotFoundError(lf)

_bytes = r.content

# this is an existing file
elif os.path.isfile(lf):
with open(lf, 'rb') as lf:
_bytes = bytearray(lf.read())

# this is a new file
elif os.path.isdir(os.path.dirname(lf)):
pass

# this is all wrong
else:
raise FileNotFoundError(lf)

# this is probably data we want to load directly
elif isinstance(lf, bytearray):
_bytes = lf

elif isinstance(lf, six.binary_type):
_bytes = bytearray(lf)

elif isinstance(lf, six.text_type):
_bytes = bytearray(lf.encode('latin-1'))

# something else entirely
else:
raise TypeError(type(lf))

return _bytes

def __init__(self):
super(FileLoader, self).__init__()
self._path = ''


class Exportable(six.with_metaclass(abc.ABCMeta, FileLoader)):
__crc24_init__ = 0x0B704CE
__crc24_poly__ = 0x1864CFB

__armor_fmt__ = '-----BEGIN PGP {block_type}-----\n' \
'{headers}\n' \
'{packet}\n' \
'={crc}\n' \
'-----END PGP {block_type}-----\n'

@abc.abstractproperty
def magic(self):
"""The magic string identifier for the current PGP type"""
return ""

def __init__(self):
super(Exportable, self).__init__()
super(Armorable, self).__init__()
self.ascii_headers = collections.OrderedDict()
self.ascii_headers['Version'] = 'PGPy v' + __version__ # Default value

Expand Down Expand Up @@ -162,7 +97,7 @@ def ascii_unarmor(text):
:raises:
"""
m = {'magic': None, 'headers': None, 'body': bytearray(), 'crc': None}
if not FileLoader.is_ascii(text):
if not Armorable.is_ascii(text):
m['body'] = bytearray(text)
return m

Expand Down Expand Up @@ -237,16 +172,6 @@ def crc24(self, data=None):
return crc & 0xFFFFFF


class Signable(six.with_metaclass(abc.ABCMeta, object)):
##TODO: this
pass


class SignatureContainer(object):
##TODO: this
pass


class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
__metaclass__ = abc.ABCMeta

Expand Down
1 change: 0 additions & 1 deletion requirements.txt
@@ -1,5 +1,4 @@
cryptography==0.5.4
requests
enum34
six
singledispatch

0 comments on commit d00010e

Please sign in to comment.