コード例 #1
0
 def _create_new_key(self, vcard, keytype):
     passphrase = okay_random(26, self.session.config.master_key
                              ).lower()
     random_uid = vcard.random_uid
     bits = int(keytype.replace('RSA', ''))
     key_args = {
         # FIXME: EC keys!
         'bits': bits,
         'name': vcard.fn,
         'email': vcard.email,
         'passphrase': passphrase,
         'comment': ''
     }
     event = Event(source=self,
                   message=_('Generating new %d bit PGP key. '
                             'This may take some time!') % bits,
                   flags=Event.INCOMPLETE,
                   data={'keygen_started': int(time.time()),
                         'profile_id': random_uid},
                   private_data=key_args)
     self._key_generator = GnuPGKeyGenerator(
        # FIXME: Passphrase handling is a problem here
        variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args),
        on_complete=(random_uid,
                     lambda: self._new_key_created(event, random_uid,
                                                   passphrase))
     )
     self._key_generator.start()
     self.session.config.event_log.log_event(event)
コード例 #2
0
ファイル: contacts.py プロジェクト: jean/Mailpile
 def _create_new_key(self, vcard, keytype):
     passphrase = okay_random(26, self.session.config.master_key
                              ).lower()
     random_uid = vcard.random_uid
     bits = int(keytype.replace('RSA', ''))
     key_args = {
         # FIXME: EC keys!
         'bits': bits,
         'name': vcard.fn,
         'email': vcard.email,
         'passphrase': passphrase,
         'comment': ''
     }
     event = Event(source=self,
                   message=_('Generating new %d bit PGP key. '
                             'This may take some time!') % bits,
                   flags=Event.INCOMPLETE,
                   data={'keygen_started': int(time.time()),
                         'profile_id': random_uid},
                   private_data=key_args)
     self._key_generator = GnuPGKeyGenerator(
        # FIXME: Passphrase handling is a problem here
        variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args),
        on_complete=(random_uid,
                     lambda: self._new_key_created(event, random_uid,
                                                   passphrase))
     )
     self._key_generator.start()
     self.session.config.event_log.log_event(event)
