confd/client: make it possible to update peer list
[ganeti-local] / lib / serializer.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Serializer abstraction module
22
23 This module introduces a simple abstraction over the serialization
24 backend (currently json).
25
26 """
27
28 import simplejson
29 import re
30 import hmac
31
32 from ganeti import errors
33
34 try:
35   from hashlib import sha1
36 except ImportError:
37   import sha as sha1
38
39 # Check whether the simplejson module supports indentation
40 _JSON_INDENT = 2
41 try:
42   simplejson.dumps(1, indent=_JSON_INDENT)
43 except TypeError:
44   _JSON_INDENT = None
45
46 _RE_EOLSP = re.compile('[ \t]+$', re.MULTILINE)
47
48
49 def DumpJson(data, indent=True):
50   """Serialize a given object.
51
52   @param data: the data to serialize
53   @param indent: whether to indent output (depends on simplejson version)
54
55   @return: the string representation of data
56
57   """
58   if not indent or _JSON_INDENT is None:
59     txt = simplejson.dumps(data)
60   else:
61     txt = simplejson.dumps(data, indent=_JSON_INDENT)
62
63   txt = _RE_EOLSP.sub("", txt)
64   if not txt.endswith('\n'):
65     txt += '\n'
66   return txt
67
68
69 def LoadJson(txt):
70   """Unserialize data from a string.
71
72   @param txt: the json-encoded form
73
74   @return: the original data
75
76   """
77   return simplejson.loads(txt)
78
79
80 def DumpSignedJson(data, key, salt=None):
81   """Serialize a given object and authenticate it.
82
83   @param data: the data to serialize
84   @param key: shared hmac key
85   @return: the string representation of data signed by the hmac key
86
87   """
88   txt = DumpJson(data, indent=False)
89   if salt is None:
90     salt = ''
91   signed_dict = {
92     'msg': txt,
93     'salt': salt,
94     'hmac': hmac.new(key, salt + txt, sha1).hexdigest(),
95   }
96   return DumpJson(signed_dict, indent=False)
97
98
99 def LoadSignedJson(txt, key):
100   """Verify that a given message was signed with the given key, and load it.
101
102   @param txt: json-encoded hmac-signed message
103   @param key: shared hmac key
104   @rtype: tuple of original data, string
105   @return: original data, salt
106   @raises errors.SignatureError: if the message signature doesn't verify
107
108   """
109   signed_dict = LoadJson(txt)
110   if not isinstance(signed_dict, dict):
111     raise errors.SignatureError('Invalid external message')
112   try:
113     msg = signed_dict['msg']
114     salt = signed_dict['salt']
115     hmac_sign = signed_dict['hmac']
116   except KeyError:
117     raise errors.SignatureError('Invalid external message')
118
119   if hmac.new(key, salt + msg, sha1).hexdigest() != hmac_sign:
120     raise errors.SignatureError('Invalid Signature')
121
122   return LoadJson(msg), salt
123
124
125 Dump = DumpJson
126 Load = LoadJson
127 DumpSigned = DumpSignedJson
128 LoadSigned = LoadSignedJson