Merge lp:~macieksitarz/calibre/keyring-support into lp:calibre

Proposed by Maciej Sitarz
Status: Superseded
Proposed branch: lp:~macieksitarz/calibre/keyring-support
Merge into: lp:calibre
Diff against target: 132 lines (+31/-11)
3 files modified
src/calibre/gui2/email.py (+3/-2)
src/calibre/gui2/wizard/send_email.py (+22/-8)
src/calibre/utils/smtp.py (+6/-1)
To merge this branch: bzr merge lp:~macieksitarz/calibre/keyring-support
Reviewer Review Type Date Requested Status
Kovid Goyal Disapprove
Review via email: mp+142893@code.launchpad.net

This proposal has been superseded by a proposal from 2013-01-13.

Description of the change

This branch introduces python keyring support. Keyring's are used as a common secure password storage.

python-keyring:
http://pypi.python.org/pypi/keyring
https://bitbucket.org/kang/python-keyring-lib

It has wide platform support:

The keyring services supported by the Python keyring lib:

    OSXKeychain: supports the Keychain service in Mac OS X.
    KDEKWallet: supports the KDE's Kwallet service.
    GnomeKeyring: for Gnome 2 environment.
    SecretServiceKeyring: for newer GNOME and KDE environments.

Besides these native password storing services provided by operating systems. Python keyring lib also provides following build-in keyrings.

    Win32CryptoKeyring: for Windows 2k+.
    CryptedFileKeyring: a command line interface keyring base on PyCrypto.
    UncryptedFileKeyring: a keyring which leaves passwords directly in file.

To post a comment you must log in.
Revision history for this message
Maciej Sitarz (macieksitarz) wrote :

Instead of using hexlified password stored in file keyring would be used.
It improves security and uses common keyrings depending on the platform and/or window manager.

Revision history for this message
Maciej Sitarz (macieksitarz) wrote :

One more thing about testing done.

I tested this on Linux using KWallet(KDE's keyring), but it should work properly on other OSes and window managers, as python-keyring-lib handles that internaly.

Revision history for this message
Kovid Goyal (kovid) wrote :

Some problems I see:

1) The patch must not break existing setups. Email cannot stop working on a calibre update.

2) The various keyrings depend on environment variables, X sessions, and the like. Those environemnt variables may not be present in all contexts calibre-smtp runs, for example, in cron jobs. That makes calibre-smtp significantly less robust.

3) I dont see how a keyring is fundamentally more or less secure than plaintext password storage. Once an attacher has read access to a user's data, decrypting password stores is possible by brute force attacks. Reversible password storage can simply *never* be secure. However, I do agree it makes an attackers' life a little less convenient.

4) Someone has to test this witht he various keyring implementations. That someone is not going to be me :)

Given all those problems, I am not willing to accept this patch. I will consider a patch that uses an optional keyring based password storage, for people that want to use it. But the default method will remain plaintext storage.

review: Disapprove
Revision history for this message
Maciej Sitarz (macieksitarz) wrote :

> 1) The patch must not break existing setups. Email cannot stop working on a
> calibre update.

If compatibility is necessary migration code could be added.

Algorithm like:
if relay_password exists in config:
    unhexlify password
    save password to keyring
    remove relay_password from config

> 2) The various keyrings depend on environment variables, X sessions, and the
> like. Those environemnt variables may not be present in all contexts calibre-
> smtp runs, for example, in cron jobs. That makes calibre-smtp significantly
> less robust.

Rigt now calibre-smtp is not using password from config file. Is there such functionality?
I'm asking because I also have a patch adding such functionality.

After applying the patch calibre-smtp will still have the -p/--password option, so one can use it in cron (after specifying plain text password).

> 3) I dont see how a keyring is fundamentally more or less secure than
> plaintext password storage. Once an attacher has read access to a user's data,
> decrypting password stores is possible by brute force attacks. Reversible
> password storage can simply *never* be secure. However, I do agree it makes an
> attackers' life a little less convenient.

Using strong password for the keyring will increase security. But you're right it won't be 100% secure.

> 4) Someone has to test this witht he various keyring implementations. That
> someone is not going to be me :)

Let's wait for volunteers...

> Given all those problems, I am not willing to accept this patch. I will
> consider a patch that uses an optional keyring based password storage, for
> people that want to use it. But the default method will remain plaintext
> storage.

The setting should be boolean "Use keyring for password storage" and visible in the SMTP settings window?

Revision history for this message
Kovid Goyal (kovid) wrote :

Yes, you would need to add a boolean option (checkbox) that says "Store password securely in keyring" to the GUI (opts.use_keyring) and similarly have a --use-keyring option for calibre-smtp. If opts.use_keyring is True, the code will try to get the password from the keyring, otherwise it will use opts.password.

Unmerged revisions

14073. By Maciej Sitarz

Merge from main branch

14072. By Maciej Sitarz

