Script to store passwords in a file with GPG or using OSX's secure keychain
Submitted-by: https://github.com/lorenzog Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
parent
b5ffa1c163
commit
c865dcc03c
37
contrib/store-pw-with-gpg/README.md
Normal file
37
contrib/store-pw-with-gpg/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# gpg-offlineimap
|
||||||
|
|
||||||
|
Python bindings for offlineimap to use gpg instead of storing cleartext passwords
|
||||||
|
|
||||||
|
Author: Lorenzo G.
|
||||||
|
[GitHub](https://github.com/lorenzog/gpg-offlineimap)
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Requirements: a working GPG set-up. Ideally with gpg-agent. Should work
|
||||||
|
out of the box on most modern Linux desktop environments.
|
||||||
|
|
||||||
|
1. Enable IMAP in gmail (if you have two factor authentication, you
|
||||||
|
need to create an app-specific password)
|
||||||
|
|
||||||
|
2. Create a directory `~/Mail`
|
||||||
|
|
||||||
|
3. In `~/Mail`, create a password file `passwords-gmail.txt`. Format:
|
||||||
|
`account@gmail.com password`. Look at the example file in this
|
||||||
|
directory.
|
||||||
|
|
||||||
|
4. **ENCRYPT** the file: `gpg -e passwords-gmail.txt`. It should create
|
||||||
|
a file `passwords-gmail.txt.gpg`. Check you can decrypt it: `gpg -d
|
||||||
|
passwords-gmail.txt.gpg`: it will ask you for your GPG password and
|
||||||
|
show it to you.
|
||||||
|
|
||||||
|
5. Use the file `offlineimaprc.sample` as a sample for your own
|
||||||
|
`.offlineimaprc`; edit it by following the comments. Minimal items
|
||||||
|
to configure: the `remoteuser` field and the `pythonfile` parameter
|
||||||
|
pointing at the `offlineimap.py` file in this directory.
|
||||||
|
|
||||||
|
6. Run it: `offlineimap`. It should ask you for your GPG passphrase to
|
||||||
|
decrypt the password file.
|
||||||
|
|
||||||
|
7. If all works well, delete the cleartext password file.
|
||||||
|
|
||||||
|
|
99
contrib/store-pw-with-gpg/gpg-pw.py
Normal file
99
contrib/store-pw-with-gpg/gpg-pw.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# Originally taken from: http://stevelosh.com/blog/2012/10/the-homely-mutt/
|
||||||
|
# by Steve Losh
|
||||||
|
# Modified by Lorenzo Grespan on Jan, 2014
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from sys import argv
|
||||||
|
import logging
|
||||||
|
from os.path import expanduser
|
||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PASSWORDS_FILE = os.path.join(
|
||||||
|
os.path.expanduser('~/Mail'),
|
||||||
|
'passwords.gpg')
|
||||||
|
|
||||||
|
|
||||||
|
def get_keychain_pass(account=None, server=None):
|
||||||
|
'''Mac OSX keychain password extraction'''
|
||||||
|
params = {
|
||||||
|
'security': '/usr/bin/security',
|
||||||
|
'command': 'find-internet-password',
|
||||||
|
'account': account,
|
||||||
|
'server': server,
|
||||||
|
'keychain': expanduser('~') + '/Library/Keychains/login.keychain',
|
||||||
|
}
|
||||||
|
command = ("%(security)s -v %(command)s"
|
||||||
|
" -g -a %(account)s -s %(server)s %(keychain)s" % params)
|
||||||
|
output = subprocess.check_output(
|
||||||
|
command, shell=True, stderr=subprocess.STDOUT)
|
||||||
|
outtext = [l for l in output.splitlines()
|
||||||
|
if l.startswith('password: ')][0]
|
||||||
|
return find_password(outtext)
|
||||||
|
|
||||||
|
|
||||||
|
def find_password(text):
|
||||||
|
'''Helper method for osx password extraction'''
|
||||||
|
# a non-capturing group
|
||||||
|
r = re.match(r'password: (?:0x[A-F0-9]+ )?"(.*)"', text)
|
||||||
|
if r:
|
||||||
|
return r.group(1)
|
||||||
|
else:
|
||||||
|
logging.warn("Not found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_gpg_pass(account, storage):
|
||||||
|
'''GPG method'''
|
||||||
|
command = ("gpg", "-d", storage)
|
||||||
|
# get attention
|
||||||
|
print '\a' # BEL
|
||||||
|
output = subprocess.check_output(command)
|
||||||
|
# p = subprocess.Popen(command, stdout=subprocess.PIPE)
|
||||||
|
# output, err = p.communicate()
|
||||||
|
for line in output.split('\n'):
|
||||||
|
r = re.match(r'{} ([a-zA-Z0-9]+)'.format(account), line)
|
||||||
|
if r:
|
||||||
|
return r.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_pass(account=None, server=None, passwd_file=None):
|
||||||
|
'''Main method'''
|
||||||
|
if not passwd_file:
|
||||||
|
storage = DEFAULT_PASSWORDS_FILE
|
||||||
|
else:
|
||||||
|
storage = os.path.join(
|
||||||
|
os.path.expanduser('~/Mail'),
|
||||||
|
passwd_file)
|
||||||
|
if os.path.exists('/usr/bin/security'):
|
||||||
|
return get_keychain_pass(account, server)
|
||||||
|
if os.path.exists(storage):
|
||||||
|
logging.info("Using {}".format(storage))
|
||||||
|
return get_gpg_pass(account, storage)
|
||||||
|
else:
|
||||||
|
logging.warn("No password file found")
|
||||||
|
sys.exit(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# test with: python -m unittest <this module name>
|
||||||
|
# really basic tests.. nothing to see. move along
|
||||||
|
class Tester(unittest.TestCase):
|
||||||
|
def testMatchSimple(self):
|
||||||
|
text = 'password: "exampleonetimepass "'
|
||||||
|
self.assertTrue(find_password(text))
|
||||||
|
|
||||||
|
def testMatchComplex(self):
|
||||||
|
text = r'password: 0x74676D62646D736B646970766C66696B0A "anotherexamplepass\012"'
|
||||||
|
self.assertTrue(find_password(text))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print get_pass(argv[1], argv[2], argv[3])
|
63
contrib/store-pw-with-gpg/offlineimaprc.sample
Normal file
63
contrib/store-pw-with-gpg/offlineimaprc.sample
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
[general]
|
||||||
|
# GPG quirks, leave unconfigured
|
||||||
|
ui = ttyui
|
||||||
|
# you can use any name as long as it matches the 'account1, 'account2' in the rest
|
||||||
|
# of the file
|
||||||
|
accounts = account1, account2
|
||||||
|
# this is where the `gpg-pw.py` file is on disk
|
||||||
|
pythonfile=~/where/is/the/file/gpg-pw.py
|
||||||
|
fsync = False
|
||||||
|
|
||||||
|
# you can call this any way you like
|
||||||
|
[Account account1]
|
||||||
|
localrepository = account1-local
|
||||||
|
remoterepository = account1-remote
|
||||||
|
# no need to touch this
|
||||||
|
status_backend = sqlite
|
||||||
|
|
||||||
|
[Account account2]
|
||||||
|
localrepository = account2-local
|
||||||
|
remoterepository = account2-remote
|
||||||
|
status_backend = sqlite
|
||||||
|
|
||||||
|
# thi sis a gmail account
|
||||||
|
[Repository account1-local]
|
||||||
|
type = Maildir
|
||||||
|
# create with maildirmake or by hand by creating cur, new, tmp
|
||||||
|
localfolders = ~/Mail/Mailboxes/account1
|
||||||
|
# standard Gmail stuff
|
||||||
|
nametrans = lambda folder: { 'drafts': '[Gmail]/Drafts',
|
||||||
|
'sent': '[Gmail]/Sent mail',
|
||||||
|
'flagged': '[Gmail]/Starred',
|
||||||
|
'trash': '[Gmail]/Trash',
|
||||||
|
'archive': '[Gmail]/All Mail'
|
||||||
|
}.get(folder, folder)
|
||||||
|
|
||||||
|
[Repository account1-remote]
|
||||||
|
maxconnections = 1
|
||||||
|
type = Gmail
|
||||||
|
ssl=yes
|
||||||
|
# for osx, you might need to download the certs by hand
|
||||||
|
#sslcacertfile=~/Mail/certs.pem
|
||||||
|
#sslcacertfile=~/Mail/imap.gmail.com.pem
|
||||||
|
# sslcacertfile=/etc/ssl/cert.pem
|
||||||
|
|
||||||
|
# or use Linux's standard certs
|
||||||
|
sslcacertfile=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
# your account
|
||||||
|
remoteuser = account1@gmail.com
|
||||||
|
remotepasseval = get_pass(account="account1@gmail.com", server="imap.gmail.com", passwd_file="passwords-gmail.txt.gpg")
|
||||||
|
realdelete = no
|
||||||
|
createfolders = no
|
||||||
|
nametrans = lambda folder: {'[Gmail]/Drafts': 'drafts',
|
||||||
|
'[Gmail]/Sent Mail': 'sent',
|
||||||
|
'[Gmail]/Starred': 'star',
|
||||||
|
'[Gmail]/Trash': 'trash',
|
||||||
|
'[Gmail]/All Mail': 'archive',
|
||||||
|
}.get(folder, folder)
|
||||||
|
folderfilter = lambda folder: folder not in ['[Gmail]/Trash',
|
||||||
|
'[Gmail]/Spam',
|
||||||
|
]
|
||||||
|
|
||||||
|
[Repository account2-remote]
|
||||||
|
# copy the stanza above, change the 'account' parameter of get_pass, etc.
|
2
contrib/store-pw-with-gpg/passwords-gmail.txt
Normal file
2
contrib/store-pw-with-gpg/passwords-gmail.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
account1@gmail.com password1
|
||||||
|
account2@gmail.com password2
|
Loading…
Reference in New Issue
Block a user