#!/usr/bin/env python # -*- coding: utf-8 -*- # Created by Stuart Colville on 2008-02-02 # Muffin Research Labs. http://muffinresearch.co.uk/ # Updated by Graham Dutton on 2008-11-23 # # Copyright (c) 2008, Stuart J Colville # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the Muffin Research Labs nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY Stuart J Colville ``AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL Stuart J Colville BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os, sys, commands, re def listkeychains(): """ Returns a dictionary of all of the keychains found on the system """ k = commands.getoutput("security list-keychains") rx = re.compile(r'".*?/([\w]*)\.keychain"', re.I | re.M) keychains = {} for match in rx.finditer(k): keychains[match.group(1)]=match.group().strip('"') return keychains def createkeychain(newkeychain, password=None): if not password: from getpass import getpass password = getpass('Password:') newkeychain = newkeychain.find('.keychain') > -1 and newkeychain or '%s.keychain' % newkeychain k = commands.getstatusoutput("security create-keychain -p %s %s" % (password, newkeychain,)) if self.DEBUG: print k if k[0]: return False, 'Create creation failed' if k[0] == False: return keychain(newkeychain), 'Keychain created successfully' def checkkeychainname(keychain, list): """ Rationalises keychain strings as to whether they have .keychain or not and looks them up in the dictionary of keychains created at instantiation. Returns a string if successful and False if keychain is not available""" ext = '.keychain' if not keychain.find(ext): keychain += ext if keychain in list or keychain[0] == '/': return keychain return False class Keychain: DEBUG=False def __init__(self, keychainname): """ Keychain.py is a simple class allowing access to keychain data and settings. Keychain.py can also setup new keychains as required. As the keychain is only available on MaxOSX the module will raise ImportError if import is attempted on anything other than Mac OSX """ if sys.platform == 'darwin': self.keychains = listkeychains() else: raise ImportError('Keychain is only available on Mac OSX') self.keychain = checkkeychainname(keychainname, self.keychains) if not self.keychain: raise ValueError('Keychain not found') def getinternetpassword(self, item): """ Returns account + password pair from specified keychain item """ return self._getpassword(servername=item, type='internet') def getgenericpassword(self, item): """ Returns account + password pair from specified keychain item """ return self._getpassword(accountname=item, type='generic') def _getpassword(self, accountname=None, servername=None, type='generic'): args="security find-%s-password -g " % type if accountname: args += "-a %s " % accountname match = 'acct' if servername: args += "-s %s " % servername match = 'srvr' args += "%s " % self.keychain k = commands.getstatusoutput(args) if self.DEBUG: print k if k[0]: return False, 'The specified item could not be found', k else: rx1 = re.compile(r'"'+match+'"="(.*?)"', re.S) rx2 = re.compile(r'password: "(.*?)"', re.S) account = rx1.search(k[1]) password = rx2.search(k[1]) if account and password: return {match:account.group(1),"password":password.group(1)} else: return False def setgenericpassword(self, account, password, servicename=None): """ Create and store a generic account and password in the given keychain """ account = account and '-a %s' % (account,) or '' password = password and '-p %s' % (password,) or '' servicename = servicename and '-s %s' % (servicename,) or '' k = commands.getstatusoutput("security add-generic-password %s %s %s %s" % (account, password, servicename, self.keychain)) if self.DEBUG: print k if k[0]: return False, 'The specified password could not be added to %s' % self.keychain if k[0] == False: return True, 'Password added to %s successfully' % self.keychain def lockkeychain(self): k = commands.getstatusoutput("security lock-keychain %s" % (self.keychain,)) if self.DEBUG: print k if k[0]: return False, 'Keychain: %s could not be locked' % self.keychain if k[0] == False: return True, 'Keychain: %s locked successfully' % self.keychain def unlockkeychain(self, password=None): if not password: from getpass import getpass password = getpass('Password:') k = commands.getstatusoutput("security unlock-keychain -p %s %s" % (password, self.keychain,)) if self.DEBUG: print k if k[0]: return False, 'Keychain could not be unlocked' if k[0] == False: return True, 'Keychain unlocked successfully' def setkeychain(self, lock=True, timeout=0): """ Allows setting the keychain configuration. If lock is True the keychain will be locked on sleep. If the timeout is set to anything other than 0 the keychain will be set to lock after timeout seconds of inactivity """ lock = lock and '-l' or '' timeout = timeout and '-u -t %s' % (timeout,) or '' k = commands.getstatusoutput("security keychain-settings %s %s %s" % (lock, timeout, self.keychain,)) if self.DEBUG: print k if k[0]: return False, 'Keychain settings failed' if k[0] == False: return True, 'Keychain updated successfully' def showkeychaininfo(self): """Returns a dictionary containing the keychain settings""" k = commands.getstatusoutput("security show-keychain-info %s" % (self.keychain,)) if self.DEBUG: print k if k[0]: return False, 'Keychain could not be found' if k[0] == False: result = {} result['keychain'] = self.keychain if k[1].find('lock-on-sleep') > -1: result['lock-on-sleep'] = True if k[1].find('no-timeout') > -1: result['timeout'] = 0 else: rx = re.compile(r'timeout=(\d+)s', re.S) match = rx.search(k[1]) result['timeout'] = match.group(1) return result