コード例 #3
0
ファイル: contacts.py プロジェクト: jean/Mailpile
    class ProfileVCardCommand(parent):
        SYNOPSIS = tuple(synopsis)
        KIND = 'profile'
        ORDER = ('Tagging', 3)
        VCARD = "profile"

        DEFAULT_KEYTYPE = 'RSA2048'

        def _yn(self, val, default='no'):
            return (self.data.get(val, [default])[0][:2].lower()
                    in ('ye', '1', 'on', 'tr'))

        def _sendmail_command(self):
            # FIXME - figure out where sendmail is for reals
            return mailpile.defaults.DEFAULT_SENDMAIL

        def _sanity_check(self, kind, triplets):
            route_id = self.data.get('route_id', [None])[0]
            if (route_id or [k for k in self.data.keys() if k[:5] in
                             ('route', 'smtp-', 'sourc', 'secur', 'local')]):
                if len(triplets) > 1 or kind != 'profile':
                    raise ValueError('Can only configure detailed settings '
                                     'for one profile at a time')

            # FIXME: Check more important invariants and raise

        def _configure_sending_route(self, vcard, route_id):
            # Sending route
            route = self.session.config.routes.get(route_id)
            protocol = self.data.get('route-protocol', ['none'])[0]
            if protocol == 'none':
                if route:
                    del self.session.config.routes[route_id]
                vcard.route = ''
                return
            elif protocol == 'local':
                route.password = route.username = route.host = ''
                route.name = _("Local mail")
                route.command = self.data.get('route-command', [None]
                                              )[0] or self._sendmail_command()
            elif protocol in ('smtp', 'smtptls', 'smtpssl'):
                route.command = ''
                route.name = vcard.email
                for var in ('route-username', 'route-password',
                            'route-auth_type', 'route-host', 'route-port'):
                   rvar = var.split('-', 1)[1]
                   route[rvar] = self.data.get(var, [''])[0]
            else:
                raise ValueError(_('Unhandled outgoing mail protocol: %s'
                                   ) % protocol)
            route.protocol = protocol
            vcard.route = route_id

        def _get_mail_spool(self):
            path = os.getenv('MAIL') or None
            user = os.getenv('USER')
            if user and not path:
                if os.path.exists('/var/spool/mail'):
                    path = os.path.normpath('/var/spool/mail/%s' % user)
                if os.path.exists('/var/mail'):
                    path = os.path.normpath('/var/mail/%s' % user)
            return path

        def _configure_mail_sources(self, vcard):
            config = self.session.config
            sources = [r[7:].rsplit('-', 1)[0] for r in self.data.keys()
                       if r.startswith('source-') and r.endswith('-protocol')]
            for src_id in sources:
                prefix = 'source-%s-' % src_id
                protocol = self.data.get(prefix + 'protocol', ['none'])[0]
                def configure_source(source):
                    source.host = ''
                    source.password = ''
                    source.username = ''
                    source.enabled = self._yn(prefix + 'enabled')
                    source.discovery.create_tag = True
                    source.discovery.process_new = True
                    if src_id not in vcard.sources():
                        vcard.add_source(source._key)
                    return source
                def make_new_source():
                    # This little dance makes sure source is actually a
                    # config section, not just an anonymous dict.
                    if src_id not in config.sources:
                        config.sources[src_id] = {}
                    source = config.sources[src_id]
                    source.profile = vcard.random_uid
                    source.discovery.apply_tags = [vcard.tag]
                    return configure_source(source)

                if protocol == 'none':
                    pass

                elif protocol == 'local':
                    source = configure_source(vcard.get_source_by_proto(
                        'local', create=src_id))

                elif protocol == 'spool':
                    path = self._get_mail_spool()
                    if not path:
                        raise ValueError(_('Mail spool not found'))

                    if path in config.sys.mailbox.values():
                        raise ValueError(_('Already configured: %s') % path)
                    else:
                        mailbox_idx = config.sys.mailbox.append(path)

                    source = configure_source(vcard.get_source_by_proto(
                        'local', create=src_id))
                    src_id = source._key

                    # We need to communicate with the source below,
                    # so we save config to trigger instanciation.
                    self._background_save(config=True, wait=True)

                    inbox = [t._key for t in config.get_tags(type='inbox')]
                    local_copy = self._yn(prefix + 'copy-local')
                    if self._yn(prefix + 'delete-source'):
                        policy = 'move'
                    else:
                        policy = 'read'

                    src_obj = config.mail_sources[src_id]
                    src_obj.take_over_mailbox(mailbox_idx,
                                              policy=policy,
                                              create_local=local_copy,
                                              apply_tags=inbox,
                                              save=False)

                elif protocol in ('imap', 'imap_ssl', 'imap_tls',
                                  'pop3', 'pop3_ssl'):
                    source = make_new_source()

                    # Discovery policy
                    disco = source.discovery
                    if self._yn(prefix + 'index-all-mail'):
                        if self._yn(prefix + 'leave-on-server'):
                            disco.policy = 'sync'
                        else:
                            disco.policy = 'move'
                        disco.local_copy = True
                        disco.paths = ['']
                    else:
                        disco.policy = 'ignore'
                        disco.local_copy = False
                        disco.paths = []
                    disco.guess_tags = True
                    disco.visible_tags = self._yn(prefix + 'visible-tags')

                    # Connection settings
                    for rvar in ('protocol', 'auth_type', 'host', 'port',
                                 'username', 'password'):
                        source[rvar] = self.data.get(prefix + rvar, [''])[0]
                    if (self._yn(prefix + 'force-starttls')
                            and source.protocol == 'imap'):
                        source.protocol = 'imap_tls'
                    username = source.username
                    if '@' not in username:
                        username += '@%s' % source.host
                    source.name = username

                else:
                    raise ValueError(_('Unhandled incoming mail protocol: %s'
                                       ) % protocol)

        def _new_key_created(self, event, vcard_rid, passphrase):
            config = self.session.config
            fingerprint = self._key_generator.generated_key
            if fingerprint:
                vcard = vcard_rid and config.vcards.get_vcard(vcard_rid)
                if vcard:
                    vcard.pgp_key = fingerprint
                    vcard.save()
                    event.message = _('The PGP key for %s is ready for use.'
                                      ) % vcard.email
                else:
                    event.message = _('PGP key generation is complete')

                # Record the passphrase!
                config.secrets[fingerprint] = {'password': passphrase}

                # FIXME: Toggle something that indicates we need a backup ASAP.
                self._background_save(config=True)
            else:
                event.message = _('PGP key generation failed!')
                event.data['keygen_failed'] = True

            event.flags = event.COMPLETE
            event.data['keygen_finished'] = int(time.time())
            config.event_log.log_event(event)

        def _create_new_key(self, vcard, keytype):
            passphrase = okay_random(26, self.session.config.master_key
                                     ).lower()
            random_uid = vcard.random_uid
            bits = int(keytype.replace('RSA', ''))
            key_args = {
                # FIXME: EC keys!
                'bits': bits,
                'name': vcard.fn,
                'email': vcard.email,
                'passphrase': passphrase,
                'comment': ''
            }
            event = Event(source=self,
                          message=_('Generating new %d bit PGP key. '
                                    'This may take some time!') % bits,
                          flags=Event.INCOMPLETE,
                          data={'keygen_started': int(time.time()),
                                'profile_id': random_uid},
                          private_data=key_args)
            self._key_generator = GnuPGKeyGenerator(
               # FIXME: Passphrase handling is a problem here
               variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args),
               on_complete=(random_uid,
                            lambda: self._new_key_created(event, random_uid,
                                                          passphrase))
            )
            self._key_generator.start()
            self.session.config.event_log.log_event(event)

        def _configure_security(self, vcard):
            openpgp_key = self.data.get('security-pgp-key', [''])[0]
            if openpgp_key:
                if openpgp_key.startswith('!CREATE'):
                    key_type = openpgp_key[8:] or self.DEFAULT_KEYTYPE
                    self._create_new_key(vcard, key_type)
                else:
                    vcard.pgp_key = openpgp_key
                    # FIXME: Schedule a background sync job which edits
                    #        the key to add this Account as a UID, if it

            else:
                vcard.remove_all('key')

            # Set the following even if we don't have a key, so they don't
            # get lost if the user edits settings while a key is being
            # generated - or if they just deselect a key temporarily.

            # Encryption policy rules
            outg_auto = self._yn('security-best-effort-crypto')
            outg_sig  = self._yn('security-always-sign')
            outg_enc  = self._yn('security-always-encrypt')
            if outg_enc and outg_sig:
                vcard.crypto_policy = 'openpgp-sign-encrypt'
            elif outg_sig:
                vcard.crypto_policy = 'openpgp-sign'
            elif outg_enc:
                vcard.crypto_policy = 'openpgp-encrypt'
            elif outg_auto:
                vcard.crypto_policy = 'best-effort'
            else:
                vcard.crypto_policy = 'none'

            # Crypto formatting rules
            pgp_keys     = self._yn('security-attach-keys')
            pgp_inline   = self._yn('security-prefer-inline')
            pgp_hdr_enc  = self._yn('security-openpgp-header-encrypt')
            pgp_hdr_sig  = self._yn('security-openpgp-header-sign')
            pgp_hdr_none = self._yn('security-openpgp-header-none')
            pgp_hdr_both = pgp_hdr_enc and pgp_hdr_sig
            if pgp_hdr_both:
                pgp_hdr_enc = pgp_hdr_sig = False
            vcard.crypto_format = ''.join([
                'openpgp_header:SE+' if (pgp_hdr_both) else '',
                'openpgp_header:S+'  if (pgp_hdr_sig)  else '',
                'openpgp_header:E+'  if (pgp_hdr_enc)  else '',
                'openpgp_header:N+'  if (pgp_hdr_none) else '',
                'send_keys+'         if (pgp_keys)     else '',
                'prefer_inline'      if (pgp_inline)   else 'pgpmime'
            ])
