Coverage for libs/sm/plugins/keymanagerutil.py: 71%
147 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
1#!/usr/bin/python3
3"""
4Helper module used for XenRT testing of the VDI encryption feature (REQ-718).
5This module implements the key lookup plugin interface, so if it is
6installed, SM will use it to retrieve keys based on their hashes.
7This key store is backed by a file stored on disk in dom0, and helper
8functions are provided to manipulate it.
9"""
10import base64
11import os
12import os.path
13import hashlib
14import json
15import string
16import sys
17from random import SystemRandom
19from sm.core import util
20import XenAPI # pylint: disable=import-error
23PROGRAM_NAME = 'keymanagerutil'
26def load_key(key_hash, vdi_uuid):
27 """
28 load_key is called by SM plugin when it needs to find the key for
29 specified key_hash from the key store
30 """
31 _check_key(key_hash, vdi_uuid)
32 try:
33 key = KeyManager(key_hash=key_hash).get_key(log_key_info=False)
34 return key
35 except KeyLookUpError:
36 return None
39def _check_key(key_hash, vdi_uuid):
40 session = XenAPI.xapi_local()
41 session.xenapi.login_with_password('root', '', '', PROGRAM_NAME)
42 try:
43 vdi = session.xenapi.VDI.get_by_uuid(vdi_uuid)
44 sm_config = session.xenapi.VDI.get_sm_config(vdi)
45 if 'key_hash' in sm_config:
46 if key_hash != sm_config['key_hash']:
47 raise Exception('A key was requested with key hash {}'
48 ' for VDI {}, but it has a different'
49 ' key_hash in its sm_config:'
50 ' {}'.format(key_hash, vdi_uuid, sm_config['key_hash']))
51 else:
52 raise Exception('Encryption key requested for VDI {}'
53 ' whose sm_config does not contain the key_hash'
54 ' entry. Its sm_config is {}'.format(vdi_uuid, sm_config))
55 finally:
56 session.xenapi.logout()
59class InputError(Exception):
60 def __init__(self, message):
61 super(InputError, self).__init__(message)
64class KeyLookUpError(Exception):
65 """Raised when the key / key hash we've requested is not in the keystore"""
67 def __init__(self, message):
68 super(KeyLookUpError, self).__init__(message)
71def _print_key_info(key=None, key_hash=None):
72 """
73 Output the key details as JSON to the standard output. This output
74 will be interpreted by XenRT.
75 """
76 data = {}
77 if key:
78 data['key_base64'] = base64.b64encode(key).decode()
79 if key_hash: 79 ↛ 81line 79 didn't jump to line 81, because the condition on line 79 was never false
80 data['key_hash'] = key_hash
81 print(json.dumps(data))
84KEYSTORE_PATH = '/tmp/keystore.json'
87def _read_keystore():
88 """If the keystore file exists, returns its contents, otherwise returns an empty dictionary."""
89 if os.path.isfile(KEYSTORE_PATH):
90 with open(KEYSTORE_PATH, "r") as key_store_file:
91 key_store = json.load(key_store_file)
92 for key_hash in key_store:
93 key_base64 = key_store[key_hash]
94 key = base64.b64decode(key_base64)
95 key_store[key_hash] = key
96 return key_store
97 else:
98 util.SMlog(f'Keystore path {KEYSTORE_PATH} not found')
99 return {}
102def _write_keystore(key_store):
103 """
104 Write the given key store contents to the key store file, which will be
105 created if it does not exist.
106 """
107 for key_hash in key_store:
108 key = key_store[key_hash]
109 key_base64 = base64.b64encode(key).decode()
110 key_store[key_hash] = key_base64
111 with open(KEYSTORE_PATH, "w+") as key_store_file:
112 json.dump(key_store, key_store_file)
113 key_store_file.write("\n")
116class KeyManager(object):
117 """
118 KeyManager is a python utility tool for generating and managing the keys in the jey store.
119 One can request KeyManager to generate the keys, passing just the type of
120 the key - either strong or weak or even the length of the key.
121 One can request KeyManger to get the key from the key store by passing key_hash.
122 One can request KeyManager to get the key_hash from the key store by passing encryption key.
123 KeyManager maintains the keystore(json record) under /tmp/keystore.json.
125 """
127 def __init__(self, key_type=None, key_length=None, key=None, key_hash=None):
128 self.key_type = key_type
129 self.key_length = key_length
130 self.key = key
131 self.key_hash = key_hash
133 def __add_to_keystore(self):
134 """
135 Update the key and key hash in the key store - requires both hash and
136 key.
137 """
138 if not self.key_hash or not self.key: 138 ↛ 139line 138 didn't jump to line 139, because the condition on line 138 was never true
139 raise InputError("Need both key_hash and key to update into key store")
140 key_store = _read_keystore()
141 key_store[self.key_hash] = self.key
142 _write_keystore(key_store)
144 def __hash_key(self):
146 # hash the given key - requires key
147 if not self.key: 147 ↛ 148line 147 didn't jump to line 148, because the condition on line 147 was never true
148 raise InputError("Need key to hash")
150 hash_it = hashlib.new('sha256')
151 hash_it.update(b'\0' * 32)
152 hash_it.update(self.key)
153 self.key_hash = hash_it.hexdigest()
154 return self.key_hash
156 def generate(self):
157 """
158 generate the encryption key
159 Hash the generated key
160 Update the key store with key and hash
161 """
162 util.SMlog('Generating key')
163 self.key = _get_key_generator(key_length=self.key_length, key_type=self.key_type).generate()
164 self.key_hash = self.__hash_key()
165 _print_key_info(key=self.key, key_hash=self.key_hash)
166 util.SMlog(f'Generated key, hash {self.key_hash}')
167 self.__add_to_keystore()
169 def get_key(self, log_key_info=True):
170 """Fetch the key from the key store based on the key_hash - requires key hash"""
171 if not self.key_hash:
172 util.SMlog('No key hash set, cannot retrieve unknown key')
173 raise InputError("Need key hash to get the key from the key store")
175 key_store = _read_keystore()
176 key = key_store.get(self.key_hash, None)
177 if key and log_key_info: 177 ↛ 178line 177 didn't jump to line 178, because the condition on line 177 was never true
178 _print_key_info(key=key)
179 if not key:
180 util.SMlog(f'No key found in keystore for hash {self.key_hash}, known hashes {key_store.keys()}')
181 raise KeyLookUpError("No keys in the keystore which matches the given key hash")
183 return key
185 def get_keyhash(self):
186 """Fetch the key hash from the key store based on the key - requires key"""
187 if not self.key: 187 ↛ 188line 187 didn't jump to line 188, because the condition on line 187 was never true
188 raise InputError("Need key to get the key hash from the key store")
189 key_store = _read_keystore()
190 try:
191 hashes = [x for x in key_store.keys() if key_store[x] == self.key]
192 key_hash = hashes[0] if hashes else None
193 _print_key_info(key_hash=key_hash)
194 except ValueError:
195 raise KeyLookUpError("No key hash in the keystore which matches the given key")
197 def update_keystore(self):
198 """If this key hash is already in the key store, update its corresponding key"""
200 if not (self.key_hash and self.key):
201 raise InputError("Need key hash and key to update the key store")
203 key_store = _read_keystore()
204 if self.key_hash in key_store:
205 key_store[self.key_hash] = self.key
206 else:
207 raise InputError("No existing key in the keystore"
208 "with key hash {}".format(self.key_hash))
209 _write_keystore(key_store)
212def _get_key_generator(key_length=None, key_type=None):
213 if key_type == "alphanumeric": 213 ↛ 214line 213 didn't jump to line 214, because the condition on line 213 was never true
214 return AlphaNumericKeyGenerator(key_length=key_length)
215 elif key_length: 215 ↛ 216line 215 didn't jump to line 216, because the condition on line 215 was never true
216 return RandomKeyGenerator(key_length=key_length)
217 elif key_type == "weak": 217 ↛ 218line 217 didn't jump to line 218, because the condition on line 217 was never true
218 return WeakKeyGenerator()
219 elif key_type == "strong": 219 ↛ 222line 219 didn't jump to line 222, because the condition on line 219 was never false
220 return StrongKeyGenerator()
221 else:
222 raise InputError("Either key_length in byte or key_type(\"strong OR weak\")"
223 " should be specified to generate the key")
226class RandomKeyGenerator(object):
227 """Generates a completely random key of the specified length"""
229 def __init__(self, key_length):
230 self.key_length = key_length
232 def generate(self):
233 """Generate a completely random byte sequence"""
234 return os.urandom(self.key_length)
237class StrongKeyGenerator(RandomKeyGenerator):
238 """Generates a completely random 512-bit key"""
240 def __init__(self):
241 super(StrongKeyGenerator, self).__init__(key_length=64)
244class WeakKeyGenerator(RandomKeyGenerator):
245 """Generates a completely random 256-bit key"""
247 def __init__(self):
248 super(WeakKeyGenerator, self).__init__(key_length=32)
251class AlphaNumericKeyGenerator(object):
252 """Generates alphanumeric keys"""
254 def __init__(self, key_length=None):
255 self.key_length = 64 if key_length is None else key_length
257 def generate(self):
258 """Generate a completely random alphanumeric sequence"""
259 keys_from = string.ascii_letters + string.digits
260 return ("".join([SystemRandom().choice(keys_from) for _ in range(self.key_length)])).encode("utf-8")