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
=== modified file 'src/calibre/gui2/email.py'
--- src/calibre/gui2/email.py 2012-12-20 04:18:34 +0000
+++ src/calibre/gui2/email.py 2013-01-11 13:32:25 +0000
@@ -7,7 +7,7 @@
7__docformat__ = 'restructuredtext en'7__docformat__ = 'restructuredtext en'
88
9import os, socket, time9import os, socket, time
10from binascii import unhexlify10import keyring
11from functools import partial11from functools import partial
12from threading import Thread12from threading import Thread
13from itertools import repeat13from itertools import repeat
@@ -109,7 +109,8 @@
109 verbose=1,109 verbose=1,
110 relay=opts.relay_host,110 relay=opts.relay_host,
111 username=opts.relay_username,111 username=opts.relay_username,
112 password=unhexlify(opts.relay_password), port=opts.relay_port,112 password=keyring.get_password('Calibre',opts.relay_username),
113 port=opts.relay_port,
113 encryption=opts.encryption,114 encryption=opts.encryption,
114 debug_output=log.debug)115 debug_output=log.debug)
115 finally:116 finally:
116117
=== modified file 'src/calibre/gui2/wizard/send_email.py'
--- src/calibre/gui2/wizard/send_email.py 2011-07-10 19:09:11 +0000
+++ src/calibre/gui2/wizard/send_email.py 2013-01-11 13:32:25 +0000
@@ -7,14 +7,13 @@
7__docformat__ = 'restructuredtext en'7__docformat__ = 'restructuredtext en'
88
9import cStringIO, sys9import cStringIO, sys
10from binascii import hexlify, unhexlify
11from functools import partial10from functools import partial
1211
13from PyQt4.Qt import QWidget, pyqtSignal, QDialog, Qt, QLabel, \12from PyQt4.Qt import QWidget, pyqtSignal, QDialog, Qt, QLabel, \
14 QLineEdit, QDialogButtonBox, QGridLayout, QCheckBox13 QLineEdit, QDialogButtonBox, QGridLayout, QCheckBox
1514
16from calibre.gui2.wizard.send_email_ui import Ui_Form15from calibre.gui2.wizard.send_email_ui import Ui_Form
17from calibre.utils.smtp import config as smtp_prefs16from calibre.utils.smtp import config as smtp_prefs, get_password_from_wallet
18from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog17from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog
19from calibre.gui2 import error_dialog, question_dialog18from calibre.gui2 import error_dialog, question_dialog
2019
@@ -32,7 +31,7 @@
32 self.to.setText(pa)31 self.to.setText(pa)
33 if opts.relay_host:32 if opts.relay_host:
34 self.label.setText(_('Using: %(un)s:%(pw)s@%(host)s:%(port)s and %(enc)s encryption')%33 self.label.setText(_('Using: %(un)s:%(pw)s@%(host)s:%(port)s and %(enc)s encryption')%
35 dict(un=opts.relay_username, pw=unhexlify(opts.relay_password),34 dict(un=opts.relay_username, pw=get_password_from_wallet(opts.relay_username),
36 host=opts.relay_host, port=opts.relay_port, enc=opts.encryption))35 host=opts.relay_host, port=opts.relay_port, enc=opts.encryption))
3736
38 def test(self, *args):37 def test(self, *args):
@@ -125,11 +124,14 @@
125 self.relay_host.textChanged.connect(self.changed)124 self.relay_host.textChanged.connect(self.changed)
126 self.relay_port.setValue(opts.relay_port)125 self.relay_port.setValue(opts.relay_port)
127 self.relay_port.valueChanged.connect(self.changed)126 self.relay_port.valueChanged.connect(self.changed)
127 self.relay_username_old = None
128 if opts.relay_username:128 if opts.relay_username:
129 self.relay_username_old = opts.relay_username
129 self.relay_username.setText(opts.relay_username)130 self.relay_username.setText(opts.relay_username)
131 relay_password = get_password_from_wallet(opts.relay_username)
132 if None != relay_password:
133 self.relay_password.setText(relay_password)
130 self.relay_username.textChanged.connect(self.changed)134 self.relay_username.textChanged.connect(self.changed)
131 if opts.relay_password:
132 self.relay_password.setText(unhexlify(opts.relay_password))
133 self.relay_password.textChanged.connect(self.changed)135 self.relay_password.textChanged.connect(self.changed)
134 getattr(self, 'relay_'+opts.encryption.lower()).setChecked(True)136 getattr(self, 'relay_'+opts.encryption.lower()).setChecked(True)
135 self.relay_tls.toggled.connect(self.changed)137 self.relay_tls.toggled.connect(self.changed)
@@ -151,7 +153,7 @@
151 to_set = pa is not None153 to_set = pa is not None
152 if self.set_email_settings(to_set):154 if self.set_email_settings(to_set):
153 opts = smtp_prefs().parse()155 opts = smtp_prefs().parse()
154 if not opts.relay_password or question_dialog(self, _('OK to proceed?'),156 if not opts.relay_username or question_dialog(self, _('OK to proceed?'),
155 _('This will display your email password on the screen'157 _('This will display your email password on the screen'
156 '. Is it OK to proceed?'), show_copy_button=False):158 '. Is it OK to proceed?'), show_copy_button=False):
157 TestEmail(pa, self).exec_()159 TestEmail(pa, self).exec_()
@@ -169,7 +171,7 @@
169 sendmail(msg, from_=opts.from_, to=[to],171 sendmail(msg, from_=opts.from_, to=[to],
170 verbose=3, timeout=30, relay=opts.relay_host,172 verbose=3, timeout=30, relay=opts.relay_host,
171 username=opts.relay_username,173 username=opts.relay_username,
172 password=unhexlify(opts.relay_password),174 password=get_password_from_wallet(opts.relay_username),
173 encryption=opts.encryption, port=opts.relay_port)175 encryption=opts.encryption, port=opts.relay_port)
174 except:176 except:
175 import traceback177 import traceback
@@ -248,8 +250,20 @@
248 conf.set('relay_host', host if host else None)250 conf.set('relay_host', host if host else None)
249 conf.set('relay_port', self.relay_port.value())251 conf.set('relay_port', self.relay_port.value())
250 conf.set('relay_username', username if username else None)252 conf.set('relay_username', username if username else None)
251 conf.set('relay_password', hexlify(password))
252 conf.set('encryption', enc_method)253 conf.set('encryption', enc_method)
254
255 if not self.relay_username_old is None:
256 if username != self.relay_username_old:
257 import keyring
258 try:
259 keyring.delete_password('Calibre', self.relay_username_old)
260 except: pass
261 if username:
262 import keyring
263 keyring.set_password('Calibre', username, password)
264
265 self.relay_username_old = username
266
253 return True267 return True
254268
255269
256270
=== modified file 'src/calibre/utils/smtp.py'
--- src/calibre/utils/smtp.py 2013-01-11 09:35:10 +0000
+++ src/calibre/utils/smtp.py 2013-01-11 13:32:25 +0000
@@ -179,6 +179,12 @@
179 from email.utils import parseaddr179 from email.utils import parseaddr
180 return parseaddr(raw)[-1]180 return parseaddr(raw)[-1]
181181
182def get_password_from_wallet(username):
183 import keyring
184 if username:
185 return keyring.get_password('Calibre',username)
186 return None
187
182def compose_mail(from_, to, text, subject=None, attachment=None,188def compose_mail(from_, to, text, subject=None, attachment=None,
183 attachment_name=None):189 attachment_name=None):
184 attachment_type = attachment_data = None190 attachment_type = attachment_data = None
@@ -265,7 +271,6 @@
265 c.add_opt('relay_host')271 c.add_opt('relay_host')
266 c.add_opt('relay_port', default=25)272 c.add_opt('relay_port', default=25)
267 c.add_opt('relay_username')273 c.add_opt('relay_username')
268 c.add_opt('relay_password')
269 c.add_opt('encryption', default='TLS', choices=['TLS', 'SSL'])274 c.add_opt('encryption', default='TLS', choices=['TLS', 'SSL'])
270 return c275 return c
271276

Subscribers

People subscribed via source and target branches