Add password keyring support

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/calibre/gui2/email.py'
2--- src/calibre/gui2/email.py 2012-12-20 04:18:34 +0000
3+++ src/calibre/gui2/email.py 2013-01-11 13:32:25 +0000
4@@ -7,7 +7,7 @@
5 __docformat__ = 'restructuredtext en'
6
7 import os, socket, time
8-from binascii import unhexlify
9+import keyring
10 from functools import partial
11 from threading import Thread
12 from itertools import repeat
13@@ -109,7 +109,8 @@
14 verbose=1,
15 relay=opts.relay_host,
16 username=opts.relay_username,
17- password=unhexlify(opts.relay_password), port=opts.relay_port,
18+ password=keyring.get_password('Calibre',opts.relay_username),
19+ port=opts.relay_port,
20 encryption=opts.encryption,
21 debug_output=log.debug)
22 finally:
23
24=== modified file 'src/calibre/gui2/wizard/send_email.py'
25--- src/calibre/gui2/wizard/send_email.py 2011-07-10 19:09:11 +0000
26+++ src/calibre/gui2/wizard/send_email.py 2013-01-11 13:32:25 +0000
27@@ -7,14 +7,13 @@
28 __docformat__ = 'restructuredtext en'
29
30 import cStringIO, sys
31-from binascii import hexlify, unhexlify
32 from functools import partial
33
34 from PyQt4.Qt import QWidget, pyqtSignal, QDialog, Qt, QLabel, \
35 QLineEdit, QDialogButtonBox, QGridLayout, QCheckBox
36
37 from calibre.gui2.wizard.send_email_ui import Ui_Form
38-from calibre.utils.smtp import config as smtp_prefs
39+from calibre.utils.smtp import config as smtp_prefs, get_password_from_wallet
40 from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog
41 from calibre.gui2 import error_dialog, question_dialog
42
43@@ -32,7 +31,7 @@
44 self.to.setText(pa)
45 if opts.relay_host:
46 self.label.setText(_('Using: %(un)s:%(pw)s@%(host)s:%(port)s and %(enc)s encryption')%
47- dict(un=opts.relay_username, pw=unhexlify(opts.relay_password),
48+ dict(un=opts.relay_username, pw=get_password_from_wallet(opts.relay_username),
49 host=opts.relay_host, port=opts.relay_port, enc=opts.encryption))
50
51 def test(self, *args):
52@@ -125,11 +124,14 @@
53 self.relay_host.textChanged.connect(self.changed)
54 self.relay_port.setValue(opts.relay_port)
55 self.relay_port.valueChanged.connect(self.changed)
56+ self.relay_username_old = None
57 if opts.relay_username:
58+ self.relay_username_old = opts.relay_username
59 self.relay_username.setText(opts.relay_username)
60+ relay_password = get_password_from_wallet(opts.relay_username)
61+ if None != relay_password:
62+ self.relay_password.setText(relay_password)
63 self.relay_username.textChanged.connect(self.changed)
64- if opts.relay_password:
65- self.relay_password.setText(unhexlify(opts.relay_password))
66 self.relay_password.textChanged.connect(self.changed)
67 getattr(self, 'relay_'+opts.encryption.lower()).setChecked(True)
68 self.relay_tls.toggled.connect(self.changed)
69@@ -151,7 +153,7 @@
70 to_set = pa is not None
71 if self.set_email_settings(to_set):
72 opts = smtp_prefs().parse()
73- if not opts.relay_password or question_dialog(self, _('OK to proceed?'),
74+ if not opts.relay_username or question_dialog(self, _('OK to proceed?'),
75 _('This will display your email password on the screen'
76 '. Is it OK to proceed?'), show_copy_button=False):
77 TestEmail(pa, self).exec_()
78@@ -169,7 +171,7 @@
79 sendmail(msg, from_=opts.from_, to=[to],
80 verbose=3, timeout=30, relay=opts.relay_host,
81 username=opts.relay_username,
82- password=unhexlify(opts.relay_password),
83+ password=get_password_from_wallet(opts.relay_username),
84 encryption=opts.encryption, port=opts.relay_port)
85 except:
86 import traceback
87@@ -248,8 +250,20 @@
88 conf.set('relay_host', host if host else None)
89 conf.set('relay_port', self.relay_port.value())
90 conf.set('relay_username', username if username else None)
91- conf.set('relay_password', hexlify(password))
92 conf.set('encryption', enc_method)
93+
94+ if not self.relay_username_old is None:
95+ if username != self.relay_username_old:
96+ import keyring
97+ try:
98+ keyring.delete_password('Calibre', self.relay_username_old)
99+ except: pass
100+ if username:
101+ import keyring
102+ keyring.set_password('Calibre', username, password)
103+
104+ self.relay_username_old = username
105+
106 return True
107
108
109
110=== modified file 'src/calibre/utils/smtp.py'
111--- src/calibre/utils/smtp.py 2013-01-11 09:35:10 +0000
112+++ src/calibre/utils/smtp.py 2013-01-11 13:32:25 +0000
113@@ -179,6 +179,12 @@
114 from email.utils import parseaddr
115 return parseaddr(raw)[-1]
116
117+def get_password_from_wallet(username):
118+ import keyring
119+ if username:
120+ return keyring.get_password('Calibre',username)
121+ return None
122+
123 def compose_mail(from_, to, text, subject=None, attachment=None,
124 attachment_name=None):
125 attachment_type = attachment_data = None
126@@ -265,7 +271,6 @@
127 c.add_opt('relay_host')
128 c.add_opt('relay_port', default=25)
129 c.add_opt('relay_username')
130- c.add_opt('relay_password')
131 c.add_opt('encryption', default='TLS', choices=['TLS', 'SSL'])
132 return c
133

Subscribers

People subscribed via source and target branches