1 #! /usr/bin/env python3
2 # -*- coding: utf-8 -*-
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. *
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. *
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/>. *
17 * Copyright (C) 2014-2015 Andrew Engelbrecht (AGPLv3+) *
18 * Copyright (C) 2014 Josh Drake (AGPLv3+) *
19 * Copyright (C) 2014 Lisa Marie Maginnis (AGPLv3+) *
20 * Copyright (C) 2009-2015 Tails developers <tails@boum.org> ( GPLv3+) *
21 * Copyright (C) 2009 W. Trevor King <wking@drexel.edu> ( GPLv2+) *
23 * Special thanks to Josh Drake for writing the original edward bot! :) *
25 ************************************************************************
27 Code sourced from these projects:
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
45 from email
.mime
.multipart
import MIMEMultipart
46 from email
.mime
.application
import MIMEApplication
47 from email
.mime
.nonmultipart
import MIMENonMultipart
51 langs
= ["de", "el", "en", "fr", "ja", "pt-br", "ro", "ru", "tr"]
53 """This list contains the abbreviated names of reply languages available to
57 match_types
= [('clearsign',
58 '-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----'),
60 '-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----'),
62 '-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----'),
64 '-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----')]
66 """This list of tuples matches query names with re.search() queries used
67 to find GPG data for edward to process."""
70 class EddyMsg (object):
72 The EddyMsg class represents relevant parts of a mime message.
74 The represented message can be single-part or multi-part.
76 'multipart' is set to True if there are multiple mime parts.
78 'subparts' points to a list of mime sub-parts if it is a multi-part
79 message. Otherwise it points to an empty list.
81 'payload_bytes' contains the raw mime-decoded bytes that haven't been
82 encoded into a character set.
84 'payload_pieces' is a list of objects containing strings that when strung
85 together form the fully-decoded string representation of the mime part.
87 The 'charset' describes the character set of payload_bytes.
89 The 'filename', 'content_type' and 'description_list' come from the mime
94 self
.multipart
= False
98 self
.payload_bytes
= None
99 self
.payload_pieces
= []
102 self
.content_type
= None
103 self
.description_list
= None
106 class PayloadPiece (object):
108 PayloadPiece represents a complte or sub-section of a mime part.
110 Instances of this class are often strung together within one or more arrays
111 pointed to by each instance of the EddyMsg class.
113 'piece_type' refers to a string whose value describes the content of
114 'string'. Examples include "pubkey", for public keys, and "message", for
115 encrypted data (or armored signatures until they are known to be such.) The
116 names derive from the header and footer of each of these ascii-encoded gpg
119 'string' contains some string of text, such as non-GPG text, an encrypted
120 block of text, a signature, or a public key.
122 'gpg_data' points to any instances of GPGData that have been created based
123 on the contents of 'string'.
127 self
.piece_type
= None
132 class GPGData (object):
134 GPGData holds info from decryption, sig. verification, and/or pub. keys.
136 Instances of this class contain decrypted information, signature
137 fingerprints and/or fingerprints of processed and imported public keys.
139 'decrypted' is set to True if 'plainobj' was created from encrypted data.
141 'plainobj' points to any decrypted, or signed part of, a GPG signature. It
142 is intended to be an instance of the EddyMsg class.
144 'sigs' is a list of fingerprints of keys used to sign the data in plainobj.
146 'keys' is a list of fingerprints of keys obtained in public key blocks.
150 self
.decrypted
= False
156 class ReplyInfo (object):
158 ReplyInfo contains details that edward uses in generating its reply.
160 Instances of this class contain information about whether a message was
161 successfully encrypted or signed, and whether a public key was attached, or
162 retrievable, from the local GPG store. It stores the fingerprints of
163 potential encryption key candidates and the message (if any at all) to
164 quote in edward's reply.
166 'replies' points one of the dictionaries of translated replies.
168 'target_key' refers to the fingerprint of a key used to sign encrypted
169 data. This is the preferred key, if it is set, and if is available.
171 'fallback_target_key' referst to the fingerprint of a key used to sign
172 unencrypted data; alternatively it may be a public key attached to the
175 'msg_to_quote' refers to the part of a message which edward should quote in
176 his reply. This should remain as None if there was no encrypted and singed
177 part. This is to avoid making edward a service for decrypting other
178 people's messages to edward.
180 'success_decrypt' is set to True if edward could decrypt part of the
183 'failed_decrypt' is set to True if edward failed to decrypt part of the
186 'publick_key_received' is set to True if edward successfully imported a
189 'no_public_key' is set to True if edward doesn't have a key to encrypt to
190 when replying to the user.
192 'sig_success' is set to True if edward could to some extent verify the
193 signature of a signed part of the message to edward.
195 'sig_failure' is set to True if edward failed to some extent verify the
196 signature of a signed part of the message to edward.
202 self
.target_key
= None
203 self
.fallback_target_key
= None
204 self
.msg_to_quote
= ""
206 self
.success_decrypt
= False
207 self
.failed_decrypt
= False
208 self
.public_key_received
= False
209 self
.no_public_key
= False
210 self
.sig_success
= False
211 self
.sig_failure
= False
217 This is the main function for edward, a GPG reply bot.
219 Edward responds to GPG-encrypted and signed mail, encrypting and signing
220 the response if the user's public key is, or was, included in the message.
229 Mime or plaintext email passing in through standard input. Portions of
230 the email may be encrypted. If the To: address contains the text
231 "edward-ja", then the reply will contain a reply written in the
232 Japanese language. There are other languages as well. The default
236 A reply email will be printed to standard output. The contents of the
237 reply email depends on whether the original email was encrypted or not,
238 has or doesn't have a signature, whether a public key used in the
239 original message is provided or locally stored, and the language
240 implied by the To: address in the original email.
245 gpgme_ctx
= get_gpg_context(edward_config
.gnupghome
,
246 edward_config
.sign_with_key
)
248 email_text
= sys
.stdin
.read()
249 email_struct
= parse_pgp_mime(email_text
, gpgme_ctx
)
251 email_to
, email_from
, email_subject
= email_to_from_subject(email_text
)
252 lang
= import_lang(email_to
)
254 replyinfo_obj
= ReplyInfo()
255 replyinfo_obj
.replies
= lang
.replies
257 prepare_for_reply(email_struct
, replyinfo_obj
)
258 encrypt_to_key
= get_key_from_fp(replyinfo_obj
, gpgme_ctx
)
259 reply_plaintext
= write_reply(replyinfo_obj
)
261 reply_mime
= generate_encrypted_mime(reply_plaintext
, email_from
, \
262 email_subject
, encrypt_to_key
,
268 def get_gpg_context (gnupghome
, sign_with_key_fp
):
270 os
.environ
['GNUPGHOME'] = gnupghome
272 gpgme_ctx
= gpgme
.Context()
273 gpgme_ctx
.armor
= True
276 sign_with_key
= gpgme_ctx
.get_key(sign_with_key_fp
)
278 error("unable to load signing key. is the gnupghome "
279 + "and signing key properly set in the edward_config.py?")
282 gpgme_ctx
.signers
= [sign_with_key
]
287 def parse_pgp_mime (email_text
, gpgme_ctx
):
289 email_struct
= email
.parser
.Parser().parsestr(email_text
)
291 eddymsg_obj
= parse_mime(email_struct
)
292 split_payloads(eddymsg_obj
)
293 gpg_on_payloads(eddymsg_obj
, gpgme_ctx
)
298 def parse_mime(msg_struct
):
300 eddymsg_obj
= EddyMsg()
302 if msg_struct
.is_multipart() == True:
303 payloads
= msg_struct
.get_payload()
305 eddymsg_obj
.multipart
= True
306 eddymsg_obj
.subparts
= list(map(parse_mime
, payloads
))
309 eddymsg_obj
= get_subpart_data(msg_struct
)
314 def scan_and_split (payload_piece
, match_type
, pattern
):
316 # don't try to re-split pieces containing gpg data
317 if payload_piece
.piece_type
!= "text":
318 return [payload_piece
]
320 flags
= re
.DOTALL | re
.MULTILINE
321 matches
= re
.search("(?P<beginning>.*?)(?P<match>" + pattern
+
322 ")(?P<rest>.*)", payload_piece
.string
, flags
=flags
)
325 pieces
= [payload_piece
]
329 beginning
= PayloadPiece()
330 beginning
.string
= matches
.group('beginning')
331 beginning
.piece_type
= payload_piece
.piece_type
333 match
= PayloadPiece()
334 match
.string
= matches
.group('match')
335 match
.piece_type
= match_type
337 rest
= PayloadPiece()
338 rest
.string
= matches
.group('rest')
339 rest
.piece_type
= payload_piece
.piece_type
341 more_pieces
= scan_and_split(rest
, match_type
, pattern
)
342 pieces
= [beginning
, match
] + more_pieces
347 def get_subpart_data (part
):
351 obj
.charset
= part
.get_content_charset()
352 obj
.payload_bytes
= part
.get_payload(decode
=True)
354 obj
.filename
= part
.get_filename()
355 obj
.content_type
= part
.get_content_type()
356 obj
.description_list
= part
['content-description']
358 # your guess is as good as a-myy-ee-ine...
359 if obj
.charset
== None:
360 obj
.charset
= 'utf-8'
362 if obj
.payload_bytes
!= None:
364 payload
= PayloadPiece()
365 payload
.string
= obj
.payload_bytes
.decode(obj
.charset
)
366 payload
.piece_type
= 'text'
368 obj
.payload_pieces
= [payload
]
369 except UnicodeDecodeError:
375 def do_to_eddys_pieces (function_to_do
, eddymsg_obj
, data
):
377 if eddymsg_obj
.multipart
== True:
378 for sub
in eddymsg_obj
.subparts
:
379 do_to_eddys_pieces(function_to_do
, sub
, data
)
381 function_to_do(eddymsg_obj
, data
)
384 def split_payloads (eddymsg_obj
):
386 for match_type
in match_types
:
387 do_to_eddys_pieces(split_payload_pieces
, eddymsg_obj
, match_type
)
390 def split_payload_pieces (eddymsg_obj
, match_type
):
392 (match_name
, pattern
) = match_type
395 for piece
in eddymsg_obj
.payload_pieces
:
396 new_pieces_list
+= scan_and_split(piece
, match_name
, pattern
)
398 eddymsg_obj
.payload_pieces
= new_pieces_list
401 def gpg_on_payloads (eddymsg_obj
, gpgme_ctx
, prev_parts
=[]):
403 if eddymsg_obj
.multipart
== True:
405 for sub
in eddymsg_obj
.subparts
:
406 gpg_on_payloads (sub
, gpgme_ctx
, prev_parts
)
411 for piece
in eddymsg_obj
.payload_pieces
:
413 if piece
.piece_type
== "text":
414 # don't transform the plaintext.
417 elif piece
.piece_type
== "message":
418 (plaintext
, sigs
) = decrypt_block(piece
.string
, gpgme_ctx
)
421 piece
.gpg_data
= GPGData()
422 piece
.gpg_data
.decrypted
= True
423 piece
.gpg_data
.sigs
= sigs
425 piece
.gpg_data
.plainobj
= parse_pgp_mime(plaintext
, gpgme_ctx
)
428 # if not encrypted, check to see if this is an armored signature.
429 (plaintext
, sigs
) = verify_sig_message(piece
.string
, gpgme_ctx
)
432 piece
.piece_type
= "signature"
433 piece
.gpg_data
= GPGData()
434 piece
.gpg_data
.sigs
= sigs
436 piece
.gpg_data
.plainobj
= parse_pgp_mime(plaintext
, gpgme_ctx
)
438 elif piece
.piece_type
== "pubkey":
439 key_fps
= add_gpg_key(piece
.string
, gpgme_ctx
)
442 piece
.gpg_data
= GPGData()
443 piece
.gpg_data
.keys
= key_fps
445 elif piece
.piece_type
== "clearsign":
446 (plaintext
, sig_fps
) = verify_clear_signature(piece
.string
, gpgme_ctx
)
449 piece
.gpg_data
= GPGData()
450 piece
.gpg_data
.sigs
= sig_fps
451 piece
.gpg_data
.plainobj
= parse_pgp_mime(plaintext
, gpgme_ctx
)
453 elif piece
.piece_type
== "detachedsig":
454 for prev
in prev_parts
:
455 payload_bytes
= prev
.payload_bytes
456 sig_fps
= verify_detached_signature(piece
.string
, payload_bytes
, gpgme_ctx
)
459 piece
.gpg_data
= GPGData()
460 piece
.gpg_data
.sigs
= sig_fps
461 piece
.gpg_data
.plainobj
= prev
468 def prepare_for_reply (eddymsg_obj
, replyinfo_obj
):
470 do_to_eddys_pieces(prepare_for_reply_pieces
, eddymsg_obj
, replyinfo_obj
)
472 def prepare_for_reply_pieces (eddymsg_obj
, replyinfo_obj
):
474 for piece
in eddymsg_obj
.payload_pieces
:
475 if piece
.piece_type
== "text":
476 # don't quote the plaintext part.
479 elif piece
.piece_type
== "message":
480 prepare_for_reply_message(piece
, replyinfo_obj
)
482 elif piece
.piece_type
== "pubkey":
483 prepare_for_reply_pubkey(piece
, replyinfo_obj
)
485 elif (piece
.piece_type
== "clearsign") \
486 or (piece
.piece_type
== "detachedsig") \
487 or (piece
.piece_type
== "signature"):
488 prepare_for_reply_sig(piece
, replyinfo_obj
)
491 def prepare_for_reply_message (piece
, replyinfo_obj
):
493 if piece
.gpg_data
== None:
494 replyinfo_obj
.failed_decrypt
= True
497 replyinfo_obj
.success_decrypt
= True
499 # we already have a key (and a message)
500 if replyinfo_obj
.target_key
!= None:
503 if piece
.gpg_data
.sigs
!= []:
504 replyinfo_obj
.target_key
= piece
.gpg_data
.sigs
[0]
505 get_signed_part
= False
507 # only include a signed message in the reply.
508 get_signed_part
= True
510 replyinfo_obj
.msg_to_quote
= flatten_decrypted_payloads(piece
.gpg_data
.plainobj
, get_signed_part
)
512 # to catch public keys in encrypted blocks
513 prepare_for_reply(piece
.gpg_data
.plainobj
, replyinfo_obj
)
516 def prepare_for_reply_pubkey (piece
, replyinfo_obj
):
518 if piece
.gpg_data
== None or piece
.gpg_data
.keys
== []:
519 replyinfo_obj
.no_public_key
= True
521 replyinfo_obj
.public_key_received
= True
523 if replyinfo_obj
.fallback_target_key
== None:
524 replyinfo_obj
.fallback_target_key
= piece
.gpg_data
.keys
[0]
527 def prepare_for_reply_sig (piece
, replyinfo_obj
):
529 if piece
.gpg_data
== None or piece
.gpg_data
.sigs
== []:
530 replyinfo_obj
.sig_failure
= True
532 replyinfo_obj
.sig_success
= True
534 if replyinfo_obj
.fallback_target_key
== None:
535 replyinfo_obj
.fallback_target_key
= piece
.gpg_data
.sigs
[0]
539 def flatten_decrypted_payloads (eddymsg_obj
, get_signed_part
):
543 if eddymsg_obj
== None:
546 # recurse on multi-part mime
547 if eddymsg_obj
.multipart
== True:
548 for sub
in eddymsg_obj
.subparts
:
549 flat_string
+= flatten_decrypted_payloads (sub
, get_signed_part
)
553 for piece
in eddymsg_obj
.payload_pieces
:
554 if piece
.piece_type
== "text":
555 flat_string
+= piece
.string
557 if (get_signed_part
):
558 # don't include nested encryption
559 if (piece
.piece_type
== "message") \
560 and (piece
.gpg_data
!= None) \
561 and (piece
.gpg_data
.decrypted
== False):
562 flat_string
+= flatten_decrypted_payloads(piece
.gpg_data
.plainobj
, get_signed_part
)
564 elif ((piece
.piece_type
== "clearsign") \
565 or (piece
.piece_type
== "detachedsig") \
566 or (piece
.piece_type
== "signature")) \
567 and (piece
.gpg_data
!= None):
568 # FIXME: the key used to sign this message needs to be the one that is used for the encrypted reply.
569 flat_string
+= flatten_decrypted_payloads (piece
.gpg_data
.plainobj
, get_signed_part
)
574 def get_key_from_fp (replyinfo_obj
, gpgme_ctx
):
576 if replyinfo_obj
.target_key
== None:
577 replyinfo_obj
.target_key
= replyinfo_obj
.fallback_target_key
579 if replyinfo_obj
.target_key
!= None:
581 encrypt_to_key
= gpgme_ctx
.get_key(replyinfo_obj
.target_key
)
582 return encrypt_to_key
587 # no available key to use
588 replyinfo_obj
.target_key
= None
589 replyinfo_obj
.fallback_target_key
= None
591 replyinfo_obj
.no_public_key
= True
592 replyinfo_obj
.public_key_received
= False
597 def write_reply (replyinfo_obj
):
601 if replyinfo_obj
.success_decrypt
== True:
602 reply_plain
+= replyinfo_obj
.replies
['success_decrypt']
604 if replyinfo_obj
.no_public_key
== False:
605 quoted_text
= email_quote_text(replyinfo_obj
.msg_to_quote
)
606 reply_plain
+= quoted_text
608 elif replyinfo_obj
.failed_decrypt
== True:
609 reply_plain
+= replyinfo_obj
.replies
['failed_decrypt']
612 if replyinfo_obj
.sig_success
== True:
613 reply_plain
+= "\n\n"
614 reply_plain
+= replyinfo_obj
.replies
['sig_success']
616 elif replyinfo_obj
.sig_failure
== True:
617 reply_plain
+= "\n\n"
618 reply_plain
+= replyinfo_obj
.replies
['sig_failure']
621 if replyinfo_obj
.public_key_received
== True:
622 reply_plain
+= "\n\n"
623 reply_plain
+= replyinfo_obj
.replies
['public_key_received']
625 elif replyinfo_obj
.no_public_key
== True:
626 reply_plain
+= "\n\n"
627 reply_plain
+= replyinfo_obj
.replies
['no_public_key']
630 reply_plain
+= "\n\n"
631 reply_plain
+= replyinfo_obj
.replies
['signature']
636 def add_gpg_key (key_block
, gpgme_ctx
):
638 fp
= io
.BytesIO(key_block
.encode('ascii'))
640 result
= gpgme_ctx
.import_(fp
)
641 imports
= result
.imports
643 key_fingerprints
= []
646 for import_
in imports
:
647 fingerprint
= import_
[0]
648 key_fingerprints
+= [fingerprint
]
650 debug("added gpg key: " + fingerprint
)
652 return key_fingerprints
655 def verify_sig_message (msg_block
, gpgme_ctx
):
657 block_b
= io
.BytesIO(msg_block
.encode('ascii'))
658 plain_b
= io
.BytesIO()
661 sigs
= gpgme_ctx
.verify(block_b
, None, plain_b
)
665 plaintext
= plain_b
.getvalue().decode('utf-8')
669 fingerprints
+= [sig
.fpr
]
670 return (plaintext
, fingerprints
)
673 def verify_clear_signature (sig_block
, gpgme_ctx
):
675 # FIXME: this might require the un-decoded bytes
676 # or the correct re-encoding with the carset of the mime part.
677 msg_fp
= io
.BytesIO(sig_block
.encode('utf-8'))
678 ptxt_fp
= io
.BytesIO()
680 result
= gpgme_ctx
.verify(msg_fp
, None, ptxt_fp
)
682 # FIXME: this might require using the charset of the mime part.
683 plaintext
= ptxt_fp
.getvalue().decode('utf-8')
685 sig_fingerprints
= []
687 sig_fingerprints
+= [res_
.fpr
]
689 return plaintext
, sig_fingerprints
692 def verify_detached_signature (detached_sig
, plaintext_bytes
, gpgme_ctx
):
694 detached_sig_fp
= io
.BytesIO(detached_sig
.encode('ascii'))
695 plaintext_fp
= io
.BytesIO(plaintext_bytes
)
696 ptxt_fp
= io
.BytesIO()
698 result
= gpgme_ctx
.verify(detached_sig_fp
, plaintext_fp
, None)
700 sig_fingerprints
= []
702 sig_fingerprints
+= [res_
.fpr
]
704 return sig_fingerprints
707 def decrypt_block (msg_block
, gpgme_ctx
):
709 block_b
= io
.BytesIO(msg_block
.encode('ascii'))
710 plain_b
= io
.BytesIO()
713 sigs
= gpgme_ctx
.decrypt_verify(block_b
, plain_b
)
717 plaintext
= plain_b
.getvalue().decode('utf-8')
721 fingerprints
+= [sig
.fpr
]
722 return (plaintext
, fingerprints
)
725 def choose_reply_encryption_key (gpgme_ctx
, fingerprints
):
728 for fp
in fingerprints
:
730 key
= gpgme_ctx
.get_key(fp
)
732 if (key
.can_encrypt
== True):
742 def email_to_from_subject (email_text
):
744 email_struct
= email
.parser
.Parser().parsestr(email_text
)
746 email_to
= email_struct
['To']
747 email_from
= email_struct
['From']
748 email_subject
= email_struct
['Subject']
750 return email_to
, email_from
, email_subject
753 def import_lang(email_to
):
757 if "edward-" + lang
in email_to
:
758 lang
= "lang." + re
.sub('-', '_', lang
)
759 language
= importlib
.import_module(lang
)
763 return importlib
.import_module("lang.en")
766 def generate_encrypted_mime (plaintext
, email_from
, email_subject
, encrypt_to_key
,
769 # quoted printable encoding lets most ascii characters look normal
770 # before the mime message is decoded.
771 char_set
= email
.charset
.Charset("utf-8")
772 char_set
.body_encoding
= email
.charset
.QP
774 # MIMEText doesn't allow setting the text encoding
775 # so we use MIMENonMultipart.
776 plaintext_mime
= MIMENonMultipart('text', 'plain')
777 plaintext_mime
.set_payload(plaintext
, charset
=char_set
)
779 if (encrypt_to_key
!= None):
781 encrypted_text
= encrypt_sign_message(plaintext_mime
.as_string(),
785 control_mime
= MIMEApplication("Version: 1",
786 _subtype
='pgp-encrypted',
787 _encoder
=email
.encoders
.encode_7or8bit
)
788 control_mime
['Content-Description'] = 'PGP/MIME version identification'
789 control_mime
.set_charset('us-ascii')
791 encoded_mime
= MIMEApplication(encrypted_text
,
792 _subtype
='octet-stream; name="encrypted.asc"',
793 _encoder
=email
.encoders
.encode_7or8bit
)
794 encoded_mime
['Content-Description'] = 'OpenPGP encrypted message'
795 encoded_mime
['Content-Disposition'] = 'inline; filename="encrypted.asc"'
796 encoded_mime
.set_charset('us-ascii')
798 message_mime
= MIMEMultipart(_subtype
="encrypted", protocol
="application/pgp-encrypted")
799 message_mime
.attach(control_mime
)
800 message_mime
.attach(encoded_mime
)
801 message_mime
['Content-Disposition'] = 'inline'
804 message_mime
= plaintext_mime
806 message_mime
['To'] = email_from
807 message_mime
['Subject'] = email_subject
809 reply
= message_mime
.as_string()
814 def email_quote_text (text
):
816 quoted_message
= re
.sub(r
'^', r
'> ', text
, flags
=re
.MULTILINE
)
818 return quoted_message
821 def encrypt_sign_message (plaintext
, encrypt_to_key
, gpgme_ctx
):
823 plaintext_bytes
= io
.BytesIO(plaintext
.encode('ascii'))
824 encrypted_bytes
= io
.BytesIO()
826 gpgme_ctx
.encrypt_sign([encrypt_to_key
], gpgme
.ENCRYPT_ALWAYS_TRUST
,
827 plaintext_bytes
, encrypted_bytes
)
829 encrypted_txt
= encrypted_bytes
.getvalue().decode('ascii')
833 def error (error_msg
):
835 sys
.stderr
.write(progname
+ ": " + str(error_msg
) + "\n")
838 def debug (debug_msg
):
840 if edward_config
.debug
== True:
845 if __name__
== "__main__":
848 progname
= sys
.argv
[0]
850 if len(sys
.argv
) > 1:
851 print(progname
+ ": error, this program doesn't " \
852 "need any arguments.", file=sys
.stderr
)