serializer.py 3.67 KB
Newer Older
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
#
#

# Copyright (C) 2007, 2008 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

"""Serializer abstraction module

This module introduces a simple abstraction over the serialization
backend (currently json).

"""

import simplejson
import re
30
31
32
import hmac

from ganeti import errors
33

Guido Trotter's avatar
Guido Trotter committed
34
35
36
37
try:
  from hashlib import sha1
except ImportError:
  import sha as sha1
38

Michael Hanselmann's avatar
Michael Hanselmann committed
39

40
41
_JSON_INDENT = 2

42
_RE_EOLSP = re.compile('[ \t]+$', re.MULTILINE)
43
44


Michael Hanselmann's avatar
Michael Hanselmann committed
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
def _GetJsonDumpers():
  """Returns two JSON functions to serialize data.

  @rtype: (callable, callable)
  @return: The function to generate a compact form of JSON and another one to
           generate a more readable, indented form of JSON (if supported)

  """
  plain_dump = simplejson.dumps

  # Check whether the simplejson module supports indentation
  try:
    simplejson.dumps(1, indent=_JSON_INDENT)
  except TypeError:
    # Indentation not supported
    indent_dump = plain_dump
  else:
    # Indentation supported
    indent_dump = lambda data: simplejson.dumps(data, indent=_JSON_INDENT)

  assert callable(plain_dump)
  assert callable(indent_dump)

  return (plain_dump, indent_dump)


(_DumpJson, _DumpJsonIndent) = _GetJsonDumpers()


74
def DumpJson(data, indent=True):
75
76
  """Serialize a given object.

Iustin Pop's avatar
Iustin Pop committed
77
78
79
80
  @param data: the data to serialize
  @param indent: whether to indent output (depends on simplejson version)

  @return: the string representation of data
81

82
  """
Michael Hanselmann's avatar
Michael Hanselmann committed
83
84
  if indent:
    fn = _DumpJsonIndent
85
  else:
Michael Hanselmann's avatar
Michael Hanselmann committed
86
    fn = _DumpJson
87

Michael Hanselmann's avatar
Michael Hanselmann committed
88
  txt = _RE_EOLSP.sub("", fn(data))
89
90
  if not txt.endswith('\n'):
    txt += '\n'
Michael Hanselmann's avatar
Michael Hanselmann committed
91

92
93
94
  return txt


95
def LoadJson(txt):
96
97
  """Unserialize data from a string.

Iustin Pop's avatar
Iustin Pop committed
98
99
100
101
  @param txt: the json-encoded form

  @return: the original data

102
103
  """
  return simplejson.loads(txt)
104
105


106
107
108
109
110
111
112
113
114
115
116
117
118
119
def DumpSignedJson(data, key, salt=None):
  """Serialize a given object and authenticate it.

  @param data: the data to serialize
  @param key: shared hmac key
  @return: the string representation of data signed by the hmac key

  """
  txt = DumpJson(data, indent=False)
  if salt is None:
    salt = ''
  signed_dict = {
    'msg': txt,
    'salt': salt,
Guido Trotter's avatar
Guido Trotter committed
120
    'hmac': hmac.new(key, salt + txt, sha1).hexdigest(),
121
  }
Guido Trotter's avatar
Guido Trotter committed
122
  return DumpJson(signed_dict, indent=False)
123
124


125
def LoadSignedJson(txt, key):
126
127
128
129
130
  """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
  @rtype: tuple of original data, string
131
  @return: original data, salt
132
133
134
135
136
137
138
139
140
141
142
143
144
  @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')
  try:
    msg = signed_dict['msg']
    salt = signed_dict['salt']
    hmac_sign = signed_dict['hmac']
  except KeyError:
    raise errors.SignatureError('Invalid external message')

Guido Trotter's avatar
Guido Trotter committed
145
  if hmac.new(key, salt + msg, sha1).hexdigest() != hmac_sign:
146
147
    raise errors.SignatureError('Invalid Signature')

148
  return LoadJson(msg), salt
149
150


151
152
Dump = DumpJson
Load = LoadJson
153
154
DumpSigned = DumpSignedJson
LoadSigned = LoadSignedJson