[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