def command(self): args = list(self.args) for q in self.data.get("q", []): args.extend(q.split()) g = GnuPG() return g.search_key(" ".join(args))
def get_vcards(self): if not self.config.active: return [] gnupg = GnuPG() keys = gnupg.list_keys() results = [] vcards = {} for key_id, key in keys.iteritems(): vcls = [VCardLine(name='KEY', value=self.VCL_KEY_FMT % key_id)] card = None emails = [] for uid in key.get('uids', []): if uid.get('email'): vcls.append(VCardLine(name='email', value=uid['email'])) card = card or vcards.get(uid['email']) emails.append(uid['email']) if uid.get('name'): name = uid['name'] vcls.append(VCardLine(name='fn', value=name)) if card and emails: card.add(*vcls) elif emails: # This is us taking care to only create one card for each # set of e-mail addresses. card = MailpileVCard(*vcls) for email in emails: vcards[email] = card results.append(card) return results
def command(self): args = self.args[:] for q in self.data.get('q', []): args.extend(q.split()) g = GnuPG() return g.search_key(" ".join(args))
def lookup_crypto_keys(session, address): x = {} scores = [] for handler in KEY_LOOKUP_HANDLERS: h = handler(session) r, s = h.lookup(address) for key, value in r.iteritems(): if key in x: x[key].update(value) else: x[key] = value x[key]["origin"] = [] x[key]["origin"].append(h.NAME) scores.append(s) for scoreset in scores: for key, value in scoreset.iteritems(): if key not in x: continue if "score" not in x[key]: x[key]["score"] = 0 x[key]["score"] += value g = GnuPG() known_keys_list = g.list_keys() for key in x.keys(): x[key]["fingerprint"] = key x[key]["score"] += crypto_keys_scorer(known_keys_list, key) x = [i for i in x.values()] x.sort(key=lambda k: -k["score"]) return x
def get_vcards(self): if not self.config.active: return [] gnupg = GnuPG() keys = gnupg.list_keys() results = [] vcards = {} for key in keys.values(): vcls = [VCardLine(name="KEY", value=self.VCL_KEY_FMT % key)] card = None emails = [] for uid in key["uids"]: if "email" in uid and uid["email"]: vcls.append(VCardLine(name="email", value=uid["email"])) card = card or vcards.get(uid['email']) emails.append(uid["email"]) if "name" in uid and uid["name"]: name = uid["name"] vcls.append(VCardLine(name="fn", value=name)) if card and emails: card.add(*vcls) elif emails: # This is us taking care to only create one card for each # set of e-mail addresses. card = SimpleVCard(*vcls) for email in emails: vcards[email] = card results.append(card) return results
def evaluate_pgp(self, tree, check_sigs=True, decrypt=False): if 'text_parts' not in tree: return tree pgpdata = [] for part in tree['text_parts']: if 'crypto' not in part: part['crypto'] = {} ei = si = None if check_sigs: if part['type'] == 'pgpbeginsigned': pgpdata = [part] elif part['type'] == 'pgpsignedtext': pgpdata.append(part) elif part['type'] == 'pgpsignature': pgpdata.append(part) try: gpg = GnuPG() message = ''.join([p['data'].encode(p['charset']) for p in pgpdata]) si = pgpdata[1]['crypto']['signature' ] = gpg.verify(message) pgpdata[0]['data'] = '' pgpdata[2]['data'] = '' except Exception, e: print e if decrypt: if part['type'] in ('pgpbegin', 'pgptext'): pgpdata.append(part) elif part['type'] == 'pgpend': pgpdata.append(part) gpg = GnuPG() (signature_info, encryption_info, text ) = gpg.decrypt(''.join([p['data'] for p in pgpdata])) # FIXME: If the data is binary, we should provide some # sort of download link or maybe leave the PGP # blob entirely intact, undecoded. text, charset = self.decode_text(text, binary=False) ei = pgpdata[1]['crypto']['encryption'] = encryption_info si = pgpdata[1]['crypto']['signature'] = signature_info if encryption_info["status"] == "decrypted": pgpdata[1]['data'] = text pgpdata[0]['data'] = "" pgpdata[2]['data'] = "" # Bubbling up! if (si or ei) and 'crypto' not in tree: tree['crypto'] = {'signature': SignatureInfo(), 'encryption': EncryptionInfo()} if si: tree['crypto']['signature'].mix(si) if ei: tree['crypto']['encryption'].mix(ei)
def command(self): keyid = self.data.get("keyid", self.args) g = GnuPG() res = [] for key in keyid: res.append(g.recv_key(key)) return res
def command(self): args = self.args[:] for q in self.data.get('terms', []): args.extend(q.split()) print "Querying PGP keyservers for: '%s'" % " ".join(args) g = GnuPG() return g.search_key(" ".join(args))
def _import_key(self, result, keytype): if keytype == "openpgp": g = GnuPG(self.config) res = g.import_keys(result[keytype]) if len(res["updated"]): self._managed_keys_add(result["address"], keytype) return res else: # We currently only support OpenPGP keys return False
def evaluate_pgp(self, tree, check_sigs=True, decrypt=False): if "text_parts" not in tree: return tree pgpdata = [] for part in tree["text_parts"]: if "crypto" not in part: part["crypto"] = {} ei = si = None if check_sigs: if part["type"] == "pgpbeginsigned": pgpdata = [part] elif part["type"] == "pgpsignedtext": pgpdata.append(part) elif part["type"] == "pgpsignature": pgpdata.append(part) try: gpg = GnuPG() message = "".join([p["data"].encode(p["charset"]) for p in pgpdata]) si = pgpdata[1]["crypto"]["signature"] = gpg.verify(message) pgpdata[0]["data"] = "" pgpdata[2]["data"] = "" except Exception, e: print e if decrypt: if part["type"] in ("pgpbegin", "pgptext"): pgpdata.append(part) elif part["type"] == "pgpend": pgpdata.append(part) gpg = GnuPG() (signature_info, encryption_info, text) = gpg.decrypt("".join([p["data"] for p in pgpdata])) # FIXME: If the data is binary, we should provide some # sort of download link or maybe leave the PGP # blob entirely intact, undecoded. text, charset = self.decode_text(text, binary=False) ei = pgpdata[1]["crypto"]["encryption"] = encryption_info si = pgpdata[1]["crypto"]["signature"] = signature_info if encryption_info["status"] == "decrypted": pgpdata[1]["data"] = text pgpdata[0]["data"] = "" pgpdata[2]["data"] = "" # Bubbling up! if (si or ei) and "crypto" not in tree: tree["crypto"] = {"signature": SignatureInfo(), "encryption": EncryptionInfo()} if si: tree["crypto"]["signature"].mix(si) if ei: tree["crypto"]["encryption"].mix(ei)
def _check_password(self, password, account=None, fingerprint=None): if account: # We're going to keep punting on this for a while... return True elif fingerprint: sps = SecurePassphraseStorage(password) gpg = GnuPG(self.session.config) status, sig = gpg.sign('OK', fromkey=fingerprint, passphrase=sps) return (status == 0) else: return True
def lookup_crypto_keys(session, address, event=None, allowremote=True): def _calc_scores(x, scores): for key in x.keys(): x[key]["score"] = 0 for scoreset in scores: for key, value in scoreset.iteritems(): if key not in x: continue if "score" not in x[key]: x[key]["score"] = 0 x[key]["score"] += value return x x = {} scores = [] lastresult = {} for handler in KEY_LOOKUP_HANDLERS: h = handler(session) if not allowremote and not h.LOCAL: continue if event: m = _calc_scores(x, scores) m = [i for i in m.values()] m.sort(key=lambda k: -k["score"]) event.private_data = {"result": m, "runningsearch": h.NAME} session.config.event_log.log_event(event) r, s = h.lookup(address) for key, value in r.iteritems(): if key in x: x[key].update(value) else: x[key] = value x[key]["origin"] = [] x[key]["origin"].append(h.NAME) scores.append(s) x = _calc_scores(x, scores) g = GnuPG() known_keys_list = g.list_keys() for key in x.keys(): x[key]["fingerprint"] = key x[key]["score"] += crypto_keys_scorer(known_keys_list, key) x = [i for i in x.values()] x.sort(key=lambda k: -k["score"]) if event: event.private_data = {"result": x, "runningsearch": None} session.config.event_log.log_event(event) return x
def _import_key(self, result, keytype): if keytype == "openpgp": g = GnuPG() if self.config: g.passphrase = self.config.gnupg_passphrase.get_reader() res = g.import_keys(result[keytype]) if len(res["updated"]): self._managed_keys_add(result["address"], keytype) return res else: # We currently only support OpenPGP keys return False
def command(self): key_data = "" if len(self.args) != 0: key_file = self.data.get("key_file", self.args[0]) with open(key_file) as file: key_data = file.read() if "key_data" in self.data: key_data = self.data.get("key_data") elif "key_file" in self.data: pass g = GnuPG() return g.import_keys(key_data)
def command(self): args = list(self.args) if len(args) >= 0: addr = args[0] else: addr = self.data.get("address", None) if addr is None: return self._error("Must supply e-mail address", None) g = GnuPG() res = g.address_to_keys(args[0]) return self._success("Searched for keys for e-mail address", res)
def _do_login(self, user, password, load_index=False, redirect=False): session, config = self.session, self.session.config session_id = self.session.ui.html_variables.get('http_session') # This prevents folks from sending us a DEFAULT user (upper case), # which is an internal security bypass below. user = user and user.lower() if not user: from mailpile.config import SecurePassphraseStorage sps = SecurePassphraseStorage(password) password = '' try: # Verify the passphrase gpg = GnuPG(use_agent=False) if gpg.is_available(): gpg.passphrase = sps.get_reader() assert(gpg.sign('Sign This!')[0] == 0) # Store the varified passphrase config.gnupg_passphrase.data = sps.data # Load the config and index, if necessary if not config.loaded_config: self._config() if load_index: self._idx() else: pass # FIXME: Start load in background session.ui.debug('Good passphrase for %s' % session_id) return self._logged_in(redirect=redirect) else: session.ui.debug('No GnuPG, checking DEFAULT user') # No GnuPG, see if there is a DEFAULT user in the config user = '******' except (AssertionError, IOError): session.ui.debug('Bad passphrase for %s' % session_id) return self._error(_('Invalid passphrase, please try again')) if user in config.logins or user == 'DEFAULT': # FIXME: Salt and hash the password, check if it matches # the entry in our user/password list (TODO). # NOTE: This hack effectively disables auth without GnUPG if user == 'DEFAULT': session.ui.debug('FIXME: Unauthorized login allowed') return self._logged_in(redirect=redirect) raise Exception('FIXME') self._error(_('Incorrect username or password'))
def TransformOutgoing(self, sender, rcpts, msg, **kwargs): matched = False keydata = mutual = sender_keyid = key_binary = None gnupg = GnuPG(self.config, event=GetThreadEvent()) profile = self._get_sender_profile(sender, kwargs) vcard = profile['vcard'] if vcard is not None: crypto_format = vcard.crypto_format sender_keyid = vcard.pgp_key if sender_keyid and 'autocrypt' in crypto_format: key_binary = gnupg.get_minimal_key(key_id=sender_keyid, user_id=sender) if key_binary: mutual = 'E' in crypto_format.split('+')[0].split(':')[-1] msg["Autocrypt"] = make_autocrypt_header( sender, key_binary, prefer_encrypt_mutual=mutual) if 'encrypt' in msg.get('Encryption', '').lower(): gossip_list = [] for rcpt in rcpts: # FIXME: Check if any of the recipients are in the BCC # header; omit their keys if so? try: # This *should* always succeed: if we are encrypting, # then the key we encrypt to should already be in # the keychain. if '#' in rcpt: rcpt, rcpt_keyid = rcpt.split('#') else: # This happens when composing in the CLI. rcpt_keyid = rcpt if (rcpt != sender) and rcpt_keyid: kb = gnupg.get_minimal_key(key_id=rcpt_keyid, user_id=rcpt) if kb: gossip_list.append(make_autocrypt_header( rcpt, kb, prefix='Autocrypt-Gossip')) except (ValueError, IndexError): pass if len(gossip_list) > 1: # No point gossiping peoples keys back to them alone. for hdr in gossip_list: msg.add_header('Autocrypt-Gossip', hdr) matched = True return sender, rcpts, msg, matched, True
def VerifyAndStorePassphrase(config, passphrase=None, sps=None): if passphrase and not sps: from mailpile.config import SecurePassphraseStorage sps = SecurePassphraseStorage(passphrase) passphrase = 'this probably does not really overwrite :-( ' assert(sps is not None) gpg = GnuPG(use_agent=False) if gpg.is_available(): gpg.passphrase = sps.get_reader() gpgr = config.prefs.gpg_recipient gpgr = gpgr if (gpgr not in (None, '', '!CREATE')) else None assert(gpg.sign('Sign This!', fromkey=gpgr)[0] == 0) return sps
def command(self): signingkey = None keyid = None args = list(self.args) try: keyid = args.pop(0) except: keyid = self.data.get("keyid", None) try: signingkey = args.pop(0) except: signingkey = self.data.get("signingkey", None) print keyid if not keyid: return self._error("You must supply a keyid", None) g = GnuPG() return g.sign_key(keyid, signingkey)
def decrypt_gpg(lines, fd): for line in fd: lines.append(line) if line.startswith(GPG_END_MESSAGE): break gpg = GnuPG() _, encryption_info, plaintext = gpg.decrypt(''.join(lines)) if encryption_info['status'] != 'decrypted': gpg_exec = distutils.spawn.find_executable('gpg') gpg_version = gpg.version() raise AccessError("GPG (version: %s, location: %s) was unable to decrypt the data: %s" % (gpg_version, gpg_exec, encryption_info['status'])) return plaintext.splitlines(True)
def command(self): if self.session.config.sys.lockdown: return self._error(_('In lockdown, doing nothing.')) signingkey = None keyid = None args = list(self.args) try: keyid = args.pop(0) except: keyid = self.data.get("keyid", None) try: signingkey = args.pop(0) except: signingkey = self.data.get("signingkey", None) print keyid if not keyid: return self._error("You must supply a keyid", None) g = GnuPG() return g.sign_key(keyid, signingkey)
def VerifyAndStorePassphrase(config, passphrase=None, sps=None, key=None): if passphrase and not sps: from mailpile.config import SecurePassphraseStorage sps = SecurePassphraseStorage(passphrase) passphrase = 'this probably does not really overwrite :-( ' assert(sps is not None) gpg = GnuPG(None, use_agent=False) if gpg.is_available(): gpg.passphrase = sps.get_reader() gpgr = config.prefs.gpg_recipient gpgr = key or (gpgr if (gpgr not in (None, '', '!CREATE')) else None) assert(gpg.sign('Sign This!', fromkey=gpgr)[0] == 0) # Fun side effect: changing the passphrase invalidates the message cache import mailpile.mailutils mailpile.mailutils.ClearParseCache(full=True) return sps
def scorer_trust(key): global known_keys_list score = 0 if known_keys_list == None: g = GnuPG() known_keys_list = g.list_keys() if key in known_keys_list: if "e" in known_keys_list[key]["validity"]: score += -100 elif "r" in known_keys_list[key]["validity"]: score += -10000 elif "d" in known_keys_list[key]["validity"]: score += -10000 elif ("f" in known_keys_list[key]["validity"] or "u" in known_keys_list[key]["validity"]): score += 50 else: score += 10 return score
def _getkey(self, key): if key["fingerprint"] and not key["url"]: g = GnuPG() res = g.recv_key(key["fingerprint"]) elif key["url"]: r = urllib2.urlopen(key["url"]) result = r.readlines() start = 0 end = len(result) # Hack to deal with possible HTML results from keyservers: for i in range(len(result)): if result[i].startswith("-----BEGIN PGP"): start = i elif result[i].startswith("-----END PGP"): end = i result = "".join(result[start:end]) g = GnuPG() res = g.import_keys(result) return res else: raise ValueError("Need a fingerprint or a URL")
def command(self): session, config, idx = self.session, self.session.config, self._idx() args = list(self.args) if args and args[-1][0] == "#": attid = args.pop() else: attid = self.data.get("att", 'application/pgp-keys') args.extend(["=%s" % x for x in self.data.get("mid", [])]) eids = self._choose_messages(args) if len(eids) < 0: return self._error("No messages selected", None) elif len(eids) > 1: return self._error("One message at a time, please", None) email = Email(idx, list(eids)[0]) fn, attr = email.extract_attachment(session, attid, mode='inline') if attr and attr["data"]: g = GnuPG() res = g.import_keys(attr["data"]) return self._success("Imported key", res) return self._error("No results found", None)
def _getkey(self, entry): pkaver = None fingerprint = None url = None for stmt in entry.split(";"): key, value = stmt.split("=", 1) if key == "v": pkaver = value elif key == "fpr": fingerprint = value elif key == "uri": url = value if pkaver != "pka1": raise ValueError("We only know how to deal with pka version 1") if fingerprint and not url: g = GnuPG() res = g.recv_key(fingerprint) elif url: r = urllib2.urlopen(url) result = r.readlines() start = 0 end = len(result) # Hack to deal with possible HTML results from keyservers: for i in range(len(result)): if result[i].startswith("-----BEGIN PGP"): start = i elif result[i].startswith("-----END PGP"): end = i result = "".join(result[start:end]) g = GnuPG() res = g.import_keys(result) return res else: raise ValueError("Need a fingerprint or a URL")
def lookup_crypto_keys(session, address, event=None, strict_email_match=False, allowremote=True, origins=None, get=None, vcard=None, only_good=False, pin_key=False, known_keys_list=None): config = (session and session.config) found_keys = {} ordered_keys = [] known_keys_list = known_keys_list or _mailpile_key_list( GnuPG(config or None).list_keys()) if origins: handlers = [ h for h in KEY_LOOKUP_HANDLERS if (h.NAME in origins) or ( h.NAME.lower() in origins) or (h.SHORTNAME in origins) ] else: handlers = KEY_LOOKUP_HANDLERS ungotten = get and get[:] or [] progress = [] # If the user has disabled "web content", only allow local requests if config and allowremote and config.prefs.web_content == 'off': if ('keylookup' in config.sys.debug or 'keytofu' in config.sys.debug): session.ui.debug( "Remote key lookups disabled by prefs.web_content.") allowremote = False for handler in handlers: if get and not ungotten: # We have all the keys! break try: h = handler(session, known_keys_list) if not allowremote and not h.LOCAL: continue if allowremote and config and config.prefs.web_content == 'anon': if (config.sys.proxy.protocol not in ('tor', 'tor-risky') or not h.PRIVACY_FRIENDLY): if ('keylookup' in config.sys.debug or 'keytofu' in config.sys.debug): session.ui.debug( "Origin %s disabled by prefs.web_content" % h.NAME) continue if found_keys and (not h.PRIVACY_FRIENDLY) and (not origins): # We only try the privacy-hostile methods if we haven't # found any keys (unless origins were specified). if not ungotten: continue progress.append(h.NAME) if event and config: ordered_keys.sort(key=lambda k: -k["score"]) event.message = _('Searching for encryption keys in: %s') % _( h.NAME) event.private_data = { "result": ordered_keys, "progress": progress, "runningsearch": h.NAME } config.event_log.log_event(event) # We allow for more time when importing keys timeout = h.TIMEOUT if ungotten: timeout *= 4 # h.lookup will remove found keys from the wanted list, # but we have to watch out for the effects of timeouts. wanted = ungotten[:] results = RunTimed(timeout, h.lookup, address, strict_email_match=strict_email_match, get=(wanted if (get is not None) else None)) ungotten[:] = wanted except KeyboardInterrupt: raise except: if config and config.sys.debug: traceback.print_exc() results = {} # FIXME: This merging of info about keys is probably misguided. for key_id, key_info in results.iteritems(): if key_id in found_keys: old_scores = found_keys[key_id].scores old_uids = found_keys[key_id].uids found_keys[key_id].update(key_info) found_keys[key_id].scores.update(old_scores) # Merge in the old UIDs uid_emails = [u.email for u in key_info.uids] for uid in old_uids: email = uid.email if email and email not in uid_emails: found_keys[key_id].uids.append(uid) else: found_keys[key_id] = key_info found_keys[key_id].origins.append(h.NAME) for key_id in found_keys.keys(): _normalize_key(session, found_keys[key_id]) _update_scores(session, key_id, found_keys[key_id], known_keys_list) # This updates and sorts ordered_keys in place. This will magically # also update the data on the viewable event, because Python. ordered_keys[:] = found_keys.values() ordered_keys.sort(key=lambda k: -k.score) if only_good: ordered_keys = [k for k in ordered_keys if k.score > 0] if get and vcard and ordered_keys: with vcard: vcard.pgp_key = ordered_keys[0].fingerprint vcard.pgp_key_pinned = 'true' if pin_key else 'false' vcard.save() ordered_keys[0].is_preferred = True ordered_keys[0].is_pinned = pin_key for k in ordered_keys[1:]: k.is_preferred = k.is_pinned = False if event and config: event.private_data = {"result": ordered_keys, "runningsearch": False} config.event_log.log_event(event) return ordered_keys
def command(self): keyid = self.data.get("keyid", self.args[0]) g = GnuPG() return g.recv_key(keyid)
def PrepareMessage(config, msg, sender=None, rcpts=None, events=None): msg = copy.deepcopy(msg) # Short circuit if this message has already been prepared. if 'x-mp-internal-sender' in msg and 'x-mp-internal-rcpts' in msg: return (sender or msg['x-mp-internal-sender'], rcpts or [r.strip() for r in msg['x-mp-internal-rcpts'].split(',')], msg, events) crypto_policy = config.prefs.crypto_policy.lower() rcpts = rcpts or [] # Iterate through headers to figure out what we want to do... need_rcpts = not rcpts for hdr, val in msg.items(): lhdr = hdr.lower() if lhdr == 'from': sender = sender or val elif lhdr == 'encryption': crypto_policy = val.lower() elif need_rcpts and lhdr in ('to', 'cc', 'bcc'): rcpts += ExtractEmails(val, strip_keys=False) # Are we sane? if not sender: raise NoFromAddressError() if not rcpts: raise NoRecipientError() # Are we encrypting? Signing? if crypto_policy == 'default': crypto_policy = config.prefs.crypto_policy # This is the BCC hack that Brennan hates! rcpts += [sender] sender = ExtractEmails(sender, strip_keys=False)[0] sender_keyid = None if config.prefs.openpgp_header: try: gnupg = GnuPG() seckeys = dict([(x["email"], y["fingerprint"]) for y in gnupg.list_secret_keys().values() for x in y["uids"]]) sender_keyid = seckeys[sender] except: pass rcpts, rr = [sender], rcpts for r in rr: for e in ExtractEmails(r, strip_keys=False): if e not in rcpts: rcpts.append(e) # Add headers we require if 'date' not in msg: msg['Date'] = email.utils.formatdate() if sender_keyid and config.prefs.openpgp_header: msg["OpenPGP"] = "id=%s; preference=%s" % (sender_keyid, config.prefs.openpgp_header) if 'openpgp' in crypto_policy: # FIXME: Make a more efficient sign+encrypt wrapper cleaner = lambda m: CleanMessage(config, m) if 'sign' in crypto_policy: msg = OpenPGPMimeSigningWrapper(config, sender=sender, cleaner=cleaner, recipients=rcpts).wrap(msg) if 'encrypt' in crypto_policy: msg = OpenPGPMimeEncryptingWrapper(config, sender=sender, cleaner=cleaner, recipients=rcpts).wrap(msg) rcpts = set([r.rsplit('#', 1)[0] for r in rcpts]) msg['x-mp-internal-readonly'] = str(int(time.time())) msg['x-mp-internal-sender'] = sender msg['x-mp-internal-rcpts'] = ', '.join(rcpts) return (sender, rcpts, msg, events)
def evaluate_pgp(self, tree, check_sigs=True, decrypt=False): if 'text_parts' not in tree: return tree pgpdata = [] for part in tree['text_parts']: if 'crypto' not in part: part['crypto'] = {} ei = si = None if check_sigs: if part['type'] == 'pgpbeginsigned': pgpdata = [part] elif part['type'] == 'pgpsignedtext': pgpdata.append(part) elif part['type'] == 'pgpsignature': pgpdata.append(part) try: gpg = GnuPG() message = ''.join( [p['data'].encode(p['charset']) for p in pgpdata]) si = pgpdata[1]['crypto']['signature'] = gpg.verify( message) pgpdata[0]['data'] = '' pgpdata[2]['data'] = '' except Exception, e: print e if decrypt: if part['type'] in ('pgpbegin', 'pgptext'): pgpdata.append(part) elif part['type'] == 'pgpend': pgpdata.append(part) gpg = GnuPG() (signature_info, encryption_info, text) = gpg.decrypt(''.join([p['data'] for p in pgpdata])) # FIXME: If the data is binary, we should provide some # sort of download link or maybe leave the PGP # blob entirely intact, undecoded. text, charset = self.decode_text(text, binary=False) ei = pgpdata[1]['crypto']['encryption'] = encryption_info si = pgpdata[1]['crypto']['signature'] = signature_info if encryption_info["status"] == "decrypted": pgpdata[1]['data'] = text pgpdata[0]['data'] = "" pgpdata[2]['data'] = "" # Bubbling up! if (si or ei) and 'crypto' not in tree: tree['crypto'] = { 'signature': SignatureInfo(), 'encryption': EncryptionInfo() } if si: tree['crypto']['signature'].mix(si) if ei: tree['crypto']['encryption'].mix(ei)
def PrepareMessage(config, msg, sender=None, rcpts=None): msg = copy.deepcopy(msg) # Short circuit if this message has already been prepared. if 'x-mp-internal-sender' in msg and 'x-mp-internal-rcpts' in msg: return (sender or msg['x-mp-internal-sender'], rcpts or [r.strip() for r in msg['x-mp-internal-rcpts'].split(',')], msg) crypto_policy = config.prefs.crypto_policy.lower() rcpts = rcpts or [] # Iterate through headers to figure out what we want to do... need_rcpts = not rcpts for hdr, val in msg.items(): lhdr = hdr.lower() if lhdr == 'from': sender = sender or val elif lhdr == 'encryption': crypto_policy = val.lower() elif need_rcpts and lhdr in ('to', 'cc', 'bcc'): rcpts += ExtractEmails(val, strip_keys=False) # Are we sane? if not sender: raise NoFromAddressError() if not rcpts: raise NoRecipientError() # Are we encrypting? Signing? if crypto_policy == 'default': crypto_policy = config.prefs.crypto_policy # This is the BCC hack that Brennan hates! rcpts += [sender] sender = ExtractEmails(sender, strip_keys=False)[0] sender_keyid = None if config.prefs.openpgp_header: try: gnupg = GnuPG() seckeys = dict([(x["email"], y["fingerprint"]) for y in gnupg.list_secret_keys().values() for x in y["uids"]]) sender_keyid = seckeys[sender] except: pass rcpts, rr = [sender], rcpts for r in rr: for e in ExtractEmails(r, strip_keys=False): if e not in rcpts: rcpts.append(e) # Add headers we require if 'date' not in msg: msg['Date'] = email.utils.formatdate() if sender_keyid and config.prefs.openpgp_header: msg["OpenPGP"] = "id=%s; preference=%s" % (sender_keyid, config.prefs.openpgp_header) if 'openpgp' in crypto_policy: # FIXME: Make a more efficient sign+encrypt wrapper cleaner = lambda m: CleanMessage(config, m) if 'sign' in crypto_policy: msg = OpenPGPMimeSigningWrapper(config, sender=sender, cleaner=cleaner, recipients=rcpts).wrap(msg) if 'encrypt' in crypto_policy: msg = OpenPGPMimeEncryptingWrapper(config, sender=sender, cleaner=cleaner, recipients=rcpts).wrap(msg) rcpts = set([r.rsplit('#', 1)[0] for r in rcpts]) msg['x-mp-internal-readonly'] = str(int(time.time())) msg['x-mp-internal-sender'] = sender msg['x-mp-internal-rcpts'] = ', '.join(rcpts) return (sender, rcpts, msg)
def _gnupg(self, **kwargs): return GnuPG(self.session.config, event=self.event, **kwargs)
def TransformOutgoing(self, sender, rcpts, msg, **kwargs): matched = False gnupg = None sender_keyid = None # Prefer to just get everything from the profile VCard, in the # common case... profile = self.config.vcards.get_vcard(sender) if profile: sender_keyid = profile.pgp_key crypto_format = profile.crypto_format or 'none' else: crypto_format = 'none' # Parse the openpgp_header data from the crypto_format openpgp_header = [ p.split(':')[-1] for p in crypto_format.split('+') if p.startswith('openpgp_header:') ] if not openpgp_header: openpgp_header = self.config.prefs.openpgp_header and ['CFG'] if openpgp_header[0] != 'N' and not sender_keyid: # This is a fallback: this shouldn't happen much in normal use try: gnupg = gnupg or GnuPG(self.config) seckeys = dict([ (uid["email"], fp) for fp, key in gnupg.list_secret_keys().iteritems() if key["capabilities_map"].get("encrypt") and key["capabilities_map"].get("sign") for uid in key["uids"] ]) sender_keyid = seckeys.get(sender) except (KeyError, TypeError, IndexError, ValueError): traceback.print_exc() if sender_keyid and openpgp_header: preference = { 'ES': 'signencrypt', 'SE': 'signencrypt', 'E': 'encrypt', 'S': 'sign', 'N': 'unprotected', 'CFG': self.config.prefs.openpgp_header }[openpgp_header[0].upper()] msg["OpenPGP"] = ("id=%s; preference=%s" % (sender_keyid, preference)) if ('attach-pgp-pubkey' in msg and msg['attach-pgp-pubkey'][:3].lower() in ('yes', 'tru')): gnupg = gnupg or GnuPG(self.config) if sender_keyid: keys = gnupg.list_keys(selectors=[sender_keyid]) else: keys = gnupg.address_to_keys(ExtractEmails(sender)[0]) key_count = 0 for fp, key in keys.iteritems(): if not any(key["capabilities_map"].values()): continue # We should never really hit this more than once. But if we # do, should still be fine. keyid = key["keyid"] data = gnupg.get_pubkey(keyid) try: from_name = key["uids"][0]["name"] filename = _('Encryption key for %s.asc') % from_name except: filename = _('My encryption key.asc') att = MIMEBase('application', 'pgp-keys') att.set_payload(data) encoders.encode_base64(att) del att['MIME-Version'] att.add_header('Content-Id', MakeContentID()) att.add_header('Content-Disposition', 'attachment', filename=filename) att.signature_info = SignatureInfo(parent=msg.signature_info) att.encryption_info = EncryptionInfo( parent=msg.encryption_info) msg.attach(att) key_count += 1 if key_count > 0: msg['x-mp-internal-pubkeys-attached'] = "Yes" return sender, rcpts, msg, matched, True
def command(self): session = self.session if session.config.sys.lockdown: return self._error(_('In lockdown, doing nothing.')) # Perform any required migrations Migrate(session).run() # Create local mailboxes session.config.open_local_mailbox(session) # Create standard tags and filters created = [] for t in self.TAGS: if not session.config.get_tag_id(t): AddTag(session, arg=[t]).run(save=False) created.append(t) session.config.get_tag(t).update(self.TAGS[t]) for stype, statuses in (('sig', SignatureInfo.STATUSES), ('enc', EncryptionInfo.STATUSES)): for status in statuses: tagname = 'mp_%s-%s' % (stype, status) if not session.config.get_tag_id(tagname): AddTag(session, arg=[tagname]).run(save=False) created.append(tagname) session.config.get_tag(tagname).update({ 'type': 'attribute', 'display': 'invisible', 'label': False, }) if 'New' in created: Filter(session, arg=[ 'new', '@incoming', '+Inbox', '+New', 'Incoming mail filter' ]).run(save=False) session.ui.notify(_('Created default tags')) # Import all the basic plugins for plugin in PLUGINS: if plugin not in session.config.sys.plugins: session.config.sys.plugins.append(plugin) try: # If spambayes is not installed, this will fail import mailpile.plugins.autotag_sb if 'autotag_sb' not in session.config.sys.plugins: session.config.sys.plugins.append('autotag_sb') session.ui.notify(_('Enabling spambayes autotagger')) except ImportError: session.ui.warning( _('Please install spambayes ' 'for super awesome spam filtering')) session.config.save() session.config.load(session) vcard_importers = session.config.prefs.vcard.importers if not vcard_importers.gravatar: vcard_importers.gravatar.append({'active': True}) session.ui.notify(_('Enabling gravatar image importer')) gpg_home = os.path.expanduser('~/.gnupg') if os.path.exists(gpg_home) and not vcard_importers.gpg: vcard_importers.gpg.append({'active': True, 'gpg_home': gpg_home}) session.ui.notify(_('Importing contacts from GPG keyring')) if ('autotag_sb' in session.config.sys.plugins and len(session.config.prefs.autotag) == 0): session.config.prefs.autotag.append({ 'match_tag': 'spam', 'unsure_tag': 'maybespam', 'tagger': 'spambayes', 'trainer': 'spambayes' }) session.config.prefs.autotag[0].exclude_tags[0] = 'ham' # Assumption: If you already have secret keys, you want to # use the associated addresses for your e-mail. # If you don't already have secret keys, you should have # one made for you, if GnuPG is available. # If GnuPG is not available, you should be warned. gnupg = GnuPG() if gnupg.is_available(): keys = gnupg.list_secret_keys() if len(keys) == 0: # FIXME: Start background process generating a key once a user # has supplied a name and e-mail address. pass else: for key, details in keys.iteritems(): # Ignore revoked/expired keys. if ("revocation-date" in details and details["revocation-date"] <= date.today().strftime("%Y-%m-%d")): continue for uid in details["uids"]: if "email" not in uid or uid["email"] == "": continue if uid["email"] in [ x["email"] for x in session.config.profiles ]: # Don't set up the same e-mail address twice. continue # FIXME: Add route discovery mechanism. profile = { "email": uid["email"], "name": uid["name"], } session.config.profiles.append(profile) if not session.config.prefs.gpg_recipient: session.config.prefs.gpg_recipient = key session.ui.notify(_('Encrypting config to %s') % key) if session.config.prefs.crypto_policy == 'none': session.config.prefs.crypto_policy = 'openpgp-sign' else: session.ui.warning(_('Oh no, PGP/GPG support is unavailable!')) if (session.config.prefs.gpg_recipient and not (self._idx() and self._idx().INDEX) and not session.config.prefs.obfuscate_index): randcrap = sha512b64( open('/dev/urandom').read(1024), session.config.prefs.gpg_recipient, '%s' % time.time()) session.config.prefs.obfuscate_index = randcrap session.config.prefs.index_encrypted = True session.ui.notify( _('Obfuscating search index and enabling ' 'indexing of encrypted e-mail. ')) session.config.save() return self._success(_('Performed initial Mailpile setup'))
def setup_command(self, session, do_gpg_stuff=False): do_gpg_stuff = do_gpg_stuff or ('do_gpg_stuff' in self.args) # Stop the workers... want_daemons = session.config.cron_worker is not None session.config.stop_workers() # Perform any required migrations Migrate(session).run(before_setup=True, after_setup=False) # Basic app config, tags, plugins, etc. self.basic_app_config(session, save_and_update_workers=False, want_daemons=want_daemons) # Assumption: If you already have secret keys, you want to # use the associated addresses for your e-mail. # If you don't already have secret keys, you should have # one made for you, if GnuPG is available. # If GnuPG is not available, you should be warned. if do_gpg_stuff: gnupg = GnuPG() accepted_keys = [] if gnupg.is_available(): keys = gnupg.list_secret_keys() for key, details in keys.iteritems(): # Ignore revoked/expired keys. if ("revocation-date" in details and details["revocation-date"] <= date.today().strftime("%Y-%m-%d")): continue accepted_keys.append(key) for uid in details["uids"]: if "email" not in uid or uid["email"] == "": continue if uid["email"] in [x["email"] for x in session.config.profiles]: # Don't set up the same e-mail address twice. continue # FIXME: Add route discovery mechanism. profile = { "email": uid["email"], "name": uid["name"], } session.config.profiles.append(profile) if (session.config.prefs.gpg_recipient in (None, '', '!CREATE') and details["capabilities_map"][0]["encrypt"]): session.config.prefs.gpg_recipient = key session.ui.notify(_('Encrypting config to %s') % key) if session.config.prefs.crypto_policy == 'none': session.config.prefs.crypto_policy = 'openpgp-sign' if len(accepted_keys) == 0: # FIXME: Start background process generating a key once a user # has supplied a name and e-mail address. pass else: session.ui.warning(_('Oh no, PGP/GPG support is unavailable!')) # If we have a GPG key, but no master key, create it self.make_master_key() # Perform any required migrations Migrate(session).run(before_setup=False, after_setup=True) session.config.save() session.config.prepare_workers(session, daemons=want_daemons) return self._success(_('Performed initial Mailpile setup'))
def lookup_crypto_keys(session, address, event=None, strict_email_match=False, allowremote=True, origins=None, get=None): known_keys_list = GnuPG(session and session.config or None).list_keys() found_keys = {} ordered_keys = [] if origins: handlers = [ h for h in KEY_LOOKUP_HANDLERS if (h.NAME in origins) or (h.NAME.lower() in origins) ] else: handlers = KEY_LOOKUP_HANDLERS ungotten = get and get[:] or [] progress = [] for handler in handlers: if get and not ungotten: # We have all the keys! break try: h = handler(session, known_keys_list) if not allowremote and not h.LOCAL: continue if found_keys and (not h.PRIVACY_FRIENDLY) and (not origins): # We only try the privacy-hostile methods if we haven't # found any keys (unless origins were specified). if not ungotten: continue progress.append(h.NAME) if event: ordered_keys.sort(key=lambda k: -k["score"]) event.message = _('Searching for encryption keys in: %s') % _( h.NAME) event.private_data = { "result": ordered_keys, "progress": progress, "runningsearch": h.NAME } session.config.event_log.log_event(event) # We allow for more time when importing keys timeout = h.TIMEOUT if ungotten: timeout *= 4 # h.lookup will remove found keys from the wanted list, # but we have to watch out for the effects of timeouts. wanted = ungotten[:] results = RunTimed(timeout, h.lookup, address, strict_email_match=strict_email_match, get=(wanted if (get is not None) else None)) ungotten[:] = wanted except KeyboardInterrupt: raise except: if session.config.sys.debug: traceback.print_exc() results = {} for key_id, key_info in results.iteritems(): if key_id in found_keys: old_scores = found_keys[key_id].get('scores', {}) old_uids = found_keys[key_id].get('uids', [])[:] found_keys[key_id].update(key_info) if 'scores' in found_keys[key_id]: found_keys[key_id]['scores'].update(old_scores) # No need for an else, as old_scores will be empty # Merge in the old UIDs uid_emails = [u['email'] for u in key_info.get('uids', [])] if 'uids' not in found_keys[key_id]: found_keys[key_id]['uids'] = [] for uid in old_uids: email = uid.get('email') if email and email not in uid_emails: found_keys[key_id]['uids'].append(uid) else: found_keys[key_id] = key_info found_keys[key_id]["origins"] = [] found_keys[key_id]["origins"].append(h.NAME) _update_scores(session, key_id, found_keys[key_id], known_keys_list) _normalize_key(session, found_keys[key_id]) # This updates and sorts ordered_keys in place. This will magically # also update the data on the viewable event, because Python. ordered_keys[:] = found_keys.values() ordered_keys.sort(key=lambda k: -k["score"]) if event: event.private_data = {"result": ordered_keys, "runningsearch": False} session.config.event_log.log_event(event) return ordered_keys
def _gnupg(self): return GnuPG(self.session and self.session.config or None)
def setup_command(self, session): # Stop the workers... want_daemons = session.config.cron_worker is not None session.config.stop_workers() # Perform any required migrations Migrate(session).run(before_setup=True, after_setup=False) # Create local mailboxes session.config.open_local_mailbox(session) # Create standard tags and filters created = [] for t in self.TAGS: if not session.config.get_tag_id(t): AddTag(session, arg=[t]).run(save=False) created.append(t) session.config.get_tag(t).update(self.TAGS[t]) for stype, statuses in (('sig', SignatureInfo.STATUSES), ('enc', EncryptionInfo.STATUSES)): for status in statuses: tagname = 'mp_%s-%s' % (stype, status) if not session.config.get_tag_id(tagname): AddTag(session, arg=[tagname]).run(save=False) created.append(tagname) session.config.get_tag(tagname).update({ 'type': 'attribute', 'display': 'invisible', 'label': False, }) if 'New' in created: session.ui.notify(_('Created default tags')) # Import all the basic plugins for plugin in PLUGINS: if plugin not in session.config.sys.plugins: session.config.sys.plugins.append(plugin) try: # If spambayes is not installed, this will fail import mailpile.plugins.autotag_sb if 'autotag_sb' not in session.config.sys.plugins: session.config.sys.plugins.append('autotag_sb') session.ui.notify(_('Enabling spambayes autotagger')) except ImportError: session.ui.warning(_('Please install spambayes ' 'for super awesome spam filtering')) session.config.save() session.config.load(session) vcard_importers = session.config.prefs.vcard.importers if not vcard_importers.gravatar: vcard_importers.gravatar.append({'active': True}) session.ui.notify(_('Enabling gravatar image importer')) gpg_home = os.path.expanduser('~/.gnupg') if os.path.exists(gpg_home) and not vcard_importers.gpg: vcard_importers.gpg.append({'active': True, 'gpg_home': gpg_home}) session.ui.notify(_('Importing contacts from GPG keyring')) if ('autotag_sb' in session.config.sys.plugins and len(session.config.prefs.autotag) == 0): session.config.prefs.autotag.append({ 'match_tag': 'spam', 'unsure_tag': 'maybespam', 'tagger': 'spambayes', 'trainer': 'spambayes' }) session.config.prefs.autotag[0].exclude_tags[0] = 'ham' # Assumption: If you already have secret keys, you want to # use the associated addresses for your e-mail. # If you don't already have secret keys, you should have # one made for you, if GnuPG is available. # If GnuPG is not available, you should be warned. gnupg = GnuPG() accepted_keys = [] if gnupg.is_available(): keys = gnupg.list_secret_keys() for key, details in keys.iteritems(): # Ignore revoked/expired keys. if ("revocation-date" in details and details["revocation-date"] <= date.today().strftime("%Y-%m-%d")): continue accepted_keys.append(key) for uid in details["uids"]: if "email" not in uid or uid["email"] == "": continue if uid["email"] in [x["email"] for x in session.config.profiles]: # Don't set up the same e-mail address twice. continue # FIXME: Add route discovery mechanism. profile = { "email": uid["email"], "name": uid["name"], } session.config.profiles.append(profile) if (not session.config.prefs.gpg_recipient and details["capabilities_map"][0]["encrypt"]): session.config.prefs.gpg_recipient = key session.ui.notify(_('Encrypting config to %s') % key) if session.config.prefs.crypto_policy == 'none': session.config.prefs.crypto_policy = 'openpgp-sign' if len(accepted_keys) == 0: # FIXME: Start background process generating a key once a user # has supplied a name and e-mail address. pass else: session.ui.warning(_('Oh no, PGP/GPG support is unavailable!')) if (session.config.prefs.gpg_recipient and not (self._idx() and self._idx().INDEX) and not session.config.prefs.obfuscate_index): randcrap = sha512b64(open('/dev/urandom').read(1024), session.config.prefs.gpg_recipient, '%s' % time.time()) session.config.prefs.obfuscate_index = randcrap session.config.prefs.index_encrypted = True session.ui.notify(_('Obfuscating search index and enabling ' 'indexing of encrypted e-mail. ')) # Perform any required migrations Migrate(session).run(before_setup=False, after_setup=True) session.config.save() session.config.prepare_workers(session, daemons=want_daemons) return self._success(_('Performed initial Mailpile setup'))
def _gnupg(self): return GnuPG(self.session.config, event=self.event)
def setup_command(self, session, do_gpg_stuff=False): do_gpg_stuff = do_gpg_stuff or ('do_gpg_stuff' in self.args) # Stop the workers... want_daemons = session.config.cron_worker is not None session.config.stop_workers() # Perform any required migrations Migrate(session).run(before_setup=True, after_setup=False) # Basic app config, tags, plugins, etc. self.basic_app_config(session, save_and_update_workers=False, want_daemons=want_daemons) # Assumption: If you already have secret keys, you want to # use the associated addresses for your e-mail. # If you don't already have secret keys, you should have # one made for you, if GnuPG is available. # If GnuPG is not available, you should be warned. if do_gpg_stuff: gnupg = GnuPG(None) accepted_keys = [] if gnupg.is_available(): keys = gnupg.list_secret_keys() for key, details in keys.iteritems(): # Ignore revoked/expired keys. if ("revocation-date" in details and details["revocation-date"] <= date.today().strftime("%Y-%m-%d")): continue accepted_keys.append(key) for uid in details["uids"]: if "email" not in uid or uid["email"] == "": continue if uid["email"] in [x["email"] for x in session.config.profiles]: # Don't set up the same e-mail address twice. continue # FIXME: Add route discovery mechanism. profile = { "email": uid["email"], "name": uid["name"], } session.config.profiles.append(profile) if (session.config.prefs.gpg_recipient in (None, '', '!CREATE') and details["capabilities_map"][0]["encrypt"]): session.config.prefs.gpg_recipient = key session.ui.notify(_('Encrypting config to %s') % key) if session.config.prefs.crypto_policy == 'none': session.config.prefs.crypto_policy = 'openpgp-sign' if len(accepted_keys) == 0: # FIXME: Start background process generating a key once a user # has supplied a name and e-mail address. pass else: session.ui.warning(_('Oh no, PGP/GPG support is unavailable!')) # If we have a GPG key, but no master key, create it self.make_master_key() # Perform any required migrations Migrate(session).run(before_setup=False, after_setup=True) session.config.save() session.config.prepare_workers(session, daemons=want_daemons) return self._success(_('Performed initial Mailpile setup'))