コード例 #4
0
ファイル: setup_magic.py プロジェクト: whiteyeti/Mailpile
    def setup_command(self, session):
        changed = authed = False
        results = {
            'secret_keys': self.list_secret_keys(),
        }
        error_info = None

        if self.data.get('_method') == 'POST' or self._testing():

            # 1st, are we choosing or creating a new key?
            choose_key = self.data.get('choose_key', [''])[0]
            if choose_key and not error_info:
                if (choose_key not in results['secret_keys'] and
                        choose_key != '!CREATE'):
                    error_info = (_('Invalid key'), {
                        'invalid_key': True,
                        'chosen_key': choose_key
                    })

            # 2nd, check authentication...
            #
            # FIXME: Creating a new key will allow a malicious actor to
            #        bypass authentication and change settings.
            #
            try:
                passphrase = self.data.get('passphrase', [''])[0]
                passphrase2 = self.data.get('passphrase_confirm', [''])[0]
                chosen_key = ((not error_info) and choose_key
                              ) or session.config.prefs.gpg_recipient

                if not error_info:
                    assert(passphrase == passphrase2)
                    if chosen_key == '!CREATE':
                        assert(passphrase != '')
                        sps = SecurePassphraseStorage(passphrase)
                    elif chosen_key:
                        sps = mailpile.auth.VerifyAndStorePassphrase(
                            session.config,
                            passphrase=passphrase,
                            key=chosen_key)
                    else:
                        sps = mailpile.auth.VerifyAndStorePassphrase(
                            session.config, passphrase=passphrase)
                    if not chosen_key:
                        choose_key = '!CREATE'
                    results['updated_passphrase'] = True
                    session.config.gnupg_passphrase.data = sps.data
                    mailpile.auth.SetLoggedIn(self)
            except AssertionError:
                error_info = (_('Invalid passphrase'), {
                    'invalid_passphrase': True,
                    'chosen_key': session.config.prefs.gpg_recipient
                })

            # 3rd, if necessary master key and/or GPG key
            with BLOCK_HTTPD_LOCK, Idle_HTTPD():
                if choose_key and not error_info:
                    session.config.prefs.gpg_recipient = choose_key
                    # FIXME: This should probably only happen if the GPG
                    #        key was successfully created.
                    self.make_master_key()
                    changed = True

                with Setup.KEY_WORKER_LOCK:
                    if ((not error_info) and
                            (session.config.prefs.gpg_recipient
                             == '!CREATE') and
                            (Setup.KEY_CREATING_THREAD is None or
                             Setup.KEY_CREATING_THREAD.failed)):
                        gk = GnuPGKeyGenerator(
                            sps=session.config.gnupg_passphrase,
                            on_complete=('notify',
                                         lambda: self.gpg_key_ready(gk)))
                        Setup.KEY_CREATING_THREAD = gk
                        Setup.KEY_CREATING_THREAD.start()

            # Finally we update misc. settings
            for key in self.HTTP_POST_VARS.keys():
                # FIXME: This should probably only happen if the GPG
                #        key was successfully created.

                # Continue iff all is well...
                if error_info:
                    break
                if key in (['choose_key', 'passphrase', 'passphrase_confirm'] +
                           TestableWebbable.HTTP_POST_VARS.keys()):
                    continue
                try:
                    val = self.data.get(key, [''])[0]
                    if val:
                        session.config.prefs[key] = self.TRUTHY[val.lower()]
                        changed = True
                except (ValueError, KeyError):
                    error_info = (_('Invalid preference'), {
                        'invalid_setting': True,
                        'variable': key
                    })

        results.update({
            'creating_key': (Setup.KEY_CREATING_THREAD is not None and
                             Setup.KEY_CREATING_THREAD.running),
            'creating_failed': (Setup.KEY_CREATING_THREAD is not None and
                                Setup.KEY_CREATING_THREAD.failed),
            'chosen_key': session.config.prefs.gpg_recipient,
            'prefs': {
                'index_encrypted': session.config.prefs.index_encrypted,
                'obfuscate_index': session.config.prefs.obfuscate_index,
                'encrypt_mail': session.config.prefs.encrypt_mail,
                'encrypt_index': session.config.prefs.encrypt_index,
                'encrypt_vcards': session.config.prefs.encrypt_vcards,
                'encrypt_events': session.config.prefs.encrypt_events,
                'encrypt_misc': session.config.prefs.encrypt_misc
            }
        })

        if changed:
            self._background_save(config=True)

        if error_info:
            return self._error(error_info[0],
                               info=error_info[1], result=results)
        elif changed:
            return self._success(_('Updated crypto preferences'), results)
        else:
            return self._success(_('Configure crypto preferences'), results)
