SecretStorage-2.0.0/0000755000175000017500000000000012271523347015133 5ustar dmitrydmitry00000000000000SecretStorage-2.0.0/PKG-INFO0000644000175000017500000000625512271523347016240 0ustar dmitrydmitry00000000000000Metadata-Version: 1.1 Name: SecretStorage Version: 2.0.0 Summary: Secure storing of passwords and other secrets Home-page: http://launchpad.net/python-secretstorage Author: Dmitry Shachnev Author-email: mitya57@gmail.com License: BSD Description: Module description ================== This module provides a way for securely storing passwords and other secrets. It uses D-Bus `Secret Service`_ API that is supported by GNOME Keyring (>= 2.30) and KSecretsService. The main classes provided are ``secretstorage.Item``, representing a secret item (that has a *label*, a *secret* and some *attributes*) and ``secretstorage.Collection``, a place items are stored in. SecretStorage supports most of the functions provided by Secret Service, including creating and deleting items and collections, editing items, locking and unlocking collections (asynchronous unlocking is also supported). The documentation can be found on `pythonhosted.org`_. .. _`Secret Service`: http://standards.freedesktop.org/secret-service/ .. _`pythonhosted.org`: http://pythonhosted.org/SecretStorage/ Building the module =================== .. note:: SecretStorage supports all versions of Python since 2.6. Here we assume that your Python version is 3.x. SecretStorage requires these packages to work: * `dbus-python`_ (available in Debian-based distributions in `python3-dbus package`_); * PyCrypto_ (available in Debian-based distributions in `python3-crypto package`_). To build SecretStorage, use this command:: python3 setup.py build If you have Sphinx_ installed, you can also build the documentation:: python3 setup.py build_sphinx .. _`dbus-python`: http://www.freedesktop.org/wiki/Software/DBusBindings#dbus-python .. _PyCrypto: https://www.dlitz.net/software/pycrypto/ .. _`python3-dbus package`: http://packages.debian.org/sid/python3-dbus .. _`python3-crypto package`: http://packages.debian.org/sid/python3-crypto .. _Sphinx: http://sphinx-doc.org/ Get the code ============ SecretStorage is available under BSD license. The source code can be found on GitHub_. .. _GitHub: https://github.com/mitya57/secretstorage Platform: Linux Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Security Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires: dbus Requires: Crypto SecretStorage-2.0.0/docs/0000755000175000017500000000000012271523347016063 5ustar dmitrydmitry00000000000000SecretStorage-2.0.0/docs/util.rst0000644000175000017500000000033512271516047017572 0ustar dmitrydmitry00000000000000============================ Additional utility functions ============================ .. automodule:: secretstorage.util :members: format_secret, exec_prompt, exec_prompt_glib, exec_prompt_qt SecretStorage-2.0.0/docs/exceptions.rst0000644000175000017500000000016312271516047020775 0ustar dmitrydmitry00000000000000=================== Possible exceptions =================== .. automodule:: secretstorage.exceptions :members: SecretStorage-2.0.0/docs/collection.rst0000644000175000017500000000025712271516047020753 0ustar dmitrydmitry00000000000000======================================= The ``secretstorage.collection`` module ======================================= .. automodule:: secretstorage.collection :members: SecretStorage-2.0.0/docs/conf.py0000644000175000017500000001725612271516047017374 0ustar dmitrydmitry00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # SecretStorage documentation build configuration file, created by # sphinx-quickstart on Fri Dec 28 17:17:46 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'SecretStorage' copyright = '2013, Dmitry Shachnev' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.1' # The full version, including alpha/beta/rc tags. release = '1.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'SecretStorageDoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'SecretStorage.tex', 'SecretStorage Documentation', 'Dmitry Shachnev', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'secretstorage', 'SecretStorage Documentation', ['Dmitry Shachnev'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'SecretStorage', 'SecretStorage Documentation', 'Dmitry Shachnev', 'SecretStorage', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' SecretStorage-2.0.0/docs/index.rst0000644000175000017500000000517212271517202017722 0ustar dmitrydmitry00000000000000======================================= Welcome to SecretStorage documentation! ======================================= This module provides a way for securely storing passwords and other secrets. It uses `D-Bus`_-based FreeDesktop.org `Secret Service`_ standard that is, for example, supported by `GNOME Keyring`_ (since version 2.30) and by KSecretsService_. It allows one to create new secret items, delete and search for passwords matching given attributes. It also supports graphical prompts when unlocking is needed. .. _`D-Bus`: http://www.freedesktop.org/wiki/Software/dbus .. _`Secret Service`: http://standards.freedesktop.org/secret-service/ .. _`GNOME Keyring`: https://live.gnome.org/GnomeKeyring .. _KSecretsService: http://techbase.kde.org/Projects/Utils/ksecretsservice SecretStorage code is hosted on GitHub_. .. _GitHub: https://github.com/mitya57/secretstorage Initializing D-Bus ================== .. seealso:: If you don't know how D-Bus works, please read `Introduction to D-Bus`_ firstly. .. _`Introduction to D-Bus`: http://www.freedesktop.org/wiki/IntroductionToDBus Before using SecretStorage, you need to initialize D-Bus. This can be done using this function: .. autofunction:: secretstorage.dbus_init Examples of using SecretStorage =============================== Creating a new item in the default collection: >>> import secretstorage >>> bus = secretstorage.dbus_init() >>> collection = secretstorage.Collection(bus) >>> attributes = {'application': 'myapp', 'another attribute': ... 'another value'} >>> item = collection.create_item('My first item', attributes, ... b'pa$$word') Getting item's label, attributes and secret: >>> item = collection.create_item('My first item', attributes, b'pa$$word') >>> item.get_label() 'My first item' >>> item.get_attributes() {'another attribute': 'another value', 'application': 'myapp'} >>> item.get_secret() b'pa$$word' Locking and unlocking collections: >>> collection.lock() >>> collection.is_locked() True >>> collection.unlock() >>> collection.is_locked() False Asynchronously unlocking the collection (the GLib main loop is used here, Qt loop is also supported): >>> from gi.repository import GLib >>> loop = GLib.MainLoop() >>> def callback(dismissed, unlocked): ... print('dismissed:', dismissed) ... print('unlocked:', unlocked) ... loop.quit() ... >>> collection.unlock(callback); loop.run() dismissed: False unlocked: [dbus.ObjectPath('/org/freedesktop/secrets/aliases/default')] Contents ======== .. toctree:: :maxdepth: 2 collection item util exceptions Indices and tables ================== * :ref:`genindex` * :ref:`search` SecretStorage-2.0.0/docs/item.rst0000644000175000017500000000022712271516047017553 0ustar dmitrydmitry00000000000000================================= The ``secretstorage.item`` module ================================= .. automodule:: secretstorage.item :members: SecretStorage-2.0.0/tests/0000755000175000017500000000000012271523347016275 5ustar dmitrydmitry00000000000000SecretStorage-2.0.0/tests/test_compat_functions.py0000644000175000017500000000257012271521230023252 0ustar dmitrydmitry00000000000000# Tests for SecretStorage # Author: Dmitry Shachnev, 2013 # License: BSD # This file tests the compatibility functions in __init__.py. import secretstorage import random import unittest rand = str(random.randint(0, 1000)) ATTRIBUTES = {'application': 'secretstorage-test', 'attribute': rand} PASSWORD = b'pa$$word' class CompatFunctionsTest(unittest.TestCase): """A test case that tests compatibility functions, based on old SecretStorage test.""" @classmethod def setUpClass(cls): cls.item_id = secretstorage.create_item('Test item', ATTRIBUTES, PASSWORD) def test_get_items(self): attrs, secret = secretstorage.get_items(ATTRIBUTES)[-1] self.assertEqual(attrs['application'], 'secretstorage-test') self.assertEqual(secret, PASSWORD) def test_get_items_ids(self): item_id = secretstorage.get_items_ids(ATTRIBUTES)[-1] self.assertEqual(item_id, self.item_id) def test_get_item(self): attrs, secret = secretstorage.get_item(self.item_id) self.assertEqual(attrs['application'], 'secretstorage-test') self.assertEqual(secret, PASSWORD) def test_get_item_attributes(self): attrs = secretstorage.get_item_attributes(self.item_id) self.assertEqual(attrs['application'], 'secretstorage-test') self.assertEqual(attrs['attribute'], rand) @classmethod def tearDownClass(cls): secretstorage.delete_item(cls.item_id) if __name__ == '__main__': unittest.main() SecretStorage-2.0.0/tests/test_item.py0000644000175000017500000000534412271521230020637 0ustar dmitrydmitry00000000000000# Tests for SecretStorage # Author: Dmitry Shachnev, 2013 # License: BSD # This file tests the secretstorage.Collection class. import unittest import time from secretstorage import dbus_init, search_items, Collection ATTRIBUTES = {'application': 'secretstorage-test', 'attribute': 'qwerty'} NEW_ATTRIBUTES = {'application': 'secretstorage-test', 'newattribute': 'asdfgh'} class ItemTest(unittest.TestCase): """A test case that tests that all common methods of Item class work and do not crash.""" @classmethod def setUpClass(cls): cls.bus = dbus_init(main_loop=False) cls.collection = Collection(cls.bus) cls.created_timestamp = time.time() cls.item = cls.collection.create_item('My item', ATTRIBUTES, b'pa$$word') cls.other_item = cls.collection.create_item('My item', ATTRIBUTES, '', content_type='data/null') def test_equal(self): self.assertEqual(self.item, self.item) self.assertNotEqual(self.item, self.other_item) self.assertEqual(self.other_item, self.other_item) def test_searchable(self): search_results = self.collection.search_items(ATTRIBUTES) self.assertIn(self.item, search_results) search_results = search_items(self.bus, ATTRIBUTES) self.assertIn(self.item, search_results) def test_item_in_all_items(self): all_items = self.collection.get_all_items() self.assertIn(self.item, all_items) def test_attributes(self): attributes = self.item.get_attributes() for key in ATTRIBUTES: self.assertEqual(ATTRIBUTES[key], attributes[key]) self.item.set_attributes(NEW_ATTRIBUTES) attributes = self.item.get_attributes() for key in NEW_ATTRIBUTES: self.assertEqual(NEW_ATTRIBUTES[key], attributes[key]) self.item.set_attributes(ATTRIBUTES) def test_label(self): self.assertEqual(self.item.get_label(), 'My item') self.item.set_label('Hello!') self.assertEqual(self.item.get_label(), 'Hello!') def test_secret(self): self.assertEqual(self.item.get_secret(), b'pa$$word') self.item.set_secret(b'newpa$$word') self.assertEqual(self.item.get_secret(), b'newpa$$word') self.assertEqual(self.other_item.get_secret(), b'') def test_secret_content_type(self): self.assertEqual(self.item.get_secret_content_type(), 'text/plain') # The check below fails in gnome-keyring because it doesn't really # support content types. #self.assertEqual(self.other_item.get_secret_content_type(), 'data/null') def test_modified(self): now = time.time() modified = self.item.get_modified() self.assertAlmostEqual(now, modified, places=-1) def test_created(self): created = self.item.get_created() self.assertAlmostEqual(self.created_timestamp, created, places=-1) @classmethod def tearDownClass(cls): cls.item.delete() cls.other_item.delete() if __name__ == '__main__': unittest.main() SecretStorage-2.0.0/tests/test_exceptions.py0000644000175000017500000000177412271521230022065 0ustar dmitrydmitry00000000000000# Tests for SecretStorage # Author: Dmitry Shachnev, 2013 # License: BSD # Various exception tests import unittest import secretstorage from secretstorage.exceptions import ItemNotFoundException class ExceptionsTest(unittest.TestCase): """A test case that ensures that all SecretStorage exceptions are raised correctly.""" @classmethod def setUpClass(cls): cls.bus = secretstorage.dbus_init(main_loop=False) cls.collection = secretstorage.Collection(cls.bus) def test_double_deleting(self): item = self.collection.create_item('MyItem', {'application': 'secretstorage-test'}, b'pa$$word') item.delete() self.assertRaises(ItemNotFoundException, item.delete) def test_non_existing_item(self): self.assertRaises(ItemNotFoundException, secretstorage.Item, self.bus, '/not/existing/path') def test_non_existing_collection(self): self.assertRaises(ItemNotFoundException, secretstorage.get_collection_by_alias, self.bus, 'non-existing-alias') if __name__ == '__main__': unittest.main() SecretStorage-2.0.0/tests/test_collection.py0000644000175000017500000000214612271521230022031 0ustar dmitrydmitry00000000000000# Tests for SecretStorage # Author: Dmitry Shachnev, 2013 # License: BSD # This file tests the secretstorage.Collection class. import unittest from secretstorage import dbus_init, Collection, get_all_collections class CollectionTest(unittest.TestCase): """A test case that tests that all common methods of Collection class work and do not crash.""" @classmethod def setUpClass(cls): cls.bus = dbus_init(main_loop=False) cls.collection = Collection(cls.bus) def test_all_collections(self): labels = map(Collection.get_label, get_all_collections(self.bus)) self.assertIn(self.collection.get_label(), labels) def test_all_items(self): for item in self.collection.get_all_items(): item.get_label() def test_create_empty_item(self): item = self.collection.create_item('', {}, b'') item.delete() def test_label(self): old_label = self.collection.get_label() self.collection.set_label('Hello!') self.assertEqual(self.collection.get_label(), 'Hello!') self.collection.set_label(old_label) self.assertEqual(self.collection.get_label(), old_label) if __name__ == '__main__': unittest.main() SecretStorage-2.0.0/tests/test_dhcrypto.py0000644000175000017500000000161412271521230021531 0ustar dmitrydmitry00000000000000# Tests for SecretStorage # Author: Dmitry Shachnev, 2014 # License: BSD # This file tests the dhcrypto module. import unittest from secretstorage.dhcrypto import long_to_bytes, bytes_to_long class ConversionTest(unittest.TestCase): """A test case that tests conversion functions between bytes and long.""" def test_long_to_bytes(self): self.assertEqual(long_to_bytes(1), b'\x01') self.assertEqual(long_to_bytes(258), b'\x01\x02') self.assertEqual(long_to_bytes(1 << 64), b'\x01' + b'\x00' * 8) def test_bytes_to_long(self): self.assertEqual(bytes_to_long(b'\x01'), 1) self.assertEqual(bytes_to_long(b'\x01\x02'), 258) self.assertEqual(bytes_to_long(b'\x01' + b'\x00' * 8), 1 << 64) def test_array_to_long(self): self.assertEqual(bytes_to_long([1] + [0] * 8), 1 << 64) self.assertEqual(bytes_to_long(bytearray([1] + [0] * 8)), 1 << 64) if __name__ == '__main__': unittest.main() SecretStorage-2.0.0/setup.py0000755000175000017500000000240112271522541016640 0ustar dmitrydmitry00000000000000#!/usr/bin/python import os.path from distutils.core import setup version = '2.0.0' readme_file = open(os.path.join(os.path.dirname(__file__), 'README.rst')) long_description = '\n' + readme_file.read() readme_file.close() classifiers = ['Development Status :: 4 - Beta', 'License :: OSI Approved :: BSD License', 'Operating System :: POSIX', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Topic :: Security', 'Topic :: Software Development :: Libraries :: Python Modules' ] cmdclass = {} try: from sphinx.setup_command import BuildDoc except: pass else: cmdclass['build_sphinx'] = BuildDoc setup(name='SecretStorage', version=version, description='Secure storing of passwords and other secrets', long_description=long_description, author='Dmitry Shachnev', author_email='mitya57@gmail.com', url='http://launchpad.net/python-secretstorage', packages=['secretstorage'], platforms='Linux', license='BSD', classifiers=classifiers, cmdclass=cmdclass, requires=['dbus', 'Crypto'] ) SecretStorage-2.0.0/changelog0000644000175000017500000000451212271522361017002 0ustar dmitrydmitry00000000000000SecretStorage 2.0, 2014-01-27 ============================= * Add support for encrypted sessions and use them by default. * Get rid of Introspect() calls to make D-Bus messaging faster. SecretStorage 1.1, 2013-11-15 ============================= * Ported to PyQt5. * Added `Item.get_created()` method. * Improvements to error handling. SecretStorage 1.0, 2013-05-08 ============================= * Renamed `exec_prompt_async_*` functions to just `exec_prompt_*` (old aliases kept for compatibility purposes). * Added two helper functions: - `get_any_collection()` for getting default or session collection; - `get_default_collection()` for getting or creating the default collection. * Fix creation of items with empty attributes dict. * Make `SecretServiceNotAvailableException` a subclass of `SecretStorageException`. * Various documentation improvements. SecretStorage 0.9, 2013-03-05 ============================= * Added support for content types in secrets. * Added `Item.get_modified()` method. * Added `get_all_collections()` and `get_collection_by_alias()` functions. * Added `search_items()` function for global search. * Made synchronous version of `Collection.unlock()` return a boolean representing whether the operation was dismissed. * Fixed wrong parsing of Secret Service result in `Collection.get_modified()`. * Various test suite and documentation improvements. SecretStorage 0.8, 2013-01-05 ============================= * Added `Collection` and `Item` classes. * Added support for creating and deleting collections. * Added synchronous loop unlocking support. * Added support for PyQt applications. * Added test suite. * Convert D-Bus exceptions to SecretStorage exceptions. * Rewrote the documentation in Sphinx. * Miscellaneous internal improvements. SecretStorage 0.2, 2012-06-22 ============================= * Added `get_item_attributes` function. * Renamed `get_item_ids` to `get_items_ids`. * Renamed `get_item_by_id` to `get_item`. * Renamed `delete_item_by_id` to `delete_item`. * Made `create_item` return id of the created item. * Added `secretstorage.exceptions` module. * Made all functions throw exceptions from that module. * Updated the documentation. * Added `delete_test_items.py` script that deletes all test items. SecretStorage 0.1, 2012-06-02 ============================= * Initial release. SecretStorage-2.0.0/secretstorage/0000755000175000017500000000000012271523347020005 5ustar dmitrydmitry00000000000000SecretStorage-2.0.0/secretstorage/defines.py0000644000175000017500000000137312271521230021765 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2013 # License: BSD # This file contains some common defines. SECRETS = 'org.freedesktop.secrets' SS_PREFIX = 'org.freedesktop.Secret.' SS_PATH = '/org/freedesktop/secrets' DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' DBUS_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown' DBUS_EXEC_FAILED = 'org.freedesktop.DBus.Error.Spawn.ExecFailed' DBUS_NO_REPLY = 'org.freedesktop.DBus.Error.NoReply' DBUS_NOT_SUPPORTED = 'org.freedesktop.DBus.Error.NotSupported' DBUS_NO_SUCH_OBJECT = 'org.freedesktop.Secret.Error.NoSuchObject' ALGORITHM_PLAIN = 'plain' ALGORITHM_DH = 'dh-ietf1024-sha256-aes128-cbc-pkcs7' SecretStorage-2.0.0/secretstorage/collection.py0000644000175000017500000002005012271521230022474 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2013 # License: BSD """Collection is a place where secret items are stored. Normally, only the default collection should be used, but this module allows to use any registered collection. Use :func:`get_default_collection` to get the default collection (and create it, if necessary). Collections are usually automatically unlocked when user logs in, but collections can also be locked and unlocked using :meth:`Collection.lock` and :meth:`Collection.unlock` methods (unlocking requires showing the unlocking prompt to user and can be synchronous or asynchronous). Creating new items and editing existing ones is possible only in unlocked collection.""" import dbus from secretstorage.defines import SS_PREFIX, SS_PATH, SECRETS from secretstorage.exceptions import LockedException, ItemNotFoundException from secretstorage.item import Item from secretstorage.util import bus_get_object, InterfaceWrapper, \ exec_prompt, exec_prompt_glib, format_secret, open_session, \ to_unicode COLLECTION_IFACE = SS_PREFIX + 'Collection' SERVICE_IFACE = SS_PREFIX + 'Service' DEFAULT_COLLECTION = '/org/freedesktop/secrets/aliases/default' SESSION_COLLECTION = '/org/freedesktop/secrets/collection/session' class Collection(object): """Represents a collection.""" def __init__(self, bus, collection_path=DEFAULT_COLLECTION, session=None): collection_obj = bus_get_object(bus, SECRETS, collection_path) self.bus = bus self.session = session self.collection_path = collection_path self.collection_iface = InterfaceWrapper(collection_obj, COLLECTION_IFACE) self.collection_props_iface = InterfaceWrapper(collection_obj, dbus.PROPERTIES_IFACE) self.collection_props_iface.Get(COLLECTION_IFACE, 'Label', signature='ss') def is_locked(self): """Returns :const:`True` if item is locked, otherwise :const:`False`.""" return bool(self.collection_props_iface.Get( COLLECTION_IFACE, 'Locked', signature='ss')) def ensure_not_locked(self): """If collection is locked, raises :exc:`~secretstorage.exceptions.LockedException`.""" if self.is_locked(): raise LockedException('Collection is locked!') def unlock(self, callback=None): """Requests unlocking the collection. If `callback` is specified, calls it when unlocking is complete (see :func:`~secretstorage.util.exec_prompt` description for details) and returns a boolean representing whether the operation was dismissed. Otherwise, uses loop from GLib API.""" service_obj = bus_get_object(self.bus, SECRETS, SS_PATH) service_iface = InterfaceWrapper(service_obj, SERVICE_IFACE) prompt = service_iface.Unlock([self.collection_path], signature='ao')[1] if len(prompt) > 1: if callback: exec_prompt(self.bus, prompt, callback) else: return exec_prompt_glib(self.bus, prompt)[0] elif callback: # We still need to call it. callback(False, []) def lock(self): """Locks the collection.""" service_obj = bus_get_object(self.bus, SECRETS, SS_PATH) service_iface = InterfaceWrapper(service_obj, SERVICE_IFACE) service_iface.Lock([self.collection_path], signature='ao') def delete(self): """Deletes the collection and all items inside it.""" self.ensure_not_locked() self.collection_iface.Delete(signature='') def get_all_items(self): """Returns a generator of all items in the collection.""" for item_path in self.collection_props_iface.Get( COLLECTION_IFACE, 'Items', signature='ss'): yield Item(self.bus, item_path, self.session) def search_items(self, attributes): """Returns a generator of items with the given attributes. `attributes` should be a dictionary.""" result = self.collection_iface.SearchItems(attributes, signature='a{ss}') if isinstance(result, tuple): # bug in GNOME Keyring <= 3.7.5 result = result[0] + result[1] for item_path in result: yield Item(self.bus, item_path, self.session) def get_label(self): """Returns the collection label.""" label = self.collection_props_iface.Get(COLLECTION_IFACE, 'Label', signature='ss') return to_unicode(label) def set_label(self, label): """Sets collection label to `label`.""" self.ensure_not_locked() self.collection_props_iface.Set(COLLECTION_IFACE, 'Label', label, signature='ssv') def create_item(self, label, attributes, secret, replace=False, content_type='text/plain'): """Creates a new :class:`~secretstorage.item.Item` with given `label` (unicode string), `attributes` (dictionary) and `secret` (bytestring). If `replace` is :const:`True`, replaces the existing item with the same attributes. If `content_type` is given, also sets the content type of the secret (``text/plain`` by default). Returns the created item.""" self.ensure_not_locked() if not self.session: self.session = open_session(self.bus) secret = format_secret(self.session, secret, content_type) attributes = dbus.Dictionary(attributes, signature='ss') properties = { SS_PREFIX+'Item.Label': label, SS_PREFIX+'Item.Attributes': attributes } new_item, prompt = self.collection_iface.CreateItem(properties, secret, replace, signature='a{sv}(oayays)b') return Item(self.bus, new_item, self.session) def create_collection(bus, label, alias='', session=None): """Creates a new :class:`Collection` with the given `label` and `alias` and returns it. This action requires prompting. If prompt is dismissed, raises :exc:`~secretstorage.exceptions.ItemNotFoundException`. This is synchronous function, uses loop from GLib API.""" if not session: session = open_session(bus) properties = {SS_PREFIX+'Collection.Label': label} service_obj = bus_get_object(bus, SECRETS, SS_PATH) service_iface = dbus.Interface(service_obj, SERVICE_IFACE) collection_path, prompt = service_iface.CreateCollection(properties, alias, signature='a{sv}s') if len(collection_path) > 1: return Collection(bus, collection_path, session=session) dismissed, unlocked = exec_prompt_glib(bus, prompt) if dismissed: raise ItemNotFoundException('Prompt dismissed.') return Collection(bus, unlocked, session=session) def get_all_collections(bus): """Returns a generator of all available collections.""" service_obj = bus_get_object(bus, SECRETS, SS_PATH) service_props_iface = dbus.Interface(service_obj, dbus.PROPERTIES_IFACE) for collection_path in service_props_iface.Get(SERVICE_IFACE, 'Collections', signature='ss'): yield Collection(bus, collection_path) def get_default_collection(bus, session=None): """Returns the default collection. If it doesn't exist, creates it.""" try: return Collection(bus) except ItemNotFoundException: return create_collection(bus, 'Default', 'default', session) def get_any_collection(bus): """Returns any collection, in the following order of preference: - The default collection; - The "session" collection (usually temporary); - The first collection in the collections list.""" try: return Collection(bus) except ItemNotFoundException: pass try: return Collection(bus, SESSION_COLLECTION) except ItemNotFoundException: pass collections = list(get_all_collections(bus)) if collections: return collections[0] else: raise ItemNotFoundException('No collections found.') def get_collection_by_alias(bus, alias): """Returns the collection with the given `alias`. If there is no such collection, raises :exc:`~secretstorage.exceptions.ItemNotFoundException`.""" service_obj = bus_get_object(bus, SECRETS, SS_PATH) service_iface = dbus.Interface(service_obj, SERVICE_IFACE) collection_path = service_iface.ReadAlias(alias, signature='s') if len(collection_path) <= 1: raise ItemNotFoundException('No collection with such alias.') return Collection(bus, collection_path) def search_items(bus, attributes): """Returns a generator of items in all collections with the given attributes. `attributes` should be a dictionary.""" service_obj = bus_get_object(bus, SECRETS, SS_PATH) service_iface = dbus.Interface(service_obj, SERVICE_IFACE) locked, unlocked = service_iface.SearchItems(attributes, signature='a{ss}') for item_path in locked + unlocked: yield Item(bus, item_path) SecretStorage-2.0.0/secretstorage/util.py0000644000175000017500000001242012271522203021321 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2013 # License: BSD """This module provides some utility functions, but these shouldn't normally be used by external applications.""" import dbus from secretstorage.defines import DBUS_UNKNOWN_METHOD, DBUS_NO_SUCH_OBJECT, \ DBUS_SERVICE_UNKNOWN, DBUS_NO_REPLY, DBUS_NOT_SUPPORTED, DBUS_EXEC_FAILED, \ SECRETS, SS_PATH, SS_PREFIX, ALGORITHM_DH, ALGORITHM_PLAIN from secretstorage.dhcrypto import Session from secretstorage.exceptions import ItemNotFoundException, \ SecretServiceNotAvailableException from Crypto.Random.random import getrandbits from Crypto.Cipher.AES import AESCipher, MODE_CBC from secretstorage.dhcrypto import long_to_bytes, bytes_to_long class InterfaceWrapper(dbus.Interface): """Wraps :cls:`dbus.Interface` class and replaces some D-Bus exceptions with :doc:`SecretStorage exceptions `.""" def catch_errors(self, function_in): def function_out(*args, **kwargs): try: return function_in(*args, **kwargs) except dbus.exceptions.DBusException as e: if e.get_dbus_name() == DBUS_UNKNOWN_METHOD: raise ItemNotFoundException('Item does not exist!') if e.get_dbus_name() == DBUS_NO_SUCH_OBJECT: raise ItemNotFoundException(e.get_dbus_message()) if e.get_dbus_name() in (DBUS_NO_REPLY, DBUS_NOT_SUPPORTED): raise SecretServiceNotAvailableException( e.get_dbus_message()) raise return function_out def __getattr__(self, attribute): result = dbus.Interface.__getattr__(self, attribute) if callable(result): result = self.catch_errors(result) return result def bus_get_object(bus, name, object_path): """A wrapper around :meth:`SessionBus.get_object` that raises :exc:`~secretstorage.exceptions.SecretServiceNotAvailableException` when appropriate.""" try: return bus.get_object(name, object_path, introspect=False) except dbus.exceptions.DBusException as e: if e.get_dbus_name() in (DBUS_SERVICE_UNKNOWN, DBUS_EXEC_FAILED, DBUS_NO_REPLY): raise SecretServiceNotAvailableException(e.get_dbus_message()) raise def open_session(bus): """Returns a new Secret Service session.""" service_obj = bus_get_object(bus, SECRETS, SS_PATH) service_iface = dbus.Interface(service_obj, SS_PREFIX+'Service') session = Session() try: output, result = service_iface.OpenSession( ALGORITHM_DH, dbus.ByteArray(long_to_bytes(session.my_public_key)), signature='sv' ) except dbus.exceptions.DBusException as e: if e.get_dbus_name() != DBUS_NOT_SUPPORTED: raise output, result = service_iface.OpenSession( ALGORITHM_PLAIN, '', signature='sv' ) session.encrypted = False else: session.set_server_public_key(bytes_to_long(output)) session.object_path = result return session def format_secret(session, secret, content_type): """Formats `secret` to make possible to pass it to the Secret Service API.""" if not isinstance(secret, bytes): secret = secret.encode('utf-8') if not session.encrypted: return dbus.Struct((session.object_path, '', dbus.ByteArray(secret), content_type)) # PKCS-7 style padding padding = 0x10 - (len(secret) & 0xf) secret += bytes(bytearray((padding,)) * padding) aes_iv = long_to_bytes(getrandbits(0x80)) # If shorter than 16 bytes, prepend zero bytes aes_iv = b'\x00' * (0x10 - len(aes_iv)) + aes_iv aes_cipher = AESCipher(session.aes_key, mode=MODE_CBC, IV=aes_iv) return dbus.Struct(( session.object_path, dbus.Array(aes_iv), dbus.Array(bytearray(aes_cipher.encrypt(secret))), content_type )) def exec_prompt(bus, prompt, callback): """Executes the given `prompt`, when complete calls `callback` function with two arguments: a boolean representing whether the operation was dismissed and a list of unlocked item paths. A main loop should be running and registered for this function to work.""" prompt_obj = bus_get_object(bus, SECRETS, prompt) prompt_iface = dbus.Interface(prompt_obj, SS_PREFIX+'Prompt') prompt_iface.Prompt('', signature='s') def new_callback(dismissed, unlocked): if isinstance(unlocked, dbus.Array): unlocked = list(unlocked) callback(bool(dismissed), unlocked) prompt_iface.connect_to_signal('Completed', new_callback) def exec_prompt_glib(bus, prompt): """Like :func:`exec_prompt`, but synchronous (uses loop from GLib API). Returns (*dismissed*, *unlocked*) tuple.""" from gi.repository import GLib loop = GLib.MainLoop() result = [] def callback(dismissed, unlocked): result.append(dismissed) result.append(unlocked) loop.quit() exec_prompt(bus, prompt, callback) loop.run() return result[0], result[1] def exec_prompt_qt(bus, prompt): """Like :func:`exec_prompt`, but synchronous (uses loop from PyQt5 API). Returns (*dismissed*, *unlocked*) tuple.""" from PyQt5.QtCore import QCoreApplication app = QCoreApplication([]) result = [] def callback(dismissed, unlocked): result.append(dismissed) result.append(unlocked) app.quit() exec_prompt(bus, prompt, callback) app.exec_() return result[0], result[1] # Compatibility aliases exec_prompt_async_glib = exec_prompt_glib exec_prompt_async_qt = exec_prompt_qt def to_unicode(string): """Converts D-Bus string to unicode string.""" try: # For Python 2 return unicode(string) except NameError: # For Python 3 return str(string) SecretStorage-2.0.0/secretstorage/dhcrypto.py0000644000175000017500000000472512271521230022210 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2014 # License: BSD '''This module contains needed classes, functions and constants to implement dh-ietf1024-sha256-aes128-cbc-pkcs7 secret encryption algorithm.''' import hmac import math from hashlib import sha256 from Crypto.Random.random import getrandbits # A standard prime number for use in Diffie-Hellman exchange DH_PRIME_1024_BYTES = ( 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ) if hasattr(int, 'from_bytes'): bytes_to_long = lambda bytes_array: int.from_bytes(bytes_array, 'big') else: from Crypto.Util.number import bytes_to_long as _to_long # We need to support both list and bytes input bytes_to_long = lambda b: _to_long(bytearray(b)) if hasattr(int, 'to_bytes'): def long_to_bytes(number): return int.to_bytes(number, math.ceil(number.bit_length() / 8), 'big') else: from Crypto.Util.number import long_to_bytes DH_PRIME_1024 = bytes_to_long(DH_PRIME_1024_BYTES) class Session(object): def __init__(self): self.object_path = None self.server_public_key = None self.aes_key = None self.encrypted = True # 128-bytes-long strong random number self.my_private_key = getrandbits(0x400) self.my_public_key = pow(2, self.my_private_key, DH_PRIME_1024) def set_server_public_key(self, server_public_key): common_secret = pow(server_public_key, self.my_private_key, DH_PRIME_1024) common_secret = long_to_bytes(common_secret) # HKDF with null salt, empty info and SHA-256 hash salt = b'\x00' * 0x20 pseudo_random_key = hmac.new(salt, common_secret, sha256).digest() output_block = hmac.new(pseudo_random_key, b'\x01', sha256).digest() # Resulting AES key should be 128-bit self.aes_key = output_block[:0x10] SecretStorage-2.0.0/secretstorage/exceptions.py0000644000175000017500000000300412271521230022522 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2012 # License: BSD """All secretstorage functions may raise various exceptions when something goes wrong. All exceptions derive from base :exc:`SecretStorageException` class.""" class SecretStorageException(Exception): """All exceptions derive from this class.""" class SecretServiceNotAvailableException(SecretStorageException): """Raised by :class:`~secretstorage.item.Item` or :class:`~secretstorage.collection.Collection` constructors, or by other functions in the :mod:`secretstorage.collection` module, when the Secret Service API is not available.""" class LockedException(SecretStorageException): """Raised when an action cannot be performed because the collection is locked. Use :meth:`~secretstorage.collection.Collection.is_locked` to check if the collection is locked, and :meth:`~secretstorage.collection.Collection.unlock` to unlock it. """ class ItemNotFoundException(SecretStorageException): """Raised when an item does not exist or has been deleted. Example of handling: >>> import secretstorage >>> bus = secretstorage.dbus_init() >>> item_path = '/not/existing/path' >>> try: ... item = secretstorage.Item(bus, item_path) ... except secretstorage.ItemNotFoundException: ... print('Item not found!') ... Item not found! Also, :func:`~secretstorage.collection.create_collection` may raise this exception when a prompt was dismissed during creating the collection. """ SecretStorage-2.0.0/secretstorage/item.py0000644000175000017500000001112212271521551021305 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2013 # License: BSD """SecretStorage item contains a *secret*, some *attributes* and a *label* visible to user. Editing all these properties and reading the secret is possible only when the :doc:`collection ` storing the item is unlocked. The collection can be unlocked using collection's :meth:`~secretstorage.collection.Collection.unlock` method.""" import dbus from secretstorage.defines import SECRETS, SS_PREFIX from secretstorage.exceptions import LockedException from secretstorage.util import InterfaceWrapper, bus_get_object, \ open_session, format_secret, to_unicode from Crypto.Cipher.AES import AESCipher, MODE_CBC ITEM_IFACE = SS_PREFIX + 'Item' DEFAULT_COLLECTION = '/org/freedesktop/secrets/aliases/default' class Item(object): """Represents a secret item.""" def __init__(self, bus, item_path, session=None): if isinstance(item_path, int): # An item id was specified instead of the path item_path = '%s/%d' % (DEFAULT_COLLECTION, item_path) self.item_path = item_path item_obj = bus_get_object(bus, SECRETS, item_path) self.session = session self.bus = bus self.item_iface = InterfaceWrapper(item_obj, ITEM_IFACE) self.item_props_iface = InterfaceWrapper(item_obj, dbus.PROPERTIES_IFACE) self.item_props_iface.Get(ITEM_IFACE, 'Label', signature='ss') def __eq__(self, other): return (self._item_id() == other._item_id()) \ and (self.get_attributes() == other.get_attributes()) def _item_id(self): return int(self.item_path.rsplit('/', 1)[1]) def is_locked(self): """Returns :const:`True` if item is locked, otherwise :const:`False`.""" return bool(self.item_props_iface.Get(ITEM_IFACE, 'Locked', signature='ss')) def ensure_not_locked(self): """If collection is locked, raises :exc:`~secretstorage.exceptions.LockedException`.""" if self.is_locked(): raise LockedException('Item is locked!') def get_attributes(self): """Returns item attributes (dictionary).""" attrs = self.item_props_iface.Get(ITEM_IFACE, 'Attributes', signature='ss') return dict([(to_unicode(key), to_unicode(value)) for key, value in attrs.items()]) def set_attributes(self, attributes): """Sets item attributes to `attributes` (dictionary).""" self.item_props_iface.Set(ITEM_IFACE, 'Attributes', attributes, signature='ssv') def get_label(self): """Returns item label (unicode string).""" label = self.item_props_iface.Get(ITEM_IFACE, 'Label', signature='ss') return to_unicode(label) def set_label(self, label): """Sets item label to `label`.""" self.ensure_not_locked() self.item_props_iface.Set(ITEM_IFACE, 'Label', label, signature='ssv') def delete(self): """Deletes the item.""" self.ensure_not_locked() return self.item_iface.Delete(signature='') def get_secret(self): """Returns item secret (bytestring).""" self.ensure_not_locked() if not self.session: self.session = open_session(self.bus) secret = self.item_iface.GetSecret(self.session.object_path, signature='o') if not self.session.encrypted: return bytes(bytearray(secret[2])) aes_cipher = AESCipher(self.session.aes_key, mode=MODE_CBC, IV=bytes(bytearray(secret[1]))) padded_secret = bytearray(aes_cipher.decrypt( bytes(bytearray(secret[2])))) return padded_secret[:-padded_secret[-1]] def get_secret_content_type(self): """Returns content type of item secret (string).""" self.ensure_not_locked() if not self.session: self.session = open_session(self.bus) secret = self.item_iface.GetSecret(self.session.object_path, signature='o') return str(secret[3]) def set_secret(self, secret, content_type='text/plain'): """Sets item secret to `secret`. If `content_type` is given, also sets the content type of the secret (``text/plain`` by default).""" self.ensure_not_locked() if not self.session: self.session = open_session(self.bus) secret = format_secret(self.session, secret, content_type) self.item_iface.SetSecret(secret, signature='(oayays)') def get_created(self): """Returns UNIX timestamp (integer) representing the time when the item was created.""" return int(self.item_props_iface.Get(ITEM_IFACE, 'Created', signature='ss')) def get_modified(self): """Returns UNIX timestamp (integer) representing the time when the item was last modified.""" return int(self.item_props_iface.Get(ITEM_IFACE, 'Modified', signature='ss')) def to_tuple(self): """Returns (*attributes*, *secret*) tuple representing the item.""" self.ensure_not_locked() return self.get_attributes(), self.get_secret() SecretStorage-2.0.0/secretstorage/__init__.py0000644000175000017500000000720112271522556022117 0ustar dmitrydmitry00000000000000# SecretStorage module for Python # Access passwords using the SecretService DBus API # Author: Dmitry Shachnev, 2013 # License: BSD """This file provides quick access to all SecretStorage API. Please refer to documentation of individual modules for API details. It also provides some functions for compatibility with older SecretStorage releases. Those functions are not recommended for use in new software.""" import dbus from secretstorage.collection import Collection, create_collection, \ get_all_collections, get_default_collection, get_any_collection, \ get_collection_by_alias, search_items from secretstorage.item import Item from secretstorage.defines import DBUS_NOT_SUPPORTED, DBUS_EXEC_FAILED, \ DBUS_NO_REPLY from secretstorage.exceptions import SecretStorageException, \ SecretServiceNotAvailableException, LockedException, \ ItemNotFoundException __version__ = '2.0.0' def dbus_init(main_loop=True, use_qt_loop=False): """Returns new SessionBus_. If `main_loop` is :const:`True`, registers a main loop (PyQt5 main loop if `use_qt_loop` is :const:`True`, otherwise GLib main loop). .. _SessionBus: http://www.freedesktop.org/wiki/IntroductionToDBus#Buses .. note:: Qt uses GLib main loops on UNIX-like systems by default, so one will rarely need to set `use_qt_loop` to :const:`True`. """ if main_loop: if use_qt_loop: from dbus.mainloop.pyqt5 import DBusQtMainLoop DBusQtMainLoop(set_as_default=True) else: from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) try: return dbus.SessionBus() except dbus.exceptions.DBusException as e: if e.get_dbus_name() in (DBUS_NOT_SUPPORTED, DBUS_EXEC_FAILED, DBUS_NO_REPLY): raise SecretServiceNotAvailableException( e.get_dbus_message()) raise # The functions below are provided for compatibility with old # SecretStorage versions (<= 0.2). def get_items(search_attributes, unlock_all=True): """Returns tuples for all items in the default collection matching `search_attributes`.""" bus = dbus_init() collection = Collection(bus) if unlock_all and collection.is_locked(): collection.unlock() search_results = collection.search_items(search_attributes) return [item.to_tuple() for item in search_results] def get_items_ids(search_attributes): """Returns item id for all items in the default collection matching `search_attributes`.""" bus = dbus_init() collection = Collection(bus) search_results = collection.search_items(search_attributes) return [item._item_id() for item in search_results] def get_item_attributes(item_id): """Returns item attributes for item with given id.""" bus = dbus_init() item = Item(bus, item_id) return item.get_attributes() def get_item_object(item_id, unlock=True): """Returns the item with given id and unlocks it if `unlock` is `True`.""" bus = dbus_init() item = Item(bus, item_id) collection_path = item.item_path.rsplit('/', 1)[0] collection = Collection(bus, collection_path) if unlock and collection.is_locked(): collection.unlock() return item def get_item(item_id, unlock=True): """Returns tuple representing the item with given id.""" return get_item_object(item_id, unlock).to_tuple() def delete_item(item_id, unlock=True): """Deletes the item with given id.""" return get_item_object(item_id, unlock).delete() def create_item(label, attributes, secret, unlock=True): """Creates an item with given `label`, `attributes` and `secret` in the default collection. Returns id of the created item.""" bus = dbus_init() collection = Collection(bus) if unlock and collection.is_locked(): collection.unlock() item = collection.create_item(label, attributes, secret) return item._item_id() SecretStorage-2.0.0/README.rst0000644000175000017500000000353412271517216016625 0ustar dmitrydmitry00000000000000Module description ================== This module provides a way for securely storing passwords and other secrets. It uses D-Bus `Secret Service`_ API that is supported by GNOME Keyring (>= 2.30) and KSecretsService. The main classes provided are ``secretstorage.Item``, representing a secret item (that has a *label*, a *secret* and some *attributes*) and ``secretstorage.Collection``, a place items are stored in. SecretStorage supports most of the functions provided by Secret Service, including creating and deleting items and collections, editing items, locking and unlocking collections (asynchronous unlocking is also supported). The documentation can be found on `pythonhosted.org`_. .. _`Secret Service`: http://standards.freedesktop.org/secret-service/ .. _`pythonhosted.org`: http://pythonhosted.org/SecretStorage/ Building the module =================== .. note:: SecretStorage supports all versions of Python since 2.6. Here we assume that your Python version is 3.x. SecretStorage requires these packages to work: * `dbus-python`_ (available in Debian-based distributions in `python3-dbus package`_); * PyCrypto_ (available in Debian-based distributions in `python3-crypto package`_). To build SecretStorage, use this command:: python3 setup.py build If you have Sphinx_ installed, you can also build the documentation:: python3 setup.py build_sphinx .. _`dbus-python`: http://www.freedesktop.org/wiki/Software/DBusBindings#dbus-python .. _PyCrypto: https://www.dlitz.net/software/pycrypto/ .. _`python3-dbus package`: http://packages.debian.org/sid/python3-dbus .. _`python3-crypto package`: http://packages.debian.org/sid/python3-crypto .. _Sphinx: http://sphinx-doc.org/ Get the code ============ SecretStorage is available under BSD license. The source code can be found on GitHub_. .. _GitHub: https://github.com/mitya57/secretstorage SecretStorage-2.0.0/LICENSE0000644000175000017500000000273312271516047016144 0ustar dmitrydmitry00000000000000Copyright 2012 Dmitry Shachnev All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the University 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE REGENTS OR CONTRIBUTORS 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.