7aa1c8a2310044700708a5501e35faa09ec5cf8a
[pgp-mime.git] / pgp_mime / key.py
1 # Copyright
2
3 import functools as _functools
4 import xml.etree.ElementTree as _etree
5
6 from . import LOG as _LOG
7 from . import crypt as _crypt
8
9 import pyassuan
10 import logging
11 from pyassuan import common as _common
12
13
14 @_functools.total_ordering
15 class SubKey (object):
16     """The crypographic key portion of an OpenPGP key.
17     """
18     def __init__(self, fingerprint=None):
19         self.fingerprint = fingerprint
20
21     def __str__(self):
22         return '<{} {}>'.format(type(self).__name__, self.fingerprint[-8:])
23
24     def __repr__(self):
25         return str(self)
26
27     def __eq__(self, other):
28         if self.fingerprint and hasattr(other, 'fingerprint'):
29             return self.fingerprint == other.fingerprint
30         return id(self) == id(other)
31
32     def __lt__(self, other):
33         if self.fingerprint and hasattr(other, 'fingerprint'):
34             return self.fingerprint < other.fingerprint
35         return id(self) < id(other)
36
37     def __hash__(self):
38         return int(self.fingerprint, 16)
39
40
41 @_functools.total_ordering
42 class UserID (object):
43     def __init__(self, uid=None, name=None, email=None, comment=None):
44         self.uid = uid
45         self.name = name
46         self.email = email
47         self.comment = comment
48
49     def __str__(self):
50         return '<{} {}>'.format(type(self).__name__, self.name)
51
52     def __repr__(self):
53         return str(self)
54
55     def __eq__(self, other):
56         if self.uid and hasattr(other, 'uid'):
57             return self.uid == other.uid
58         return id(self) == id(other)
59
60     def __lt__(self, other):
61         if self.uid and hasattr(other, 'uid'):
62             return self.uid < other.uid
63         return id(self) < id(other)
64
65     def __hash__(self):
66         return hash(self.uid)
67
68
69 @_functools.total_ordering
70 class Key (object):
71     def __init__(self, subkeys=None, uids=None):
72         revoked = False
73         expired = False
74         disabled = False
75         invalid = False
76         can_encrypt = False
77         can_sign = False
78         can_certify = False
79         can_authenticate = False
80         is_qualified = False
81         secret = False
82         protocol = None
83         issuer = None
84         chain_id = None
85         owner_trust = None
86         if subkeys is None:
87             subkeys = []
88         self.subkeys = subkeys
89         if uids is None:
90             uids = []
91         self.uids = uids
92
93     def __str__(self):
94         return '<{} {}>'.format(
95             type(self).__name__, self.subkeys[0].fingerprint[-8:])
96
97     def __repr__(self):
98         return str(self)
99
100     def __eq__(self, other):
101         other_subkeys = getattr(other, 'subkeys', None)
102         if self.subkeys and other_subkeys:
103             return self.subkeys[0] == other.subkeys[0]
104         return id(self) == id(other)
105
106     def __lt__(self, other):
107         other_subkeys = getattr(other, 'subkeys', None)
108         if self.subkeys and other_subkeys:
109             return self.subkeys[0] < other.subkeys[0]
110         return id(self) < id(other)
111
112     def __hash__(self):
113         return int(self.fingerprint, 16)
114
115
116 def lookup_keys(patterns=None):
117     """Lookup keys matching any patterns listed in ``patterns``.
118
119     >>> import pprint
120
121     >>> key = list(lookup_keys(['pgp-mime-test']))[0]
122     >>> key
123     <Key 4332B6E3>
124     >>> key.subkeys
125     [<SubKey 4332B6E3>, <SubKey 2F73DE2E>]
126     >>> key.uids
127     [<UserID pgp-mime-test>]
128     >>> key.can_encrypt
129     True
130     >>> key.protocol
131     'OpenPGP'
132
133     >>> print(list(lookup_keys(['pgp-mime-test'])))
134     [<Key 4332B6E3>]
135     >>> print(list(lookup_keys(['pgp-mime@invalid.com'])))
136     [<Key 4332B6E3>]
137     >>> print(list(lookup_keys(['4332B6E3'])))
138     [<Key 4332B6E3>]
139     >>> print(list(lookup_keys(['0x2F73DE2E'])))
140     [<Key 4332B6E3>]
141     >>> print(list(lookup_keys()))  # doctest: +ELLIPSIS
142     [..., <Key 4332B6E3>, ...]
143     """
144     _LOG.debug('lookup key: {}'.format(patterns))
145     client,socket = _crypt.get_client()
146     parameters = []
147     if patterns:
148         args = [' '.join(patterns)]
149     else:
150         args = []
151     try:
152         _crypt.hello(client)
153         rs,result = client.make_request(_common.Request('KEYLIST', *args))
154     finally:
155         _crypt.disconnect(client, socket)
156     tag_mapping = {
157         }
158     tree = _etree.fromstring(result.replace(b'\x00', b''))
159     for key in tree.findall('.//key'):
160         k = Key()
161         for child in key:
162             attribute = tag_mapping.get(
163                 child.tag, child.tag.replace('-', '_'))
164             if child.tag in [
165                 'revoked', 'expired', 'disabled', 'invalid', 'can-encrypt',
166                 'can-sign', 'can-certify', 'can-authenticate', 'is-qualified',
167                 'secret', 'revoked']:
168                 # boolean values
169                 value = child.get('value')
170                 if not value.startswith('0x'):
171                     raise NotImplementedError('{} value {}'.format(
172                             child.tag, value))
173                 value = int(value, 16)
174                 value = bool(value)
175             elif child.tag in [
176                 'protocol', 'owner-trust']:
177                 value = child.text
178             elif child.tag in ['issuer', 'chain-id']:
179                 # ignore for now
180                 pass
181             elif child.tag in ['subkeys', 'uids']:
182                 parser = globals()['_parse_{}'.format(attribute)]
183                 value = parser(child)
184             else:
185                 raise NotImplementedError(child.tag)
186             setattr(k, attribute, value)
187         yield k
188
189 def _parse_subkeys(element):
190     tag_mapping = {
191         'fpr': 'fingerprint',
192         }
193     subkeys = []
194     for subkey in element:
195         s = SubKey()
196         for child in subkey.iter():
197             if child == subkey:  # iter() includes the root element
198                 continue
199             attribute = tag_mapping.get(
200                 child.tag, child.tag.replace('-', '_'))
201             if child.tag in [
202                 'fpr']:
203                 value = child.text
204             else:
205                 raise NotImplementedError(child.tag)
206             setattr(s, attribute, value)
207         subkeys.append(s)
208     return subkeys
209
210 def _parse_uids(element):
211     tag_mapping = {
212         }
213     uids = []
214     for uid in element:
215         u = UserID()
216         for child in uid.iter():
217             if child == uid:  # iter() includes the root element
218                 continue
219             attribute = tag_mapping.get(
220                 child.tag, child.tag.replace('-', '_'))
221             if child.tag in [
222                 'uid', 'name', 'email', 'comment']:
223                 value = child.text
224             else:
225                 raise NotImplementedError(child.tag)
226             setattr(u, attribute, value)
227         uids.append(u)
228     return uids