コード例 #5
0
    class ProfileVCardCommand(parent):
        SYNOPSIS = tuple(synopsis)
        KIND = 'profile'
        ORDER = ('Tagging', 3)
        VCARD = "profile"

        DEFAULT_KEYTYPE = 'RSA2048'

        def _yn(self, val, default='no'):
            return (self.data.get(val, [default])[0][:2].lower()
                    in ('ye', '1', 'on', 'tr'))

        def _sendmail_command(self):
            # FIXME - figure out where sendmail is for reals
            return mailpile.defaults.DEFAULT_SENDMAIL

        def _sanity_check(self, kind, triplets):
            route_id = self.data.get('route_id', [None])[0]
            if (route_id or [k for k in self.data.keys() if k[:5] in
                             ('route', 'smtp-', 'sourc', 'secur', 'local')]):
                if len(triplets) > 1 or kind != 'profile':
                    raise ValueError('Can only configure detailed settings '
                                     'for one profile at a time')

            # FIXME: Check more important invariants and raise

        def _configure_sending_route(self, vcard, route_id):
            # Sending route
            route = self.session.config.routes.get(route_id)
            protocol = self.data.get('route-protocol', ['none'])[0]
            if protocol == 'none':
                if route:
                    del self.session.config.routes[route_id]
                vcard.route = ''
                return
            elif protocol == 'local':
                route.password = route.username = route.host = ''
                route.name = _("Local mail")
                route.command = self.data.get('route-command', [None]
                                              )[0] or self._sendmail_command()
            elif protocol in ('smtp', 'smtptls', 'smtpssl'):
                route.command = ''
                route.name = vcard.email
                for var in ('route-username', 'route-password',
                            'route-auth_type', 'route-host', 'route-port'):
                   rvar = var.split('-', 1)[1]
                   route[rvar] = self.data.get(var, [''])[0]
            else:
                raise ValueError(_('Unhandled outgoing mail protocol: %s'
                                   ) % protocol)
            route.protocol = protocol
            vcard.route = route_id

        def _get_mail_spool(self):
            path = os.getenv('MAIL') or None
            user = os.getenv('USER')
            if user and not path:
                if os.path.exists('/var/mail'):
                    path = '/var/mail/%s' % user
                if os.path.exists('/var/spool/mail'):
                    path = '/var/spool/mail/%s' % user
            return path

        def _configure_mail_sources(self, vcard):
            config = self.session.config
            sources = [r[7:].rsplit('-', 1)[0] for r in self.data.keys()
                       if r.startswith('source-') and r.endswith('-protocol')]
            for src_id in sources:
                prefix = 'source-%s-' % src_id
                protocol = self.data.get(prefix + 'protocol', ['none'])[0]
                def make_new_source():
                    # This little dance makes sure source is actually a
                    # config section, not just an anonymous dict.
                    if src_id not in config.sources:
                        config.sources[src_id] = {}
                    source = config.sources[src_id]
                    source.host = ''
                    source.password = ''
                    source.username = ''
                    source.profile = vcard.random_uid
                    source.enabled = self._yn(prefix + 'enabled')
                    source.discovery.apply_tags = [vcard.tag]
                    source.discovery.create_tag = True
                    source.discovery.process_new = True
                    if src_id not in vcard.sources():
                        vcard.add_source(src_id)
                    return source

                if protocol == 'none':
                    pass

                elif protocol == 'spool':
                    path = self._get_mail_spool()
                    if not path:
                        raise ValueError(_('Mail spool not found'))

                    if path in config.sys.mailbox.values():
                        raise ValueError(_('Already configured: %s') % path)
                    else:
                        mailbox_idx = config.sys.mailbox.append(path)

                    source = make_new_source()
                    source.name = path
                    source.protocol = 'mbox'
                    for tag in config.get_tags(type='inbox'):
                        source.discovery.apply_tags.append(tag._key)

                    # We need to communicate with the source below,
                    # so we save config to trigger instanciation.
                    self._background_save(config=True, wait=True)

                    local_copy = self._yn(prefix + 'copy-local')
                    if self._yn(prefix + 'delete-source'):
                        policy = 'move'
                    else:
                        policy = 'read'

                    src_obj = config.mail_sources[src_id]
                    src_obj.take_over_mailbox(mailbox_idx,
                                              policy=policy,
                                              create_local=local_copy,
                                              save=False)

                elif protocol in ('imap', 'imap_ssl', 'pop3', 'pop3_ssl'):
                    source = make_new_source()

                    # Discovery policy
                    disco = source.discovery
                    if self._yn(prefix + 'index-all-mail'):
                        if self._yn(prefix + 'leave-on-server'):
                            disco.policy = 'sync'
                        else:
                            disco.policy = 'move'
                        disco.local_copy = True
                    else:
                        disco.policy = 'ignore'
                        disco.local_copy = False
                    disco.paths = ['']
                    disco.guess_tags = True
                    disco.visible_tags = self._yn(prefix + 'visible-tags')

                    # Connection settings
                    for rvar in ('protocol', 'auth_type', 'host', 'port',
                                 'username', 'password'):
                        source[rvar] = self.data.get(prefix + rvar, [''])[0]
                    username = source.username
                    if '@' not in username:
                        username += '@%s' % source.host
                    source.name = username

                else:
                    raise ValueError(_('Unhandled incoming mail protocol: %s'
                                       ) % protocol)

        def _new_key_created(self, event, vcard_rid, passphrase):
            config = self.session.config
            fingerprint = self._key_generator.generated_key
            if fingerprint:
                vcard = vcard_rid and config.vcards.get_vcard(vcard_rid)
                if vcard:
                    vcard.pgp_key = fingerprint
                    vcard.save()
                    event.message = _('The PGP key for %s is ready for use.'
                                      ) % vcard.email
                else:
                    event.message = _('PGP key generation is complete')

                # Record the passphrase!
                config.secrets[fingerprint] = {'password': passphrase}

                # FIXME: Toggle something that indicates we need a backup ASAP.
                self._background_save(config=True)
            else:
                event.message = _('PGP key generation failed!')
                event.data['keygen_failed'] = True

            event.flags = event.COMPLETE
            event.data['keygen_finished'] = int(time.time())
            config.event_log.log_event(event)

        def _create_new_key(self, vcard, keytype):
            passphrase = okay_random(26, self.session.config.master_key
                                     ).lower()
            random_uid = vcard.random_uid
            bits = int(keytype.replace('RSA', ''))
            key_args = {
                # FIXME: EC keys!
                'bits': bits,
                'name': vcard.fn,
                'email': vcard.email,
                'passphrase': passphrase,
                'comment': ''
            }
            event = Event(source=self,
                          message=_('Generating new %d bit PGP key. '
                                    'This may take some time!') % bits,
                          flags=Event.INCOMPLETE,
                          data={'keygen_started': int(time.time()),
                                'profile_id': random_uid},
                          private_data=key_args)
            self._key_generator = GnuPGKeyGenerator(
               # FIXME: Passphrase handling is a problem here
               variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args),
               on_complete=(random_uid,
                            lambda: self._new_key_created(event, random_uid,
                                                          passphrase))
            )
            self._key_generator.start()
            self.session.config.event_log.log_event(event)

        def _configure_security(self, vcard):
            openpgp_key = self.data.get('security-pgp-key', [''])[0]
            if openpgp_key:
                if openpgp_key.startswith('!CREATE'):
                    key_type = openpgp_key[8:] or self.DEFAULT_KEYTYPE
                    self._create_new_key(vcard, key_type)
                else:
                    vcard.pgp_key = openpgp_key
                    # FIXME: Schedule a background sync job which edits
                    #        the key to add this Account as a UID, if it

            else:
                vcard.remove_all('key')

            # Set the following even if we don't have a key, so they don't
            # get lost if the user edits settings while a key is being
            # generated - or if they just deselect a key temporarily.

            # Encryption policy rules
            outg_auto = self._yn('security-best-effort-crypto')
            outg_sig  = self._yn('security-always-sign')
            outg_enc  = self._yn('security-always-encrypt')
            if outg_enc and outg_sig:
                vcard.crypto_policy = 'openpgp-sign-encrypt'
            elif outg_sig:
                vcard.crypto_policy = 'openpgp-sign'
            elif outg_enc:
                vcard.crypto_policy = 'openpgp-encrypt'
            elif outg_auto:
                vcard.crypto_policy = 'best-effort'
            else:
                vcard.crypto_policy = 'none'

            # Crypto formatting rules
            pgp_keys     = self._yn('security-attach-keys')
            pgp_inline   = self._yn('security-prefer-inline')
            pgp_hdr_enc  = self._yn('security-openpgp-header-encrypt')
            pgp_hdr_sig  = self._yn('security-openpgp-header-sign')
            pgp_hdr_none = self._yn('security-openpgp-header-none')
            pgp_hdr_both = pgp_hdr_enc and pgp_hdr_sig
            if pgp_hdr_both:
                pgp_hdr_enc = pgp_hdr_sig = False
            vcard.crypto_format = ''.join([
                'openpgp_header:SE+' if (pgp_hdr_both) else '',
                'openpgp_header:S+'  if (pgp_hdr_sig)  else '',
                'openpgp_header:E+'  if (pgp_hdr_enc)  else '',
                'openpgp_header:N+'  if (pgp_hdr_none) else '',
                'send_keys+'         if (pgp_keys)     else '',
                'prefer_inline'      if (pgp_inline)   else 'pgpmime'
            ])