X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/f4a2f532f9c6243444ab52b67fad18e0628e036e..dc2cc657c8c6154b35731ac8166fcf25b9a4f244:/lib/serializer.py diff --git a/lib/serializer.py b/lib/serializer.py index 9781187..cbc11fa 100644 --- a/lib/serializer.py +++ b/lib/serializer.py @@ -24,42 +24,40 @@ This module introduces a simple abstraction over the serialization backend (currently json). """ +# pylint: disable=C0103 + +# C0103: Invalid name, since pylint doesn't see that Dump points to a +# function and not a constant -import simplejson import re -import hmac -import hashlib -from ganeti import errors +# Python 2.6 and above contain a JSON module based on simplejson. Unfortunately +# the standard library version is significantly slower than the external +# module. While it should be better from at least Python 3.2 on (see Python +# issue 7451), for now Ganeti needs to work well with older Python versions +# too. +import simplejson +from ganeti import errors +from ganeti import utils -# Check whether the simplejson module supports indentation -_JSON_INDENT = 2 -try: - simplejson.dumps(1, indent=_JSON_INDENT) -except TypeError: - _JSON_INDENT = None -_RE_EOLSP = re.compile('[ \t]+$', re.MULTILINE) +_RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE) -def DumpJson(data, indent=True): +def DumpJson(data): """Serialize a given object. @param data: the data to serialize - @param indent: whether to indent output (depends on simplejson version) - @return: the string representation of data """ - if not indent or _JSON_INDENT is None: - txt = simplejson.dumps(data) - else: - txt = simplejson.dumps(data, indent=_JSON_INDENT) + encoded = simplejson.dumps(data) + + txt = _RE_EOLSP.sub("", encoded) + if not txt.endswith("\n"): + txt += "\n" - txt = _RE_EOLSP.sub("", txt) - if not txt.endswith('\n'): - txt += '\n' return txt @@ -74,101 +72,72 @@ def LoadJson(txt): return simplejson.loads(txt) -def DumpSignedJson(data, key, salt=None): +def DumpSignedJson(data, key, salt=None, key_selector=None): """Serialize a given object and authenticate it. @param data: the data to serialize @param key: shared hmac key + @param key_selector: name/id that identifies the key (in case there are + multiple keys in use, e.g. in a multi-cluster environment) @return: the string representation of data signed by the hmac key """ - txt = DumpJson(data, indent=False) + txt = DumpJson(data) if salt is None: - salt = '' + salt = "" signed_dict = { - 'msg': txt, - 'salt': salt, - 'hmac': hmac.new(key, salt + txt, hashlib.sha256).hexdigest(), - } + "msg": txt, + "salt": salt, + } + + if key_selector: + signed_dict["key_selector"] = key_selector + else: + key_selector = "" + + signed_dict["hmac"] = utils.Sha1Hmac(key, txt, salt=salt + key_selector) + return DumpJson(signed_dict) -def LoadSignedJson(txt, key, salt_verifier=None): +def LoadSignedJson(txt, key): """Verify that a given message was signed with the given key, and load it. @param txt: json-encoded hmac-signed message - @param key: shared hmac key - @param salt_verifier: function taking a salt as input and returning boolean + @param key: the shared hmac key or a callable taking one argument (the key + selector), which returns the hmac key belonging to the key selector. + Typical usage is to pass a reference to the get method of a dict. @rtype: tuple of original data, string - @return: (original data, salt) + @return: original data, salt @raises errors.SignatureError: if the message signature doesn't verify """ signed_dict = LoadJson(txt) if not isinstance(signed_dict, dict): - raise errors.SignatureError('Invalid external message') + raise errors.SignatureError("Invalid external message") try: - msg = signed_dict['msg'] - salt = signed_dict['salt'] - hmac_sign = signed_dict['hmac'] + msg = signed_dict["msg"] + salt = signed_dict["salt"] + hmac_sign = signed_dict["hmac"] except KeyError: - raise errors.SignatureError('Invalid external message') - - if salt and not salt_verifier: - raise errors.SignatureError('Salted message is not verified') - elif salt_verifier is not None: - if not salt_verifier(salt): - raise errors.SignatureError('Invalid salt') - - if hmac.new(key, salt + msg, hashlib.sha256).hexdigest() != hmac_sign: - raise errors.SignatureError('Invalid Signature') - return LoadJson(msg) - - -def SaltEqualTo(expected): - """Helper salt verifier function that checks for equality. - - @type expected: string - @param expected: expected salt - @rtype: function - @return: salt verifier that returns True if the target salt is "x" - - """ - return lambda salt: salt == expected - - -def SaltIn(expected): - """Helper salt verifier function that checks for equality. - - @type expected: collection - @param expected: collection of possible valid salts - @rtype: function - @return: salt verifier that returns True if the salt is in the collection - - """ - return lambda salt: salt in expected - - -def SaltInRange(min, max): - """Helper salt verifier function that checks for equality. - - @type min: integer - @param min: minimum salt value - @type max: integer - @param max: maximum salt value - @rtype: function - @return: salt verifier that returns True if the salt is in the min,max range - - """ - def _CheckSaltInRange(salt): - try: - i_salt = int(salt) - except (TypeError, ValueError), err: - return False + raise errors.SignatureError("Invalid external message") + + if callable(key): + # pylint: disable=E1103 + key_selector = signed_dict.get("key_selector", None) + hmac_key = key(key_selector) + if not hmac_key: + raise errors.SignatureError("No key with key selector '%s' found" % + key_selector) + else: + key_selector = "" + hmac_key = key - return i_salt > min and i_salt < max + if not utils.VerifySha1Hmac(hmac_key, msg, hmac_sign, + salt=salt + key_selector): + raise errors.SignatureError("Invalid Signature") - return _CheckSaltInRange + return LoadJson(msg), salt Dump = DumpJson