[Python-modules-commits] [keyrings.alt] 10/14: Add non-preferred keyring implementations from keyring 7.3
Dmitry Shachnev
mitya57 at moszumanska.debian.org
Wed Mar 2 07:21:14 UTC 2016
This is an automated email from the git hooks/post-receive script.
mitya57 pushed a commit to tag 1.0
in repository keyrings.alt.
commit ba43be9f57a66bf5193b486eeb2fb1713109b026
Author: Jason R. Coombs <jaraco at jaraco.com>
Date: Thu Jan 14 06:13:16 2016 -0500
Add non-preferred keyring implementations from keyring 7.3
---
keyrings/alt/Gnome.py | 108 +++++++++++++++
keyrings/alt/Google.py | 321 ++++++++++++++++++++++++++++++++++++++++++++
keyrings/alt/Windows.py | 193 ++++++++++++++++++++++++++
keyrings/alt/__init__.py | 0
keyrings/alt/_win_crypto.py | 101 ++++++++++++++
keyrings/alt/file.py | 295 ++++++++++++++++++++++++++++++++++++++++
keyrings/alt/keyczar.py | 99 ++++++++++++++
keyrings/alt/kwallet.py | 114 ++++++++++++++++
keyrings/alt/multi.py | 63 +++++++++
keyrings/alt/pyfs.py | 272 +++++++++++++++++++++++++++++++++++++
setup.py | 8 ++
11 files changed, 1574 insertions(+)
diff --git a/keyrings/alt/Gnome.py b/keyrings/alt/Gnome.py
new file mode 100644
index 0000000..16bca90
--- /dev/null
+++ b/keyrings/alt/Gnome.py
@@ -0,0 +1,108 @@
+try:
+ import gi
+ gi.require_version('GnomeKeyring', '1.0')
+ from gi.repository import GnomeKeyring
+except (ImportError, ValueError):
+ pass
+
+from keyring.backend import KeyringBackend
+from keyring.errors import PasswordSetError, PasswordDeleteError
+from keyring.util import properties
+from keyring.py27compat import unicode_str
+
+
+class Keyring(KeyringBackend):
+ """Gnome Keyring"""
+
+ KEYRING_NAME = None
+ """
+ Name of the keyring in which to store the passwords.
+ Use None for the default keyring.
+ """
+
+ @properties.ClassProperty
+ @classmethod
+ def priority(cls):
+ if 'GnomeKeyring' not in globals():
+ raise RuntimeError("GnomeKeyring module required")
+ result = GnomeKeyring.get_default_keyring_sync()[0]
+ if result != GnomeKeyring.Result.OK:
+ raise RuntimeError(result.value_name)
+ return 1
+
+ @property
+ def keyring_name(self):
+ system_default = GnomeKeyring.get_default_keyring_sync()[1]
+ return self.KEYRING_NAME or system_default
+
+ def _find_passwords(self, service, username, deleting=False):
+ """Get password of the username for the service
+ """
+ passwords = []
+
+ service = self._safe_string(service)
+ username = self._safe_string(username)
+ for attrs_tuple in (('username', 'service'), ('user', 'domain')):
+ attrs = GnomeKeyring.Attribute.list_new()
+ GnomeKeyring.Attribute.list_append_string(attrs, attrs_tuple[0], username)
+ GnomeKeyring.Attribute.list_append_string(attrs, attrs_tuple[1], service)
+ result, items = GnomeKeyring.find_items_sync(
+ GnomeKeyring.ItemType.NETWORK_PASSWORD, attrs)
+ if result == GnomeKeyring.Result.OK:
+ passwords += items
+ elif deleting:
+ if result == GnomeKeyring.Result.CANCELLED:
+ raise PasswordDeleteError("Cancelled by user")
+ elif result != GnomeKeyring.Result.NO_MATCH:
+ raise PasswordDeleteError(result.value_name)
+ return passwords
+
+ def get_password(self, service, username):
+ """Get password of the username for the service
+ """
+ items = self._find_passwords(service, username)
+ if not items:
+ return None
+
+ secret = items[0].secret
+ return secret if isinstance(secret, unicode_str) else secret.decode('utf-8')
+
+ def set_password(self, service, username, password):
+ """Set password for the username of the service
+ """
+ service = self._safe_string(service)
+ username = self._safe_string(username)
+ password = self._safe_string(password)
+ attrs = GnomeKeyring.Attribute.list_new()
+ GnomeKeyring.Attribute.list_append_string(attrs, 'username', username)
+ GnomeKeyring.Attribute.list_append_string(attrs, 'service', service)
+ GnomeKeyring.Attribute.list_append_string(attrs, 'application', 'python-keyring')
+ result = GnomeKeyring.item_create_sync(
+ self.keyring_name, GnomeKeyring.ItemType.NETWORK_PASSWORD,
+ "Password for '%s' on '%s'" % (username, service),
+ attrs, password, True)[0]
+ if result == GnomeKeyring.Result.CANCELLED:
+ # The user pressed "Cancel" when prompted to unlock their keyring.
+ raise PasswordSetError("Cancelled by user")
+ elif result != GnomeKeyring.Result.OK:
+ raise PasswordSetError(result.value_name)
+
+ def delete_password(self, service, username):
+ """Delete the password for the username of the service.
+ """
+ items = self._find_passwords(service, username, deleting=True)
+ if not items:
+ raise PasswordDeleteError("Password not found")
+ for current in items:
+ result = GnomeKeyring.item_delete_sync(current.keyring,
+ current.item_id)
+ if result == GnomeKeyring.Result.CANCELLED:
+ raise PasswordDeleteError("Cancelled by user")
+ elif result != GnomeKeyring.Result.OK:
+ raise PasswordDeleteError(result.value_name)
+
+ def _safe_string(self, source, encoding='utf-8'):
+ """Convert unicode to string as gnomekeyring barfs on unicode"""
+ if not isinstance(source, str):
+ return source.encode(encoding)
+ return str(source)
diff --git a/keyrings/alt/Google.py b/keyrings/alt/Google.py
new file mode 100644
index 0000000..66affe0
--- /dev/null
+++ b/keyrings/alt/Google.py
@@ -0,0 +1,321 @@
+from __future__ import absolute_import
+
+import os
+import sys
+import copy
+import codecs
+import base64
+import io
+
+try:
+ import gdata.docs.service
+except ImportError:
+ pass
+
+from . import keyczar
+from keyring import errors
+from keyring import credentials
+from keyring.py27compat import input, pickle
+from keyring.backend import KeyringBackend
+from keyring.util import properties
+from keyring.errors import ExceptionRaisedContext
+
+class EnvironCredential(credentials.EnvironCredential):
+ """Retrieve credentials from specifically named environment variables
+ """
+
+ def __init__(self):
+ super(EnvironCredential, self).__init__('GOOGLE_KEYRING_USER',
+ 'GOOGLE_KEYRING_PASSWORD')
+
+class DocsKeyring(KeyringBackend):
+ """Backend that stores keyring on Google Docs.
+ Note that login and any other initialisation is deferred until it is
+ actually required to allow this keyring class to be added to the
+ global _all_keyring list.
+ """
+
+ keyring_title = 'GoogleKeyring'
+ # status enums
+ OK = 1
+ FAIL = 0
+ CONFLICT = -1
+
+ def __init__(self, credential, source, crypter,
+ collection=None, client=None,
+ can_create=True, input_getter=input
+ ):
+ self.credential = credential
+ self.crypter = crypter
+ self.source = source
+ self._collection = collection
+ self.can_create = can_create
+ self.input_getter = input_getter
+ self._keyring_dict = None
+
+ if not client:
+ self._client = gdata.docs.service.DocsService()
+ else:
+ self._client = client
+
+ self._client.source = source
+ self._client.ssl = True
+ self._login_reqd = True
+
+ @properties.ClassProperty
+ @classmethod
+ def priority(cls):
+ if not cls._has_gdata():
+ raise RuntimeError("Requires gdata")
+ if not keyczar.has_keyczar():
+ raise RuntimeError("Requires keyczar")
+ return 3
+
+ @classmethod
+ def _has_gdata(cls):
+ with ExceptionRaisedContext() as exc:
+ gdata.__name__
+ return not bool(exc)
+
+ def get_password(self, service, username):
+ """Get password of the username for the service
+ """
+ result = self._get_entry(self._keyring, service, username)
+ if result:
+ result = self._decrypt(result)
+ return result
+
+ def set_password(self, service, username, password):
+ """Set password for the username of the service
+ """
+ password = self._encrypt(password or '')
+ keyring_working_copy = copy.deepcopy(self._keyring)
+ service_entries = keyring_working_copy.get(service)
+ if not service_entries:
+ service_entries = {}
+ keyring_working_copy[service] = service_entries
+ service_entries[username] = password
+ save_result = self._save_keyring(keyring_working_copy)
+ if save_result == self.OK:
+ self._keyring_dict = keyring_working_copy
+ return
+ elif save_result == self.CONFLICT:
+ # check if we can avoid updating
+ self.docs_entry, keyring_dict = self._read()
+ existing_pwd = self._get_entry(self._keyring, service, username)
+ conflicting_pwd = self._get_entry(keyring_dict, service, username)
+ if conflicting_pwd == password:
+ # if someone else updated it to the same value then we are done
+ self._keyring_dict = keyring_working_copy
+ return
+ elif conflicting_pwd is None or conflicting_pwd == existing_pwd:
+ # if doesn't already exist or is unchanged then update it
+ new_service_entries = keyring_dict.get(service, {})
+ new_service_entries[username] = password
+ keyring_dict[service] = new_service_entries
+ save_result = self._save_keyring(keyring_dict)
+ if save_result == self.OK:
+ self._keyring_dict = keyring_dict
+ return
+ else:
+ raise errors.PasswordSetError(
+ 'Failed write after conflict detected')
+ else:
+ raise errors.PasswordSetError(
+ 'Conflict detected, service:%s and username:%s was '\
+ 'set to a different value by someone else' %(service,
+ username))
+
+ raise errors.PasswordSetError('Could not save keyring')
+
+ def delete_password(self, service, username):
+ return self._del_entry(self._keyring, service, username)
+
+ @property
+ def client(self):
+ if not self._client.GetClientLoginToken():
+ try:
+ self._client.ClientLogin(self.credential.username,
+ self.credential.password,
+ self._client.source)
+ except gdata.service.CaptchaRequired:
+ sys.stdout.write('Please visit ' + self._client.captcha_url)
+ answer = self.input_getter('Answer to the challenge? ')
+ self._client.email = self.credential.username
+ self._client.password = self.credential.password
+ self._client.ClientLogin(
+ self.credential.username,
+ self.credential.password,
+ self._client.source,
+ captcha_token=self._client.captcha_token,
+ captcha_response=answer)
+ except gdata.service.BadAuthentication:
+ raise errors.InitError('Users credential were unrecognized')
+ except gdata.service.Error:
+ raise errors.InitError('Login Error')
+
+ return self._client
+
+ @property
+ def collection(self):
+ return self._collection or self.credential.username.split('@')[0]
+
+ @property
+ def _keyring(self):
+ if self._keyring_dict is None:
+ self.docs_entry, self._keyring_dict = self._read()
+ return self._keyring_dict
+
+ def _get_entry(self, keyring_dict, service, username):
+ result = None
+ service_entries = keyring_dict.get(service)
+ if service_entries:
+ result = service_entries.get(username)
+ return result
+
+ def _del_entry(self, keyring_dict, service, username):
+ service_entries = keyring_dict.get(service)
+ if not service_entries:
+ raise errors.PasswordDeleteError("No matching service")
+ try:
+ del service_entries[username]
+ except KeyError:
+ raise errors.PasswordDeleteError("Not found")
+ if not service_entries:
+ del keyring_dict[service]
+
+ def _decrypt(self, value):
+ if not value:
+ return ''
+ return self.crypter.decrypt(value)
+
+ def _encrypt(self, value):
+ if not value:
+ return ''
+ return self.crypter.encrypt(value)
+
+ def _get_doc_title(self):
+ return '%s' %self.keyring_title
+
+ def _read(self):
+ from gdata.docs.service import DocumentQuery
+ title_query = DocumentQuery(categories=[self.collection])
+ title_query['title'] = self._get_doc_title()
+ title_query['title-exact'] = 'true'
+ docs = self.client.QueryDocumentListFeed(title_query.ToUri())
+
+ if not docs.entry:
+ if self.can_create:
+ docs_entry = None
+ keyring_dict = {}
+ else:
+ raise errors.InitError(
+ '%s not found in %s and create not permitted'
+ %(self._get_doc_title(), self.collection))
+ else:
+ docs_entry = docs.entry[0]
+ file_contents = ''
+ try:
+ url = docs_entry.content.src
+ url += '&exportFormat=txt'
+ server_response = self.client.request('GET', url)
+ if server_response.status != 200:
+ raise errors.InitError(
+ 'Could not read existing Google Docs keyring')
+ file_contents = server_response.read()
+ if file_contents.startswith(codecs.BOM_UTF8):
+ file_contents = file_contents[len(codecs.BOM_UTF8):]
+ keyring_dict = pickle.loads(base64.urlsafe_b64decode(
+ file_contents.decode('string-escape')))
+ except pickle.UnpicklingError as ex:
+ raise errors.InitError(
+ 'Could not unpickle existing Google Docs keyring', ex)
+ except TypeError as ex:
+ raise errors.InitError(
+ 'Could not decode existing Google Docs keyring', ex)
+
+ return docs_entry, keyring_dict
+
+ def _save_keyring(self, keyring_dict):
+ """Helper to actually write the keyring to Google"""
+ import gdata
+ result = self.OK
+ file_contents = base64.urlsafe_b64encode(pickle.dumps(keyring_dict))
+ try:
+ if self.docs_entry:
+ extra_headers = {'Content-Type': 'text/plain',
+ 'Content-Length': len(file_contents)}
+ self.docs_entry = self.client.Put(
+ file_contents,
+ self.docs_entry.GetEditMediaLink().href,
+ extra_headers=extra_headers
+ )
+ else:
+ from gdata.docs.service import DocumentQuery
+ # check for existence of folder, create if required
+ folder_query = DocumentQuery(categories=['folder'])
+ folder_query['title'] = self.collection
+ folder_query['title-exact'] = 'true'
+ docs = self.client.QueryDocumentListFeed(folder_query.ToUri())
+ if docs.entry:
+ folder_entry = docs.entry[0]
+ else:
+ folder_entry = self.client.CreateFolder(self.collection)
+ file_handle = io.BytesIO(file_contents)
+ media_source = gdata.MediaSource(
+ file_handle=file_handle,
+ content_type='text/plain',
+ content_length=len(file_contents),
+ file_name='temp')
+ self.docs_entry = self.client.Upload(
+ media_source,
+ self._get_doc_title(),
+ folder_or_uri=folder_entry
+ )
+ except gdata.service.RequestError as ex:
+ try:
+ if ex.message['reason'].lower().find('conflict') != -1:
+ result = self.CONFLICT
+ else:
+ # Google docs has a bug when updating a shared document
+ # using PUT from any account other that the owner.
+ # It returns an error 400 "Sorry, there was an error saving the file. Please try again"
+ # *despite* actually updating the document!
+ # Workaround by re-reading to see if it actually updated
+ if ex.message['body'].find(
+ 'Sorry, there was an error saving the file') != -1:
+ new_docs_entry, new_keyring_dict = self._read()
+ if new_keyring_dict == keyring_dict:
+ result = self.OK
+ else:
+ result = self.FAIL
+ else:
+ result = self.FAIL
+ except:
+ result = self.FAIL
+
+ return result
+
+class KeyczarDocsKeyring(DocsKeyring):
+ """Google Docs keyring using keyczar initialized from environment
+ variables
+ """
+
+ def __init__(self):
+ crypter = keyczar.EnvironCrypter()
+ credential = EnvironCredential()
+ source = os.environ.get('GOOGLE_KEYRING_SOURCE')
+ super(KeyczarDocsKeyring, self).__init__(
+ credential, source, crypter)
+
+ def supported(self):
+ """Return if this keyring supports current environment:
+ -1: not applicable
+ 0: suitable
+ 1: recommended
+ """
+ try:
+ from keyczar import keyczar
+ return super(KeyczarDocsKeyring, self).supported()
+ except ImportError:
+ return -1
diff --git a/keyrings/alt/Windows.py b/keyrings/alt/Windows.py
new file mode 100644
index 0000000..1b591c0
--- /dev/null
+++ b/keyrings/alt/Windows.py
@@ -0,0 +1,193 @@
+import sys
+import base64
+import platform
+import functools
+
+from keyring.util import properties
+from keyring.backend import KeyringBackend
+from keyring.errors import PasswordDeleteError, ExceptionRaisedContext
+from . import file
+
+try:
+ # prefer pywin32-ctypes
+ from win32ctypes import pywintypes
+ from win32ctypes import win32cred
+ # force demand import to raise ImportError
+ win32cred.__name__
+except ImportError:
+ # fallback to pywin32
+ try:
+ import pywintypes
+ import win32cred
+ except ImportError:
+ pass
+
+try:
+ import winreg
+except ImportError:
+ try:
+ # Python 2 compatibility
+ import _winreg as winreg
+ except ImportError:
+ pass
+
+try:
+ from . import _win_crypto
+except ImportError:
+ pass
+
+def has_pywin32():
+ """
+ Does this environment have pywin32?
+ Should return False even when Mercurial's Demand Import allowed import of
+ win32cred.
+ """
+ with ExceptionRaisedContext() as exc:
+ win32cred.__name__
+ return not bool(exc)
+
+def has_wincrypto():
+ """
+ Does this environment have wincrypto?
+ Should return False even when Mercurial's Demand Import allowed import of
+ _win_crypto, so accesses an attribute of the module.
+ """
+ with ExceptionRaisedContext() as exc:
+ _win_crypto.__name__
+ return not bool(exc)
+
+class EncryptedKeyring(file.BaseKeyring):
+ """
+ A File-based keyring secured by Windows Crypto API.
+ """
+
+ @properties.ClassProperty
+ @classmethod
+ def priority(self):
+ """
+ Preferred over file.EncryptedKeyring but not other, more sophisticated
+ Windows backends.
+ """
+ if not platform.system() == 'Windows':
+ raise RuntimeError("Requires Windows")
+ return .8
+
+ filename = 'wincrypto_pass.cfg'
+
+ def encrypt(self, password):
+ """Encrypt the password using the CryptAPI.
+ """
+ return _win_crypto.encrypt(password)
+
+ def decrypt(self, password_encrypted):
+ """Decrypt the password using the CryptAPI.
+ """
+ return _win_crypto.decrypt(password_encrypted)
+
+
+class RegistryKeyring(KeyringBackend):
+ """
+ RegistryKeyring is a keyring which use Windows CryptAPI to encrypt
+ the user's passwords and store them under registry keys
+ """
+
+ @properties.ClassProperty
+ @classmethod
+ def priority(self):
+ """
+ Preferred on Windows when pywin32 isn't installed
+ """
+ if platform.system() != 'Windows':
+ raise RuntimeError("Requires Windows")
+ if not has_wincrypto():
+ raise RuntimeError("Requires ctypes")
+ return 2
+
+ def get_password(self, service, username):
+ """Get password of the username for the service
+ """
+ try:
+ # fetch the password
+ key = r'Software\%s\Keyring' % service
+ hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key)
+ password_saved = winreg.QueryValueEx(hkey, username)[0]
+ password_base64 = password_saved.encode('ascii')
+ # decode with base64
+ password_encrypted = base64.decodestring(password_base64)
+ # decrypted the password
+ password = _win_crypto.decrypt(password_encrypted).decode('utf-8')
+ except EnvironmentError:
+ password = None
+ return password
+
+ def set_password(self, service, username, password):
+ """Write the password to the registry
+ """
+ # encrypt the password
+ password_encrypted = _win_crypto.encrypt(password.encode('utf-8'))
+ # encode with base64
+ password_base64 = base64.encodestring(password_encrypted)
+ # encode again to unicode
+ password_saved = password_base64.decode('ascii')
+
+ # store the password
+ key_name = r'Software\%s\Keyring' % service
+ hkey = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_name)
+ winreg.SetValueEx(hkey, username, 0, winreg.REG_SZ, password_saved)
+
+ def delete_password(self, service, username):
+ """Delete the password for the username of the service.
+ """
+ try:
+ key_name = r'Software\%s\Keyring' % service
+ hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_name, 0,
+ winreg.KEY_ALL_ACCESS)
+ winreg.DeleteValue(hkey, username)
+ winreg.CloseKey(hkey)
+ except WindowsError:
+ e = sys.exc_info()[1]
+ raise PasswordDeleteError(e)
+ self._delete_key_if_empty(service)
+
+ def _delete_key_if_empty(self, service):
+ key_name = r'Software\%s\Keyring' % service
+ key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_name, 0,
+ winreg.KEY_ALL_ACCESS)
+ try:
+ winreg.EnumValue(key, 0)
+ return
+ except WindowsError:
+ pass
+ winreg.CloseKey(key)
+
+ # it's empty; delete everything
+ while key_name != 'Software':
+ parent, sep, base = key_name.rpartition('\\')
+ key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, parent, 0,
+ winreg.KEY_ALL_ACCESS)
+ winreg.DeleteKey(key, base)
+ winreg.CloseKey(key)
+ key_name = parent
+
+
+class OldPywinError(object):
+ """
+ A compatibility wrapper for old PyWin32 errors, such as reported in
+ https://bitbucket.org/kang/python-keyring-lib/issue/140/
+ """
+ def __init__(self, orig):
+ self.orig = orig
+
+ @property
+ def funcname(self):
+ return self.orig[1]
+
+ @property
+ def winerror(self):
+ return self.orig[0]
+
+ @classmethod
+ def wrap(cls, orig_err):
+ attr_check = functools.partial(hasattr, orig_err)
+ is_old = not all(map(attr_check, ['funcname', 'winerror']))
+ return cls(orig_err) if is_old else orig_err
diff --git a/keyrings/alt/__init__.py b/keyrings/alt/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/keyrings/alt/_win_crypto.py b/keyrings/alt/_win_crypto.py
new file mode 100644
index 0000000..b23d636
--- /dev/null
+++ b/keyrings/alt/_win_crypto.py
@@ -0,0 +1,101 @@
+
+from ctypes import Structure, POINTER, c_void_p, cast, create_string_buffer, \
+ c_char_p, byref, memmove
+from ctypes import windll, WinDLL, WINFUNCTYPE
+try:
+ from ctypes import wintypes
+except ValueError:
+ # see http://bugs.python.org/issue16396
+ raise ImportError("wintypes")
+
+from ..util.escape import u
+
+# Crypto API ctypes bindings
+
+class DATA_BLOB(Structure):
+ _fields_ = [('cbData', wintypes.DWORD),
+ ('pbData', POINTER(wintypes.BYTE))]
+
+
+class CRYPTPROTECT_PROMPTSTRUCT(Structure):
+ _fields_ = [('cbSize', wintypes.DWORD),
+ ('dwPromptFlags', wintypes.DWORD),
+ ('hwndApp', wintypes.HWND),
+ ('szPrompt', POINTER(wintypes.WCHAR))]
+
+# Flags for CRYPTPROTECT_PROMPTSTRUCT
+
+CRYPTPROTECT_PROMPT_ON_UNPROTECT = 1
+CRYPTPROTECT_PROMPT_ON_PROTECT = 2
+
+# Flags for CryptProtectData/CryptUnprotectData
+
+CRYPTPROTECT_UI_FORBIDDEN = 0x01
+CRYPTPROTECT_LOCAL_MACHINE = 0x04
+CRYPTPROTECT_CRED_SYNC = 0x08
+CRYPTPROTECT_AUDIT = 0x10
+CRYPTPROTECT_NO_RECOVERY = 0x20
+CRYPTPROTECT_VERIFY_PROTECTION = 0x40
+CRYPTPROTECT_CRED_REGENERATE = 0x80
+
+# Crypto API Functions
+
+_dll = WinDLL('CRYPT32.DLL')
+
+CryptProtectData = WINFUNCTYPE(wintypes.BOOL,
+ POINTER(DATA_BLOB),
+ POINTER(wintypes.WCHAR),
+ POINTER(DATA_BLOB),
+ c_void_p,
+ POINTER(CRYPTPROTECT_PROMPTSTRUCT),
+ wintypes.DWORD,
+ POINTER(DATA_BLOB))(('CryptProtectData', _dll))
+
+CryptUnprotectData = WINFUNCTYPE(wintypes.BOOL,
+ POINTER(DATA_BLOB),
+ POINTER(wintypes.WCHAR),
+ POINTER(DATA_BLOB),
+ c_void_p,
+ POINTER(CRYPTPROTECT_PROMPTSTRUCT),
+ wintypes.DWORD, POINTER(DATA_BLOB))(
+ ('CryptUnprotectData', _dll))
+
+# Functions
+
+
+def encrypt(data, non_interactive=0):
+ blobin = DATA_BLOB(cbData=len(data),
+ pbData=cast(c_char_p(data),
+ POINTER(wintypes.BYTE)))
+ blobout = DATA_BLOB()
+
+ if not CryptProtectData(byref(blobin),
+ u('python-keyring-lib.win32crypto'),
+ None, None, None,
+ CRYPTPROTECT_UI_FORBIDDEN,
+ byref(blobout)):
+ raise OSError("Can't encrypt")
+
+ encrypted = create_string_buffer(blobout.cbData)
+ memmove(encrypted, blobout.pbData, blobout.cbData)
+ windll.kernel32.LocalFree(blobout.pbData)
+ return encrypted.raw
+
+
+def decrypt(encrypted, non_interactive=0):
+ blobin = DATA_BLOB(cbData=len(encrypted),
+ pbData=cast(c_char_p(encrypted),
+ POINTER(wintypes.BYTE)))
+ blobout = DATA_BLOB()
+
+ if not CryptUnprotectData(byref(blobin),
+ u('python-keyring-lib.win32crypto'),
+ None, None, None,
+ CRYPTPROTECT_UI_FORBIDDEN,
+ byref(blobout)):
+ raise OSError("Can't decrypt")
+
+ data = create_string_buffer(blobout.cbData)
+ memmove(data, blobout.pbData, blobout.cbData)
+ windll.kernel32.LocalFree(blobout.pbData)
+ return data.raw
diff --git a/keyrings/alt/file.py b/keyrings/alt/file.py
new file mode 100644
index 0000000..89563c8
--- /dev/null
+++ b/keyrings/alt/file.py
@@ -0,0 +1,295 @@
+from __future__ import with_statement
+
+import os
+import getpass
+import base64
+import sys
+import json
+import abc
+
+from keyring.py27compat import configparser
+
+from keyring.errors import PasswordDeleteError
+from keyring.backend import KeyringBackend
+from keyring.util import platform_, properties
+from keyring.util.escape import escape as escape_for_ini
+
+
+class FileBacked(object):
+ @abc.abstractproperty
+ def filename(self):
+ """
+ The filename used to store the passwords.
+ """
+
+ @properties.NonDataProperty
+ def file_path(self):
+ """
+ The path to the file where passwords are stored. This property
+ may be overridden by the subclass or at the instance level.
+ """
+ return os.path.join(platform_.data_root(), self.filename)
+
+
+class BaseKeyring(FileBacked, KeyringBackend):
+ """
+ BaseKeyring is a file-based implementation of keyring.
+
+ This keyring stores the password directly in the file and provides methods
+ which may be overridden by subclasses to support
+ encryption and decryption. The encrypted payload is stored in base64
+ format.
+ """
+
+ @abc.abstractmethod
+ def encrypt(self, password):
+ """
+ Given a password (byte string), return an encrypted byte string.
+ """
+
+ @abc.abstractmethod
+ def decrypt(self, password_encrypted):
+ """
+ Given a password encrypted by a previous call to `encrypt`, return
+ the original byte string.
+ """
+
+ def get_password(self, service, username):
+ """
+ Read the password from the file.
+ """
+ service = escape_for_ini(service)
+ username = escape_for_ini(username)
+
+ # load the passwords from the file
+ config = configparser.RawConfigParser()
+ if os.path.exists(self.file_path):
+ config.read(self.file_path)
+
+ # fetch the password
+ try:
+ password_base64 = config.get(service, username).encode()
+ # decode with base64
+ password_encrypted = base64.decodestring(password_base64)
+ # decrypted the password
+ password = self.decrypt(password_encrypted).decode('utf-8')
+ except (configparser.NoOptionError, configparser.NoSectionError):
+ password = None
+ return password
+
+ def set_password(self, service, username, password):
+ """Write the password in the file.
+ """
+ service = escape_for_ini(service)
+ username = escape_for_ini(username)
+
+ # encrypt the password
+ password_encrypted = self.encrypt(password.encode('utf-8'))
+ # encode with base64
+ password_base64 = base64.encodestring(password_encrypted).decode()
+
+ # ensure the file exists
+ self._ensure_file_path()
+
+ # load the keyring from the disk
+ config = configparser.RawConfigParser()
+ config.read(self.file_path)
+
+ # update the keyring with the password
+ if not config.has_section(service):
+ config.add_section(service)
+ config.set(service, username, password_base64)
+
+ # save the keyring back to the file
+ with open(self.file_path, 'w') as config_file:
+ config.write(config_file)
+
+ def _ensure_file_path(self):
+ """
+ Ensure the storage path exists.
+ If it doesn't, create it with "go-rwx" permissions.
+ """
+ storage_root = os.path.dirname(self.file_path)
+ if storage_root and not os.path.isdir(storage_root):
+ os.makedirs(storage_root)
+ if not os.path.isfile(self.file_path):
+ # create the file without group/world permissions
+ with open(self.file_path, 'w'):
+ pass
+ user_read_write = 0o600
+ os.chmod(self.file_path, user_read_write)
+
+ def delete_password(self, service, username):
+ """Delete the password for the username of the service.
+ """
+ service = escape_for_ini(service)
+ username = escape_for_ini(username)
+ config = configparser.RawConfigParser()
+ if os.path.exists(self.file_path):
+ config.read(self.file_path)
+ try:
+ if not config.remove_option(service, username):
+ raise PasswordDeleteError("Password not found")
+ except configparser.NoSectionError:
+ raise PasswordDeleteError("Password not found")
+ # update the file
+ with open(self.file_path, 'w') as config_file:
+ config.write(config_file)
+
+class PlaintextKeyring(BaseKeyring):
+ """Simple File Keyring with no encryption"""
+
+ priority = .5
+ "Applicable for all platforms, but not recommended"
+
+ filename = 'keyring_pass.cfg'
+
+ def encrypt(self, password):
+ """Directly return the password itself.
+ """
+ return password
+
+ def decrypt(self, password_encrypted):
+ """Directly return encrypted password.
+ """
+ return password_encrypted
+
+class Encrypted(object):
+ """
+ PyCrypto-backed Encryption support
+ """
+
+ block_size = 32
+
+ def _create_cipher(self, password, salt, IV):
+ """
+ Create the cipher object to encrypt or decrypt a payload.
+ """
+ from Crypto.Protocol.KDF import PBKDF2
+ from Crypto.Cipher import AES
+ pw = PBKDF2(password, salt, dkLen=self.block_size)
+ return AES.new(pw[:self.block_size], AES.MODE_CFB, IV)
+
+ def _get_new_password(self):
+ while True:
+ password = getpass.getpass(
+ "Please set a password for your new keyring: ")
+ confirm = getpass.getpass('Please confirm the password: ')
+ if password != confirm:
+ sys.stderr.write("Error: Your passwords didn't match\n")
+ continue
+ if '' == password.strip():
+ # forbid the blank password
+ sys.stderr.write("Error: blank passwords aren't allowed.\n")
+ continue
+ return password
+
+
+class EncryptedKeyring(Encrypted, BaseKeyring):
+ """PyCrypto File Keyring"""
+
+ filename = 'crypted_pass.cfg'
+ pw_prefix = 'pw:'.encode()
+
+ @properties.ClassProperty
+ @classmethod
+ def priority(self):
+ "Applicable for all platforms, but not recommended."
+ try:
+ __import__('Crypto.Cipher.AES')
+ __import__('Crypto.Protocol.KDF')
+ __import__('Crypto.Random')
+ except ImportError:
+ raise RuntimeError("PyCrypto required")
+ if not json:
+ raise RuntimeError("JSON implementation such as simplejson "
+ "required.")
+ return .6
+
+ @properties.NonDataProperty
+ def keyring_key(self):
+ # _unlock or _init_file will set the key or raise an exception
+ if self._check_file():
+ self._unlock()
+ else:
+ self._init_file()
+ return self.keyring_key
+
+ def _init_file(self):
+ """
+ Initialize a new password file and set the reference password.
+ """
+ self.keyring_key = self._get_new_password()
+ # set a reference password, used to check that the password provided
+ # matches for subsequent checks.
+ self.set_password('keyring-setting', 'password reference',
... 661 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/keyrings.alt.git
More information about the Python-modules-commits
mailing list