[Python-modules-commits] r23314 - in packages/python-keyring/branches/wheezy/debian (5 files)
sramacher at users.alioth.debian.org
sramacher at users.alioth.debian.org
Wed Jan 16 01:00:42 UTC 2013
Date: Wednesday, January 16, 2013 @ 01:00:39
Author: sramacher
Revision: 23314
Import 0.7.1-1+deb7u1
Added:
packages/python-keyring/branches/wheezy/debian/patches/
packages/python-keyring/branches/wheezy/debian/patches/696736-Fix-insecure-permissions-on-database-files.patch
packages/python-keyring/branches/wheezy/debian/patches/CVE-2012-4571.patch
packages/python-keyring/branches/wheezy/debian/patches/series
Modified:
packages/python-keyring/branches/wheezy/debian/changelog
Modified: packages/python-keyring/branches/wheezy/debian/changelog
===================================================================
--- packages/python-keyring/branches/wheezy/debian/changelog 2013-01-16 00:59:52 UTC (rev 23313)
+++ packages/python-keyring/branches/wheezy/debian/changelog 2013-01-16 01:00:39 UTC (rev 23314)
@@ -1,3 +1,15 @@
+python-keyring (0.7.1-1+deb7u1) testing-proposed-updates; urgency=low
+
+ * Team upload.
+ * debian/patches:
+ - CVE-2012-4571.patch: backport CryptedFileKeyring from 0.9.3 to fix
+ CVE-2012-4571. (Closes: #675379)
+ - 696736-Fix-insecure-permissions-on-database-files.patch: backport fix
+ from 0.9.2-1.1 to fix insecure permissions on database files. Fix
+ CVE-2012-5578. Thanks Salvatore Bonaccorso. (Closes: #696736)
+
+ -- Sebastian Ramacher <sramacher at debian.org> Sun, 06 Jan 2013 22:22:33 +0100
+
python-keyring (0.7.1-1) unstable; urgency=low
* New upstream version (Closes: #656680, #624690)
Added: packages/python-keyring/branches/wheezy/debian/patches/696736-Fix-insecure-permissions-on-database-files.patch
===================================================================
--- packages/python-keyring/branches/wheezy/debian/patches/696736-Fix-insecure-permissions-on-database-files.patch (rev 0)
+++ packages/python-keyring/branches/wheezy/debian/patches/696736-Fix-insecure-permissions-on-database-files.patch 2013-01-16 01:00:39 UTC (rev 23314)
@@ -0,0 +1,56 @@
+Description: set appropriate file permissions on database file.
+Bug: https://bitbucket.org/kang/python-keyring-lib/issue/67/set-go-rwx-on-keyring_passcfg
+Bug: https://bitbucket.org/kang/python-keyring-lib/issue/76/insecure-database-file-permissions
+Bug-Debian: http://bugs.debian.org/696736
+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-keyring/+bug/1031465
+Forwarded: yes
+Author: Marc Deslauriers <marc.deslauriers at canonical.com>
+Reviewed-by: Salvatore Bonaccorso <carnil at debian.org>
+Last-Update: 2013-01-06
+
+--- a/keyring/backend.py
++++ b/keyring/backend.py
+@@ -6,6 +6,7 @@
+
+ import getpass
+ import os
++import stat
+ import sys
+ import ConfigParser
+ import base64
+@@ -348,6 +349,7 @@
+ storage_root = os.path.dirname(self.file_path)
+ if storage_root and not os.path.isdir(storage_root):
+ os.makedirs(storage_root)
++ os.chmod(storage_root, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
+
+
+ class UncryptedFileKeyring(BasicFileKeyring):
+--- a/keyring/util/loc_compat.py
++++ b/keyring/util/loc_compat.py
+@@ -1,5 +1,6 @@
+ import os
+ import shutil
++import stat
+ import sys
+
+ def relocate_file(old_location, new_location):
+@@ -24,4 +25,6 @@
+ # ensure the storage path exists
+ if not os.path.isdir(os.path.dirname(new_location)):
+ os.makedirs(os.path.dirname(new_location))
++ os.chmod(os.path.dirname(new_location),
++ stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
+ shutil.move(old_location, new_location)
+--- a/keyring/tests/test_backend.py
++++ b/keyring/tests/test_backend.py
+@@ -336,7 +336,8 @@
+ def setUp(self):
+ super(FileKeyringTests, self).setUp()
+ self.keyring = self.init_keyring()
+- self.keyring.file_path = self.tmp_keyring_file = tempfile.mktemp()
++ self.keyring.file_path = self.tmp_keyring_file = os.path.join(
++ tempfile.mkdtemp(), "test_pass.cfg")
+
+ def tearDown(self):
+ try:
Added: packages/python-keyring/branches/wheezy/debian/patches/CVE-2012-4571.patch
===================================================================
--- packages/python-keyring/branches/wheezy/debian/patches/CVE-2012-4571.patch (rev 0)
+++ packages/python-keyring/branches/wheezy/debian/patches/CVE-2012-4571.patch 2013-01-16 01:00:39 UTC (rev 23314)
@@ -0,0 +1,508 @@
+Description: backport CryptedFileKeyring from 0.9.3
+ Use a random IV to initialize AES cipher. Also use PBKDF2 to derive the AES key
+ from the user provided password.
+Origin: backport,
+ https://bitbucket.org/kang/python-keyring-lib/commits/576e21a,
+ https://bitbucket.org/kang/python-keyring-lib/commits/46e94a7,
+ https://bitbucket.org/kang/python-keyring-lib/commits/2e66ff2,
+ https://bitbucket.org/kang/python-keyring-lib/commits/6481eb6,
+ https://bitbucket.org/kang/python-keyring-lib/commits/168830d,
+ https://bitbucket.org/kang/python-keyring-lib/commits/1cf1b06,
+ https://bitbucket.org/kang/python-keyring-lib/commits/8751161,
+ https://bitbucket.org/kang/python-keyring-lib/commits/cd5cdda,
+ https://bitbucket.org/kang/python-keyring-lib/commits/2e97206,
+ https://bitbucket.org/kang/python-keyring-lib/commits/7b324f0,
+ https://bitbucket.org/kang/python-keyring-lib/commits/8881c7d,
+ https://bitbucket.org/kang/python-keyring-lib/commits/28ed1e5
+Bug-Debian: http://bugs.debian.org/675379
+Last-Update: 2013-01-06
+
+--- a/keyring/backend.py
++++ b/keyring/backend.py
+@@ -12,6 +12,10 @@
+
+ from keyring.util.escape import escape as escape_for_ini
+ from keyring.util import properties
++import keyring.util.escape
++import keyring.util.platform
++import keyring.util.loc_compat
++import json
+
+ try:
+ from abc import ABCMeta, abstractmethod, abstractproperty
+@@ -31,11 +35,6 @@
+ except ImportError:
+ pass
+
+-_KEYRING_SETTING = 'keyring-setting'
+-_CRYPTED_PASSWORD = 'crypted-password'
+-_BLOCK_SIZE = 32
+-_PADDING = '0'
+-
+ class PasswordSetError(Exception):
+ """Raised when the password can't be set.
+ """
+@@ -264,7 +263,7 @@
+ """
+ The path to the file where passwords are stored.
+ """
+- return os.path.join(os.path.expanduser('~'), self.filename)
++ return os.path.join(keyring.util.platform.data_root(), self.filename)
+
+ @abstractproperty
+ def filename(self):
+@@ -284,15 +283,29 @@
+ """
+ pass
+
++ def _migrate(self, keyring_password=None):
++ """Convert older keyrings to the current format."
++ """
++ pass
++
++ def _relocate_file(self):
++ old_location = os.path.join(os.path.expanduser('~'), self.filename)
++ new_location = self.file_path
++ keyring.util.loc_compat.relocate_file(old_location, new_location)
++ # disable this function - it only needs to be run once
++ self._relocate_file = lambda: None
++
+ def get_password(self, service, username):
+ """Read the password from the file.
+ """
++ self._relocate_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):
++ self._migrate()
+ config.read(self.file_path)
+
+ # fetch the password
+@@ -309,6 +322,7 @@
+ def set_password(self, service, username, password):
+ """Write the password in the file.
+ """
++ self._relocate_file()
+ service = escape_for_ini(service)
+ username = escape_for_ini(username)
+
+@@ -325,9 +339,17 @@
+ if not config.has_section(service):
+ config.add_section(service)
+ config.set(service, username, password_base64)
++ self._ensure_file_path()
+ config_file = open(self.file_path,'w')
+ config.write(config_file)
+
++ def _ensure_file_path(self):
++ """ensure the storage path exists"""
++ storage_root = os.path.dirname(self.file_path)
++ if storage_root and not os.path.isdir(storage_root):
++ os.makedirs(storage_root)
++
++
+ class UncryptedFileKeyring(BasicFileKeyring):
+ """Uncrypted File Keyring"""
+
+@@ -351,116 +373,181 @@
+ class CryptedFileKeyring(BasicFileKeyring):
+ """PyCrypto File Keyring"""
+
++ # a couple constants
++ block_size = 32
++ pad_char = '0'
++
+ filename = 'crypted_pass.cfg'
+- crypted_password = None
+
+ def supported(self):
+ """Applicable for all platforms, but not recommend"
+ """
+ try:
+- from Crypto.Cipher import AES
++ __import__('Crypto.Cipher.AES')
++ __import__('Crypto.Protocol.KDF')
++ __import__('Crypto.Random')
+ status = 0
+ except ImportError:
+ status = -1
+ return status
+
+- def _getpass(self, *args, **kwargs):
+- """Wrap getpass.getpass(), so that we can override it when testing.
+- """
+-
+- return getpass.getpass(*args, **kwargs)
+-
+- def _init_file(self):
+- """Init the password file, set the password for it.
+- """
++ @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
+
+- password = None
+- while 1:
+- if not password:
+- password = self._getpass("Please set a password for your new keyring")
+- password2 = self._getpass('Password (again): ')
+- if password != password2:
+- sys.stderr.write("Error: Your passwords didn't match\n")
+- password = None
+- continue
++ 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")
+- password = None
+- continue
+- if len(password) > _BLOCK_SIZE:
+- # block size of AES is less than 32
+- sys.stderr.write("Error: password can't be longer than 32.\n")
+- password = None
+ continue
+- break
++ return password
+
+- # hash the password
+- import crypt
+- self.crypted_password = crypt.crypt(password, password)
++ 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',
++ 'password reference value')
+
+- # write down the initialization
++ def _check_file(self):
++ """
++ Check if the file exists and has the expected password reference.
++ """
++ if not os.path.exists(self.file_path):
++ return False
++ self._migrate()
+ config = ConfigParser.RawConfigParser()
+- config.add_section(_KEYRING_SETTING)
+- config.set(_KEYRING_SETTING, _CRYPTED_PASSWORD, self.crypted_password)
++ config.read(self.file_path)
++ try:
++ config.get(
++ escape_for_ini('keyring-setting'),
++ escape_for_ini('password reference'),
++ )
++ except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
++ return False
++ return True
+
+- config_file = open(self.file_path,'w')
+- config.write(config_file)
++ def _unlock(self):
++ """
++ Unlock this keyring by getting the password for the keyring from the
++ user.
++ """
++ self.keyring_key = getpass.getpass(
++ 'Please enter password for encrypted keyring: ')
++ try:
++ ref_pw = self.get_password('keyring-setting', 'password reference')
++ assert ref_pw == 'password reference value'
++ except AssertionError:
++ self._lock()
++ raise ValueError("Incorrect Password")
+
+- if config_file:
+- config_file.close()
++ def _lock(self):
++ """
++ Remove the keyring key from this instance.
++ """
++ del self.keyring_key
+
+- def _check_file(self):
+- """Check if the password file has been init properly.
++ def _create_cipher(self, password, salt, IV):
+ """
+- if os.path.exists(self.file_path):
++ 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 encrypt(self, password):
++ from Crypto.Random import get_random_bytes
++ salt = get_random_bytes(self.block_size)
++ from Crypto.Cipher import AES
++ IV = get_random_bytes(AES.block_size)
++ cipher = self._create_cipher(self.keyring_key, salt, IV)
++ password_encrypted = cipher.encrypt('pw:' + password)
++ # Serialize the salt, IV, and encrypted password in a secure format
++ data = dict(
++ salt=salt, IV=IV, password_encrypted=password_encrypted,
++ )
++ for key in data:
++ data[key] = data[key].encode('base64')
++ return json.dumps(data)
++
++ def decrypt(self, password_encrypted):
++ # unpack the encrypted payload
++ data = json.loads(password_encrypted)
++ for key in data:
++ data[key] = data[key].decode('base64')
++ cipher = self._create_cipher(self.keyring_key, data['salt'],
++ data['IV'])
++ plaintext = cipher.decrypt(data['password_encrypted'])
++ assert plaintext.startswith('pw:')
++ return plaintext[3:]
++
++ def _migrate(self, keyring_password=None):
++ """
++ Convert keyring from the 0.9.0 and earlier format to the current
++ format.
++ """
++ KEYRING_SETTING = 'keyring-setting'
++ CRYPTED_PASSWORD = 'crypted-password'
++
++ try:
+ config = ConfigParser.RawConfigParser()
+ config.read(self.file_path)
+- try:
+- self.crypted_password = config.get(_KEYRING_SETTING,
+- _CRYPTED_PASSWORD)
+- return self.crypted_password.strip() != ''
+- except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+- pass
+- return False
++ config.get(KEYRING_SETTING, CRYPTED_PASSWORD)
++ except Exception:
++ return
+
+- def _auth(self, password):
+- """Return if the password can open the keyring.
+- """
+- import crypt
+- return crypt.crypt(password, password) == self.crypted_password
++ print("Keyring from 0.9.0 or earlier detected. Upgrading...")
+
+- def _init_crypter(self):
+- """Init the crypter(using the password of the keyring).
+- """
+- # check the password file
+- if not self._check_file():
+- self._init_file()
++ import crypt
+
+- password = self._getpass("Please input your password for the keyring")
++ if keyring_password is None:
++ keyring_password = getpass.getpass(
++ "Please input your password for the keyring: ")
+
+- if not self._auth(password):
++ hashed = crypt.crypt(keyring_password, keyring_password)
++ if config.get(KEYRING_SETTING, CRYPTED_PASSWORD) != hashed:
+ sys.stderr.write("Wrong password for the keyring.\n")
+ raise ValueError("Wrong password")
+
+- # init the cipher with the password
+- from Crypto.Cipher import AES
+- # pad to _BLOCK_SIZE bytes
+- password = password + (_BLOCK_SIZE - len(password) % _BLOCK_SIZE) * \
+- _PADDING
+- return AES.new(password, AES.MODE_CFB)
++ self.keyring_key = keyring_password
++ config.remove_option(KEYRING_SETTING, CRYPTED_PASSWORD)
++ with open(self.file_path, 'w') as f:
++ config.write(f)
++ self.set_password('keyring-setting', 'password reference',
++ 'password reference value')
+
+- def encrypt(self, password):
+- """Encrypt the given password using the pycryto.
+- """
+- crypter = self._init_crypter()
+- return crypter.encrypt(password)
++ from Crypto.Cipher import AES
++ password = keyring_password + (
++ self.block_size - len(keyring_password) % self.block_size
++ ) * self.pad_char
++
++ for service in config.sections():
++ for user in config.options(service):
++ cipher = AES.new(password, AES.MODE_CFB,
++ '\0' * AES.block_size)
++ password_c = config.get(service, user).decode('base64')
++ service = keyring.util.escape.unescape(service)
++ user = keyring.util.escape.unescape(user)
++ password_p = cipher.decrypt(password_c)
++ self.set_password(service, user, password_p)
+
+- def decrypt(self, password_encrypted):
+- """Decrypt the given password using the pycryto.
+- """
+- crypter = self._init_crypter()
+- return crypter.decrypt(password_encrypted)
++ print("File upgraded successfully")
+
+
+ class Win32CryptoKeyring(BasicFileKeyring):
+--- a/keyring/core.py
++++ b/keyring/core.py
+@@ -13,6 +13,8 @@
+
+ from keyring import logger
+ from keyring import backend
++from keyring.util import platform
++from keyring.util import loc_compat
+
+ def set_keyring(keyring):
+ """Set current keyring backend.
+@@ -111,13 +113,19 @@
+ """
+ keyring = None
+
+- # search from current working directory and the home folder
+- keyring_cfg_list = [os.path.join(os.getcwd(), "keyringrc.cfg"),
+- os.path.join(os.path.expanduser("~"), "keyringrc.cfg")]
++ filename = 'keyringrc.cfg'
++
++ local_path = os.path.join(os.getcwd(), filename)
++ legacy_path = os.path.join(os.path.expanduser("~"), filename)
++ config_path = os.path.join(platform.data_root(), filename)
++ loc_compat.relocate_file(legacy_path, config_path)
++
++ # search from current working directory and the data root
++ keyring_cfg_candidates = [local_path, config_path]
+
+ # initialize the keyring_config with the first detected config file
+ keyring_cfg = None
+- for path in keyring_cfg_list:
++ for path in keyring_cfg_candidates:
+ keyring_cfg = path
+ if os.path.exists(path):
+ break
+--- a/keyring/tests/test_core.py
++++ b/keyring/tests/test_core.py
+@@ -8,9 +8,11 @@
+ import sys
+ import tempfile
+ import shutil
++import subprocess
+
+ import keyring.backend
+ import keyring.core
++import keyring.util.platform
+
+ PASSWORD_TEXT = "This is password"
+ PASSWORD_TEXT_2 = "This is password2"
+@@ -105,9 +107,48 @@
+ if personal_renamed:
+ os.rename(personal_cfg+'.old', personal_cfg)
+
++class LocationTestCase(unittest.TestCase):
++ legacy_location = os.path.expanduser('~/keyringrc.cfg')
++ new_location = os.path.join(keyring.util.platform.data_root(),
++ 'keyringrc.cfg')
++
++ @unittest.skipIf(os.path.exists(legacy_location),
++ "Location test requires non-existence of ~/keyringrc.cfg")
++ @unittest.skipIf(os.path.exists(new_location),
++ "Location test requires non-existence of %(new_location)s"
++ % vars())
++ def test_moves_compat(self):
++ """
++ When starting the keyring module and ~/keyringrc.cfg exists, it
++ should be moved and the user should be informed that it was
++ moved.
++ """
++ # create the legacy config
++ with open(self.legacy_location, 'w') as f:
++ f.write('[test config]\n')
++
++ # invoke load_config in a subprocess
++ cmd = [sys.executable, '-c', 'import keyring.core; keyring.core.load_config()']
++ proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
++ stdout, stderr = proc.communicate()
++ assert proc.returncode == 0, stderr
++
++ try:
++ assert not os.path.exists(self.legacy_location)
++ assert os.path.exists(self.new_location)
++ with open(self.new_location) as f:
++ assert 'test config' in f.read()
++ finally:
++ if os.path.exists(self.legacy_location):
++ os.remove(self.legacy_location)
++ if os.path.exists(self.new_location):
++ os.remove(self.new_location)
++
++
+ def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(CoreTestCase))
++ suite.addTest(unittest.makeSuite(LocationTestCase))
+ return suite
+
+ if __name__ == "__main__":
+--- /dev/null
++++ b/keyring/util/loc_compat.py
+@@ -0,0 +1,27 @@
++import os
++import shutil
++import sys
++
++def relocate_file(old_location, new_location):
++ """
++ keyring 0.8 changes the default location for storage of
++ file-based keyring locations. This function is invoked to move
++ files stored in the old location to the new location.
++
++ TODO: remove this function for keyring 1.0.
++ """
++ if not os.path.exists(old_location):
++ # nothing to do; no legacy file found
++ return
++
++ if os.path.exists(new_location):
++ print >> sys.stderr, ("Password file found in legacy "
++ "location\n %(old_location)s\nand new location\n"
++ " %(new_location)s\nOld location will be ignored."
++ % vars())
++ return
++
++ # ensure the storage path exists
++ if not os.path.isdir(os.path.dirname(new_location)):
++ os.makedirs(os.path.dirname(new_location))
++ shutil.move(old_location, new_location)
+--- /dev/null
++++ b/keyring/util/platform.py
+@@ -0,0 +1,10 @@
++import os
++
++def data_root():
++ """
++ Use freedesktop.org Base Dir Specfication to determine storage
++ location.
++ """
++ fallback = os.path.expanduser('~/.local/share')
++ root = os.environ.get('XDG_DATA_HOME', None) or fallback
++ return os.path.join(root, 'python_keyring')
Added: packages/python-keyring/branches/wheezy/debian/patches/series
===================================================================
--- packages/python-keyring/branches/wheezy/debian/patches/series (rev 0)
+++ packages/python-keyring/branches/wheezy/debian/patches/series 2013-01-16 01:00:39 UTC (rev 23314)
@@ -0,0 +1,2 @@
+CVE-2012-4571.patch
+696736-Fix-insecure-permissions-on-database-files.patch
More information about the Python-modules-commits
mailing list