/usr/lib/python3/dist-packages/keyrings/alt/file.py is in python3-keyrings.alt 3.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | from __future__ import with_statement
import os
import sys
import json
import getpass
from six.moves import configparser
from keyring.util import properties
from keyring.util.escape import escape as escape_for_ini
from keyrings.alt.file_base import (
Keyring, decodebytes, encodebytes,
)
class PlaintextKeyring(Keyring):
"""Simple File Keyring with no encryption"""
priority = .5
"Applicable for all platforms, but not recommended"
filename = 'keyring_pass.cfg'
scheme = 'no encyption'
version = '1.0'
def encrypt(self, password, assoc=None):
"""Directly return the password itself, ignore associated data.
"""
return password
def decrypt(self, password_encrypted, assoc=None):
"""Directly return encrypted password, ignore associated data.
"""
return password_encrypted
class Encrypted(object):
"""
PyCrypto-backed Encryption support
"""
scheme = '[PBKDF2] AES256.CFB'
version = '1.0'
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: # pragma: no cover
sys.stderr.write("Error: Your passwords didn't match\n")
continue
if '' == password.strip(): # pragma: no cover
# forbid the blank password
sys.stderr.write("Error: blank passwords aren't allowed.\n")
continue
return password
class EncryptedKeyring(Encrypted, Keyring):
"""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: # pragma: no cover
raise RuntimeError("PyCrypto required")
if not json: # pragma: no cover
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',
'password reference value')
self._write_config_value('keyring-setting',
'scheme',
self.scheme)
self._write_config_value('keyring-setting',
'version',
self.version)
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.read(self.file_path)
try:
config.get(
escape_for_ini('keyring-setting'),
escape_for_ini('password reference'),
)
except (configparser.NoSectionError, configparser.NoOptionError):
return False
try:
self._check_scheme(config)
except AttributeError:
# accept a missing scheme
return True
return self._check_version(config)
def _check_scheme(self, config):
"""
check for a valid scheme
raise ValueError otherwise
raise AttributeError if missing
"""
try:
scheme = config.get(
escape_for_ini('keyring-setting'),
escape_for_ini('scheme'),
)
except (configparser.NoSectionError, configparser.NoOptionError):
raise AttributeError("Encryption scheme missing")
# remove pointless crypto module name
if scheme.startswith('PyCrypto '):
scheme = scheme[9:]
if scheme != self.scheme:
raise ValueError("Encryption scheme mismatch "
"(exp.: %s, found: %s)" % (self.scheme, scheme))
def _check_version(self, config):
"""
check for a valid version
an existing scheme implies an existing version as well
return True, if version is valid, and False otherwise
"""
try:
self.file_version = config.get(
escape_for_ini('keyring-setting'),
escape_for_ini('version'),
)
except (configparser.NoSectionError, configparser.NoOptionError):
return False
return True
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")
def _lock(self):
"""
Remove the keyring key from this instance.
"""
del self.keyring_key
def encrypt(self, password, assoc=None):
# encrypt password, ignore associated data
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(self.pw_prefix + 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:
# spare a few bytes: throw away newline from base64 encoding
data[key] = encodebytes(data[key]).decode()[:-1]
return json.dumps(data).encode()
def decrypt(self, password_encrypted, assoc=None):
# unpack the encrypted payload, ignore associated data
data = json.loads(password_encrypted.decode())
for key in data:
data[key] = decodebytes(data[key].encode())
cipher = self._create_cipher(
self.keyring_key, data['salt'], data['IV'])
plaintext = cipher.decrypt(data['password_encrypted'])
assert plaintext.startswith(self.pw_prefix)
return plaintext[3:]
def _migrate(self, keyring_password=None):
"""
Convert older keyrings to the current format.
"""
|