Commit | Line | Data |
---|---|---|
0bec96d6 | 1 | #! /usr/bin/env python3 |
ff4136c7 | 2 | # -*- coding: utf-8 -*- |
0bec96d6 AE |
3 | """********************************************************************* |
4 | * Edward is free software: you can redistribute it and/or modify * | |
5 | * it under the terms of the GNU Affero Public License as published by * | |
6 | * the Free Software Foundation, either version 3 of the License, or * | |
7 | * (at your option) any later version. * | |
8 | * * | |
9 | * Edward is distributed in the hope that it will be useful, * | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
12 | * GNU Affero Public License for more details. * | |
13 | * * | |
14 | * You should have received a copy of the GNU Affero Public License * | |
15 | * along with Edward. If not, see <http://www.gnu.org/licenses/>. * | |
16 | * * | |
17 | * Copyright (C) 2014-2015 Andrew Engelbrecht (AGPLv3+) * | |
18 | * Copyright (C) 2014 Josh Drake (AGPLv3+) * | |
19 | * Copyright (C) 2014 Lisa Marie Maginnis (AGPLv3+) * | |
8bdfb6d4 AE |
20 | * Copyright (C) 2009-2015 Tails developers <tails@boum.org> ( GPLv3+) * |
21 | * Copyright (C) 2009 W. Trevor King <wking@drexel.edu> ( GPLv2+) * | |
0bec96d6 AE |
22 | * * |
23 | * Special thanks to Josh Drake for writing the original edward bot! :) * | |
24 | * * | |
25 | ************************************************************************ | |
26 | ||
a5385c04 | 27 | Code sourced from these projects: |
0bec96d6 | 28 | |
8bdfb6d4 AE |
29 | * http://agpl.fsf.org/emailselfdefense.fsf.org/edward/CURRENT/edward.tar.gz |
30 | * https://git-tails.immerda.ch/whisperback/tree/whisperBack/encryption.py?h=feature/python3 | |
31 | * http://www.physics.drexel.edu/~wking/code/python/send_pgp_mime | |
0bec96d6 AE |
32 | """ |
33 | ||
34 | import sys | |
0bec96d6 AE |
35 | import gpgme |
36 | import re | |
37 | import io | |
40c37ab3 | 38 | import os |
adcef2f7 | 39 | import importlib |
0bec96d6 | 40 | |
8bdfb6d4 AE |
41 | import email.parser |
42 | import email.message | |
43 | import email.encoders | |
44 | ||
45 | from email.mime.multipart import MIMEMultipart | |
46 | from email.mime.application import MIMEApplication | |
47 | from email.mime.nonmultipart import MIMENonMultipart | |
48 | ||
40c37ab3 | 49 | import edward_config |
c96f3837 | 50 | |
adcef2f7 AE |
51 | langs = ["an", "de", "el", "en", "fr", "ja", "pt-br", "ro", "ru", "tr"] |
52 | ||
38738401 AE |
53 | match_types = [('clearsign', |
54 | '-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----'), | |
55 | ('message', | |
56578eaf AE |
56 | '-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----'), |
57 | ('pubkey', | |
58 | '-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----'), | |
59 | ('detachedsig', | |
38738401 | 60 | '-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----')] |
56578eaf AE |
61 | |
62 | ||
63 | class EddyMsg (object): | |
64 | def __init__(self): | |
65 | self.multipart = False | |
66 | self.subparts = [] | |
67 | ||
68 | self.charset = None | |
69 | self.payload_bytes = None | |
70 | self.payload_pieces = [] | |
71 | ||
72 | self.filename = None | |
73 | self.content_type = None | |
74 | self.description_list = None | |
75 | ||
76 | ||
77 | class PayloadPiece (object): | |
78 | def __init__(self): | |
79 | self.piece_type = None | |
80 | self.string = None | |
81 | self.gpg_data = None | |
82 | ||
83 | ||
38738401 AE |
84 | class GPGData (object): |
85 | def __init__(self): | |
86 | self.decrypted = False | |
87 | ||
88 | self.plainobj = None | |
89 | self.sigs = [] | |
90 | self.keys = [] | |
91 | ||
d873ff48 AE |
92 | class ReplyInfo (object): |
93 | def __init__(self): | |
94 | self.replies = None | |
6906d46d AE |
95 | |
96 | self.target_key = None | |
97 | self.fallback_target_key = None | |
d873ff48 AE |
98 | self.msg_to_quote = "" |
99 | ||
100 | self.success_decrypt = False | |
101 | self.failed_decrypt = False | |
102 | self.public_key_received = False | |
103 | self.no_public_key = False | |
104 | self.sig_success = False | |
105 | self.sig_failure = False | |
106 | ||
38738401 | 107 | |
0bec96d6 AE |
108 | def main (): |
109 | ||
20f6e7c5 | 110 | handle_args() |
0bec96d6 | 111 | |
0a064403 AE |
112 | gpgme_ctx = get_gpg_context(edward_config.gnupghome, |
113 | edward_config.sign_with_key) | |
114 | ||
c96f3837 | 115 | email_text = sys.stdin.read() |
adcef2f7 AE |
116 | email_struct = parse_pgp_mime(email_text, gpgme_ctx) |
117 | ||
118 | email_to, email_from, email_subject = email_to_from_subject(email_text) | |
119 | lang = import_lang(email_to) | |
fafa21c3 | 120 | |
d873ff48 AE |
121 | replyinfo_obj = ReplyInfo() |
122 | replyinfo_obj.replies = lang.replies | |
123 | ||
124 | prepare_for_reply(email_struct, replyinfo_obj) | |
013f7cb8 | 125 | encrypt_to_key = get_key_from_fp(replyinfo_obj, gpgme_ctx) |
d873ff48 | 126 | reply_plaintext = write_reply(replyinfo_obj) |
adcef2f7 | 127 | |
2007103e AE |
128 | reply_mime = generate_encrypted_mime(reply_plaintext, email_from, \ |
129 | email_subject, encrypt_to_key, | |
130 | gpgme_ctx) | |
1fccb295 | 131 | |
2007103e | 132 | print(reply_mime) |
c96f3837 | 133 | |
0bec96d6 | 134 | |
0a064403 AE |
135 | def get_gpg_context (gnupghome, sign_with_key_fp): |
136 | ||
137 | os.environ['GNUPGHOME'] = gnupghome | |
138 | ||
139 | gpgme_ctx = gpgme.Context() | |
140 | gpgme_ctx.armor = True | |
141 | ||
142 | try: | |
143 | sign_with_key = gpgme_ctx.get_key(sign_with_key_fp) | |
144 | except: | |
145 | error("unable to load signing key. is the gnupghome " | |
146 | + "and signing key properly set in the edward_config.py?") | |
147 | exit(1) | |
148 | ||
149 | gpgme_ctx.signers = [sign_with_key] | |
150 | ||
151 | return gpgme_ctx | |
152 | ||
153 | ||
38738401 | 154 | def parse_pgp_mime (email_text, gpgme_ctx): |
394a1476 AE |
155 | |
156 | email_struct = email.parser.Parser().parsestr(email_text) | |
157 | ||
928e3819 AE |
158 | eddymsg_obj = parse_mime(email_struct) |
159 | split_payloads(eddymsg_obj) | |
160 | gpg_on_payloads(eddymsg_obj, gpgme_ctx) | |
8bb4b0d5 | 161 | |
928e3819 | 162 | return eddymsg_obj |
0bec96d6 | 163 | |
0bec96d6 | 164 | |
56578eaf | 165 | def parse_mime(msg_struct): |
0bec96d6 | 166 | |
928e3819 | 167 | eddymsg_obj = EddyMsg() |
8bb4b0d5 | 168 | |
56578eaf AE |
169 | if msg_struct.is_multipart() == True: |
170 | payloads = msg_struct.get_payload() | |
0bec96d6 | 171 | |
928e3819 AE |
172 | eddymsg_obj.multipart = True |
173 | eddymsg_obj.subparts = list(map(parse_mime, payloads)) | |
dd11a483 | 174 | |
56578eaf | 175 | else: |
928e3819 | 176 | eddymsg_obj = get_subpart_data(msg_struct) |
394a1476 | 177 | |
928e3819 | 178 | return eddymsg_obj |
c267c233 | 179 | |
80119cab | 180 | |
56578eaf | 181 | def scan_and_split (payload_piece, match_type, pattern): |
cf75de65 | 182 | |
a5d37d44 AE |
183 | # don't try to re-split pieces containing gpg data |
184 | if payload_piece.piece_type != "text": | |
185 | return [payload_piece] | |
186 | ||
56578eaf AE |
187 | flags = re.DOTALL | re.MULTILINE |
188 | matches = re.search("(?P<beginning>.*?)(?P<match>" + pattern + | |
189 | ")(?P<rest>.*)", payload_piece.string, flags=flags) | |
86663388 | 190 | |
56578eaf AE |
191 | if matches == None: |
192 | pieces = [payload_piece] | |
c96f3837 | 193 | |
56578eaf | 194 | else: |
d437f8b2 | 195 | |
56578eaf AE |
196 | beginning = PayloadPiece() |
197 | beginning.string = matches.group('beginning') | |
198 | beginning.piece_type = payload_piece.piece_type | |
d437f8b2 | 199 | |
56578eaf AE |
200 | match = PayloadPiece() |
201 | match.string = matches.group('match') | |
202 | match.piece_type = match_type | |
d437f8b2 | 203 | |
56578eaf AE |
204 | rest = PayloadPiece() |
205 | rest.string = matches.group('rest') | |
206 | rest.piece_type = payload_piece.piece_type | |
d437f8b2 | 207 | |
56578eaf | 208 | more_pieces = scan_and_split(rest, match_type, pattern) |
4615b156 | 209 | pieces = [beginning, match ] + more_pieces |
d437f8b2 | 210 | |
56578eaf | 211 | return pieces |
d437f8b2 | 212 | |
d437f8b2 | 213 | |
56578eaf | 214 | def get_subpart_data (part): |
0bec96d6 | 215 | |
56578eaf | 216 | obj = EddyMsg() |
0bec96d6 | 217 | |
56578eaf AE |
218 | obj.charset = part.get_content_charset() |
219 | obj.payload_bytes = part.get_payload(decode=True) | |
220 | ||
221 | obj.filename = part.get_filename() | |
222 | obj.content_type = part.get_content_type() | |
223 | obj.description_list = part['content-description'] | |
224 | ||
225 | # your guess is as good as a-myy-ee-ine... | |
226 | if obj.charset == None: | |
227 | obj.charset = 'utf-8' | |
228 | ||
229 | if obj.payload_bytes != None: | |
0eb75d9c AE |
230 | try: |
231 | payload = PayloadPiece() | |
232 | payload.string = obj.payload_bytes.decode(obj.charset) | |
233 | payload.piece_type = 'text' | |
234 | ||
235 | obj.payload_pieces = [payload] | |
236 | except UnicodeDecodeError: | |
237 | pass | |
56578eaf AE |
238 | |
239 | return obj | |
240 | ||
241 | ||
928e3819 | 242 | def do_to_eddys_pieces (function_to_do, eddymsg_obj, data): |
56578eaf | 243 | |
928e3819 AE |
244 | if eddymsg_obj.multipart == True: |
245 | for sub in eddymsg_obj.subparts: | |
d873ff48 | 246 | do_to_eddys_pieces(function_to_do, sub, data) |
394a1476 | 247 | else: |
928e3819 | 248 | function_to_do(eddymsg_obj, data) |
dd11a483 AE |
249 | |
250 | ||
928e3819 | 251 | def split_payloads (eddymsg_obj): |
a5d37d44 AE |
252 | |
253 | for match_type in match_types: | |
928e3819 | 254 | do_to_eddys_pieces(split_payload_pieces, eddymsg_obj, match_type) |
a5d37d44 | 255 | |
a5d37d44 | 256 | |
928e3819 | 257 | def split_payload_pieces (eddymsg_obj, match_type): |
a5d37d44 AE |
258 | |
259 | (match_name, pattern) = match_type | |
260 | ||
261 | new_pieces_list = [] | |
928e3819 | 262 | for piece in eddymsg_obj.payload_pieces: |
a5d37d44 AE |
263 | new_pieces_list += scan_and_split(piece, match_name, pattern) |
264 | ||
928e3819 | 265 | eddymsg_obj.payload_pieces = new_pieces_list |
a5d37d44 AE |
266 | |
267 | ||
928e3819 | 268 | def gpg_on_payloads (eddymsg_obj, gpgme_ctx, prev_parts=[]): |
38738401 | 269 | |
928e3819 | 270 | if eddymsg_obj.multipart == True: |
101d54a8 | 271 | prev_parts=[] |
928e3819 | 272 | for sub in eddymsg_obj.subparts: |
101d54a8 AE |
273 | gpg_on_payloads (sub, gpgme_ctx, prev_parts) |
274 | prev_parts += [sub] | |
38738401 | 275 | |
d873ff48 | 276 | return |
38738401 | 277 | |
928e3819 | 278 | for piece in eddymsg_obj.payload_pieces: |
38738401 AE |
279 | |
280 | if piece.piece_type == "text": | |
281 | # don't transform the plaintext. | |
282 | pass | |
283 | ||
284 | elif piece.piece_type == "message": | |
7e18f14d | 285 | (plaintext, sigs) = decrypt_block(piece.string, gpgme_ctx) |
38738401 AE |
286 | |
287 | if plaintext: | |
288 | piece.gpg_data = GPGData() | |
b23e171d | 289 | piece.gpg_data.decrypted = True |
38738401 AE |
290 | piece.gpg_data.sigs = sigs |
291 | # recurse! | |
292 | piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx) | |
3a753fd0 AE |
293 | continue |
294 | ||
295 | # if not encrypted, check to see if this is an armored signature. | |
296 | (plaintext, sigs) = verify_sig_message(piece.string, gpgme_ctx) | |
297 | ||
298 | if plaintext: | |
299 | piece.piece_type = "signature" | |
300 | piece.gpg_data = GPGData() | |
301 | piece.gpg_data.sigs = sigs | |
302 | # recurse! | |
303 | piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx) | |
129543c3 | 304 | |
8f61c66a | 305 | elif piece.piece_type == "pubkey": |
f8ee6bd3 | 306 | key_fps = add_gpg_key(piece.string, gpgme_ctx) |
8f61c66a | 307 | |
f8ee6bd3 | 308 | if key_fps != []: |
8f61c66a | 309 | piece.gpg_data = GPGData() |
f8ee6bd3 | 310 | piece.gpg_data.keys = key_fps |
129543c3 AE |
311 | |
312 | elif piece.piece_type == "clearsign": | |
f8ee6bd3 | 313 | (plaintext, sig_fps) = verify_clear_signature(piece.string, gpgme_ctx) |
129543c3 | 314 | |
f8ee6bd3 | 315 | if sig_fps != []: |
129543c3 | 316 | piece.gpg_data = GPGData() |
f8ee6bd3 | 317 | piece.gpg_data.sigs = sig_fps |
129543c3 AE |
318 | piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx) |
319 | ||
101d54a8 AE |
320 | elif piece.piece_type == "detachedsig": |
321 | for prev in prev_parts: | |
322 | payload_bytes = prev.payload_bytes | |
6d240f68 | 323 | sig_fps = verify_detached_signature(piece.string, payload_bytes, gpgme_ctx) |
101d54a8 | 324 | |
6d240f68 AE |
325 | if sig_fps != []: |
326 | piece.gpg_data = GPGData() | |
327 | piece.gpg_data.sigs = sig_fps | |
328 | piece.gpg_data.plainobj = prev | |
329 | break | |
7e18f14d | 330 | |
38738401 AE |
331 | else: |
332 | pass | |
333 | ||
334 | ||
928e3819 | 335 | def prepare_for_reply (eddymsg_obj, replyinfo_obj): |
dd11a483 | 336 | |
928e3819 | 337 | do_to_eddys_pieces(prepare_for_reply_pieces, eddymsg_obj, replyinfo_obj) |
56578eaf | 338 | |
928e3819 | 339 | def prepare_for_reply_pieces (eddymsg_obj, replyinfo_obj): |
56578eaf | 340 | |
928e3819 | 341 | for piece in eddymsg_obj.payload_pieces: |
38738401 | 342 | if piece.piece_type == "text": |
d873ff48 AE |
343 | # don't quote the plaintext part. |
344 | pass | |
345 | ||
38738401 | 346 | elif piece.piece_type == "message": |
05683768 | 347 | prepare_for_reply_message(piece, replyinfo_obj) |
d873ff48 | 348 | |
05683768 AE |
349 | elif piece.piece_type == "pubkey": |
350 | prepare_for_reply_pubkey(piece, replyinfo_obj) | |
6906d46d | 351 | |
05683768 | 352 | elif (piece.piece_type == "clearsign") \ |
3a753fd0 AE |
353 | or (piece.piece_type == "detachedsig") \ |
354 | or (piece.piece_type == "signature"): | |
05683768 | 355 | prepare_for_reply_sig(piece, replyinfo_obj) |
d873ff48 | 356 | |
d873ff48 | 357 | |
05683768 | 358 | def prepare_for_reply_message (piece, replyinfo_obj): |
44fe39b7 | 359 | |
05683768 AE |
360 | if piece.gpg_data == None: |
361 | replyinfo_obj.failed_decrypt = True | |
362 | return | |
363 | ||
5c40e676 | 364 | replyinfo_obj.success_decrypt = True |
05683768 AE |
365 | |
366 | # we already have a key (and a message) | |
367 | if replyinfo_obj.target_key != None: | |
368 | return | |
369 | ||
370 | if piece.gpg_data.sigs != []: | |
371 | replyinfo_obj.target_key = piece.gpg_data.sigs[0] | |
372 | get_signed_part = False | |
373 | else: | |
374 | # only include a signed message in the reply. | |
375 | get_signed_part = True | |
376 | ||
377 | replyinfo_obj.msg_to_quote = flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part) | |
378 | ||
379 | # to catch public keys in encrypted blocks | |
380 | prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj) | |
381 | ||
382 | ||
383 | def prepare_for_reply_pubkey (piece, replyinfo_obj): | |
384 | ||
385 | if piece.gpg_data == None or piece.gpg_data.keys == []: | |
386 | replyinfo_obj.no_public_key = True | |
387 | else: | |
388 | replyinfo_obj.public_key_received = True | |
389 | ||
390 | if replyinfo_obj.fallback_target_key == None: | |
391 | replyinfo_obj.fallback_target_key = piece.gpg_data.keys[0] | |
392 | ||
393 | ||
394 | def prepare_for_reply_sig (piece, replyinfo_obj): | |
395 | ||
396 | if piece.gpg_data == None or piece.gpg_data.sigs == []: | |
397 | replyinfo_obj.sig_failure = True | |
398 | else: | |
399 | replyinfo_obj.sig_success = True | |
400 | ||
401 | if replyinfo_obj.fallback_target_key == None: | |
402 | replyinfo_obj.fallback_target_key = piece.gpg_data.sigs[0] | |
6906d46d AE |
403 | |
404 | ||
d873ff48 | 405 | |
0402995a | 406 | def flatten_decrypted_payloads (eddymsg_obj, get_signed_part): |
d873ff48 AE |
407 | |
408 | flat_string = "" | |
409 | ||
0aa88c27 AE |
410 | if eddymsg_obj == None: |
411 | return "" | |
412 | ||
0402995a | 413 | # recurse on multi-part mime |
928e3819 AE |
414 | if eddymsg_obj.multipart == True: |
415 | for sub in eddymsg_obj.subparts: | |
0d553ce9 | 416 | flat_string += flatten_decrypted_payloads (sub, get_signed_part) |
d873ff48 AE |
417 | |
418 | return flat_string | |
419 | ||
928e3819 | 420 | for piece in eddymsg_obj.payload_pieces: |
d873ff48 AE |
421 | if piece.piece_type == "text": |
422 | flat_string += piece.string | |
0aa88c27 | 423 | |
0402995a AE |
424 | if (get_signed_part): |
425 | # don't include nested encryption | |
0d553ce9 | 426 | if (piece.piece_type == "message") \ |
0402995a AE |
427 | and (piece.gpg_data != None) \ |
428 | and (piece.gpg_data.decrypted == False): | |
429 | flat_string += flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part) | |
430 | ||
431 | elif ((piece.piece_type == "clearsign") \ | |
3a753fd0 AE |
432 | or (piece.piece_type == "detachedsig") \ |
433 | or (piece.piece_type == "signature")) \ | |
0402995a AE |
434 | and (piece.gpg_data != None): |
435 | # FIXME: the key used to sign this message needs to be the one that is used for the encrypted reply. | |
436 | flat_string += flatten_decrypted_payloads (piece.gpg_data.plainobj, get_signed_part) | |
d873ff48 AE |
437 | |
438 | return flat_string | |
439 | ||
440 | ||
013f7cb8 AE |
441 | def get_key_from_fp (replyinfo_obj, gpgme_ctx): |
442 | ||
dc24daf1 AE |
443 | if replyinfo_obj.target_key == None: |
444 | replyinfo_obj.target_key = replyinfo_obj.fallback_target_key | |
445 | ||
013f7cb8 AE |
446 | if replyinfo_obj.target_key != None: |
447 | try: | |
448 | encrypt_to_key = gpgme_ctx.get_key(replyinfo_obj.target_key) | |
449 | return encrypt_to_key | |
450 | ||
451 | except: | |
452 | pass | |
453 | ||
454 | # no available key to use | |
455 | replyinfo_obj.target_key = None | |
456 | replyinfo_obj.fallback_target_key = None | |
457 | ||
458 | replyinfo_obj.no_public_key = True | |
459 | replyinfo_obj.public_key_received = False | |
460 | ||
461 | return None | |
462 | ||
463 | ||
d873ff48 AE |
464 | def write_reply (replyinfo_obj): |
465 | ||
466 | reply_plain = "" | |
467 | ||
468 | if replyinfo_obj.success_decrypt == True: | |
d873ff48 | 469 | reply_plain += replyinfo_obj.replies['success_decrypt'] |
2694709a AE |
470 | |
471 | if replyinfo_obj.no_public_key == False: | |
472 | quoted_text = email_quote_text(replyinfo_obj.msg_to_quote) | |
473 | reply_plain += quoted_text | |
d873ff48 AE |
474 | |
475 | elif replyinfo_obj.failed_decrypt == True: | |
476 | reply_plain += replyinfo_obj.replies['failed_decrypt'] | |
477 | ||
478 | ||
479 | if replyinfo_obj.sig_success == True: | |
480 | reply_plain += "\n\n" | |
481 | reply_plain += replyinfo_obj.replies['sig_success'] | |
482 | ||
483 | elif replyinfo_obj.sig_failure == True: | |
484 | reply_plain += "\n\n" | |
485 | reply_plain += replyinfo_obj.replies['sig_failure'] | |
486 | ||
487 | ||
488 | if replyinfo_obj.public_key_received == True: | |
489 | reply_plain += "\n\n" | |
490 | reply_plain += replyinfo_obj.replies['public_key_received'] | |
491 | ||
492 | elif replyinfo_obj.no_public_key == True: | |
493 | reply_plain += "\n\n" | |
494 | reply_plain += replyinfo_obj.replies['no_public_key'] | |
495 | ||
496 | ||
497 | reply_plain += "\n\n" | |
498 | reply_plain += replyinfo_obj.replies['signature'] | |
56578eaf | 499 | |
d873ff48 | 500 | return reply_plain |
56578eaf AE |
501 | |
502 | ||
8f61c66a | 503 | def add_gpg_key (key_block, gpgme_ctx): |
c267c233 | 504 | |
8f61c66a | 505 | fp = io.BytesIO(key_block.encode('ascii')) |
c267c233 | 506 | |
8f61c66a AE |
507 | result = gpgme_ctx.import_(fp) |
508 | imports = result.imports | |
c267c233 | 509 | |
f8ee6bd3 | 510 | key_fingerprints = [] |
c267c233 | 511 | |
8f61c66a AE |
512 | if imports != []: |
513 | for import_ in imports: | |
514 | fingerprint = import_[0] | |
f8ee6bd3 | 515 | key_fingerprints += [fingerprint] |
c267c233 | 516 | |
e49673aa | 517 | debug("added gpg key: " + fingerprint) |
ec1e779a | 518 | |
f8ee6bd3 | 519 | return key_fingerprints |
ec1e779a AE |
520 | |
521 | ||
3a753fd0 AE |
522 | def verify_sig_message (msg_block, gpgme_ctx): |
523 | ||
524 | block_b = io.BytesIO(msg_block.encode('ascii')) | |
525 | plain_b = io.BytesIO() | |
526 | ||
527 | try: | |
528 | sigs = gpgme_ctx.verify(block_b, None, plain_b) | |
529 | except: | |
530 | return ("",[]) | |
531 | ||
532 | plaintext = plain_b.getvalue().decode('utf-8') | |
533 | ||
534 | fingerprints = [] | |
535 | for sig in sigs: | |
536 | fingerprints += [sig.fpr] | |
537 | return (plaintext, fingerprints) | |
538 | ||
539 | ||
129543c3 | 540 | def verify_clear_signature (sig_block, gpgme_ctx): |
cf75de65 | 541 | |
129543c3 AE |
542 | # FIXME: this might require the un-decoded bytes |
543 | # or the correct re-encoding with the carset of the mime part. | |
544 | msg_fp = io.BytesIO(sig_block.encode('utf-8')) | |
545 | ptxt_fp = io.BytesIO() | |
cf75de65 | 546 | |
129543c3 | 547 | result = gpgme_ctx.verify(msg_fp, None, ptxt_fp) |
cf75de65 | 548 | |
129543c3 AE |
549 | # FIXME: this might require using the charset of the mime part. |
550 | plaintext = ptxt_fp.getvalue().decode('utf-8') | |
cf75de65 | 551 | |
f8ee6bd3 | 552 | sig_fingerprints = [] |
129543c3 | 553 | for res_ in result: |
f8ee6bd3 | 554 | sig_fingerprints += [res_.fpr] |
cf75de65 | 555 | |
f8ee6bd3 | 556 | return plaintext, sig_fingerprints |
cf75de65 AE |
557 | |
558 | ||
101d54a8 AE |
559 | def verify_detached_signature (detached_sig, plaintext_bytes, gpgme_ctx): |
560 | ||
561 | detached_sig_fp = io.BytesIO(detached_sig.encode('ascii')) | |
562 | plaintext_fp = io.BytesIO(plaintext_bytes) | |
563 | ptxt_fp = io.BytesIO() | |
564 | ||
565 | result = gpgme_ctx.verify(detached_sig_fp, plaintext_fp, None) | |
566 | ||
567 | sig_fingerprints = [] | |
568 | for res_ in result: | |
569 | sig_fingerprints += [res_.fpr] | |
570 | ||
571 | return sig_fingerprints | |
572 | ||
573 | ||
5b3053c1 | 574 | def decrypt_block (msg_block, gpgme_ctx): |
0bec96d6 | 575 | |
5b3053c1 | 576 | block_b = io.BytesIO(msg_block.encode('ascii')) |
0bec96d6 AE |
577 | plain_b = io.BytesIO() |
578 | ||
afc1f64c AE |
579 | try: |
580 | sigs = gpgme_ctx.decrypt_verify(block_b, plain_b) | |
581 | except: | |
582 | return ("",[]) | |
0bec96d6 | 583 | |
6aa41372 | 584 | plaintext = plain_b.getvalue().decode('utf-8') |
cbdf22c1 AE |
585 | |
586 | fingerprints = [] | |
587 | for sig in sigs: | |
588 | fingerprints += [sig.fpr] | |
589 | return (plaintext, fingerprints) | |
0bec96d6 AE |
590 | |
591 | ||
d0489345 | 592 | def choose_reply_encryption_key (gpgme_ctx, fingerprints): |
fafa21c3 AE |
593 | |
594 | reply_key = None | |
d0489345 AE |
595 | for fp in fingerprints: |
596 | try: | |
597 | key = gpgme_ctx.get_key(fp) | |
598 | ||
599 | if (key.can_encrypt == True): | |
600 | reply_key = key | |
601 | break | |
602 | except: | |
603 | continue | |
604 | ||
fafa21c3 | 605 | |
216708e9 | 606 | return reply_key |
fafa21c3 AE |
607 | |
608 | ||
d65993b8 AE |
609 | def email_to_from_subject (email_text): |
610 | ||
611 | email_struct = email.parser.Parser().parsestr(email_text) | |
612 | ||
613 | email_to = email_struct['To'] | |
614 | email_from = email_struct['From'] | |
615 | email_subject = email_struct['Subject'] | |
616 | ||
617 | return email_to, email_from, email_subject | |
618 | ||
619 | ||
adcef2f7 AE |
620 | def import_lang(email_to): |
621 | ||
5250b3b8 AE |
622 | if email_to != None: |
623 | for lang in langs: | |
624 | if "edward-" + lang in email_to: | |
625 | lang = "lang." + re.sub('-', '_', lang) | |
626 | language = importlib.import_module(lang) | |
adcef2f7 | 627 | |
5250b3b8 | 628 | return language |
adcef2f7 AE |
629 | |
630 | return importlib.import_module("lang.en") | |
631 | ||
632 | ||
bf79a93e | 633 | def generate_encrypted_mime (plaintext, email_from, email_subject, encrypt_to_key, |
0a064403 | 634 | gpgme_ctx): |
1da9b527 | 635 | |
2007103e AE |
636 | # quoted printable encoding lets most ascii characters look normal |
637 | # before the decrypted mime message is decoded. | |
638 | char_set = email.charset.Charset("utf-8") | |
639 | char_set.body_encoding = email.charset.QP | |
8bdfb6d4 | 640 | |
2007103e AE |
641 | # MIMEText doesn't allow setting the text encoding |
642 | # so we use MIMENonMultipart. | |
643 | plaintext_mime = MIMENonMultipart('text', 'plain') | |
644 | plaintext_mime.set_payload(plaintext, charset=char_set) | |
216708e9 AE |
645 | |
646 | if (encrypt_to_key != None): | |
8bdfb6d4 AE |
647 | |
648 | encrypted_text = encrypt_sign_message(plaintext_mime.as_string(), | |
649 | encrypt_to_key, | |
40c37ab3 | 650 | gpgme_ctx) |
8bdfb6d4 AE |
651 | |
652 | control_mime = MIMEApplication("Version: 1", | |
653 | _subtype='pgp-encrypted', | |
654 | _encoder=email.encoders.encode_7or8bit) | |
655 | control_mime['Content-Description'] = 'PGP/MIME version identification' | |
656 | control_mime.set_charset('us-ascii') | |
657 | ||
658 | encoded_mime = MIMEApplication(encrypted_text, | |
659 | _subtype='octet-stream; name="encrypted.asc"', | |
660 | _encoder=email.encoders.encode_7or8bit) | |
661 | encoded_mime['Content-Description'] = 'OpenPGP encrypted message' | |
662 | encoded_mime['Content-Disposition'] = 'inline; filename="encrypted.asc"' | |
663 | encoded_mime.set_charset('us-ascii') | |
664 | ||
665 | message_mime = MIMEMultipart(_subtype="encrypted", protocol="application/pgp-encrypted") | |
666 | message_mime.attach(control_mime) | |
667 | message_mime.attach(encoded_mime) | |
668 | message_mime['Content-Disposition'] = 'inline' | |
216708e9 | 669 | |
216708e9 | 670 | else: |
2007103e AE |
671 | message_mime = plaintext_mime |
672 | ||
673 | message_mime['To'] = email_from | |
674 | message_mime['Subject'] = email_subject | |
675 | ||
676 | reply = message_mime.as_string() | |
1da9b527 AE |
677 | |
678 | return reply | |
679 | ||
680 | ||
f87041f8 AE |
681 | def email_quote_text (text): |
682 | ||
683 | quoted_message = re.sub(r'^', r'> ', text, flags=re.MULTILINE) | |
684 | ||
685 | return quoted_message | |
686 | ||
687 | ||
0a064403 | 688 | def encrypt_sign_message (plaintext, encrypt_to_key, gpgme_ctx): |
897cbaf6 | 689 | |
6aa41372 | 690 | plaintext_bytes = io.BytesIO(plaintext.encode('ascii')) |
1da9b527 AE |
691 | encrypted_bytes = io.BytesIO() |
692 | ||
897cbaf6 | 693 | gpgme_ctx.encrypt_sign([encrypt_to_key], gpgme.ENCRYPT_ALWAYS_TRUST, |
1da9b527 AE |
694 | plaintext_bytes, encrypted_bytes) |
695 | ||
6aa41372 | 696 | encrypted_txt = encrypted_bytes.getvalue().decode('ascii') |
1da9b527 AE |
697 | return encrypted_txt |
698 | ||
699 | ||
0a064403 AE |
700 | def error (error_msg): |
701 | ||
e4fb2ab2 | 702 | sys.stderr.write(progname + ": " + str(error_msg) + "\n") |
0a064403 AE |
703 | |
704 | ||
5e8f9094 AE |
705 | def debug (debug_msg): |
706 | ||
707 | if edward_config.debug == True: | |
0a064403 | 708 | error(debug_msg) |
5e8f9094 AE |
709 | |
710 | ||
20f6e7c5 AE |
711 | def handle_args (): |
712 | if __name__ == "__main__": | |
713 | ||
714 | global progname | |
715 | progname = sys.argv[0] | |
716 | ||
717 | if len(sys.argv) > 1: | |
718 | print(progname + ": error, this program doesn't " \ | |
719 | "need any arguments.", file=sys.stderr) | |
720 | exit(1) | |
721 | ||
722 | ||
0bec96d6 AE |
723 | main() |
724 |