Revision f4a2f532

b/lib/errors.py
98 98
  pass
99 99

  
100 100

  
101
class SignatureError(GenericError):
102
  """Error authenticating a remote message.
103

  
104
  This is raised when the hmac signature on a message doesn't verify correctly
105
  to the message itself. It can happen because of network unreliability or
106
  because of spurious traffic.
107

  
108
  """
109
  pass
110

  
111

  
101 112
class ParameterError(GenericError):
102 113
  """A passed parameter to a command is invalid.
103 114

  
b/lib/serializer.py
27 27

  
28 28
import simplejson
29 29
import re
30
import hmac
31
import hashlib
32

  
33
from ganeti import errors
30 34

  
31 35

  
32 36
# Check whether the simplejson module supports indentation
......
70 74
  return simplejson.loads(txt)
71 75

  
72 76

  
77
def DumpSignedJson(data, key, salt=None):
78
  """Serialize a given object and authenticate it.
79

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

  
84
  """
85
  txt = DumpJson(data, indent=False)
86
  if salt is None:
87
    salt = ''
88
  signed_dict = {
89
    'msg': txt,
90
    'salt': salt,
91
    'hmac': hmac.new(key, salt + txt, hashlib.sha256).hexdigest(),
92
  }
93
  return DumpJson(signed_dict)
94

  
95

  
96
def LoadSignedJson(txt, key, salt_verifier=None):
97
  """Verify that a given message was signed with the given key, and load it.
98

  
99
  @param txt: json-encoded hmac-signed message
100
  @param key: shared hmac key
101
  @param salt_verifier: function taking a salt as input and returning boolean
102
  @rtype: tuple of original data, string
103
  @return: (original data, salt)
104
  @raises errors.SignatureError: if the message signature doesn't verify
105

  
106
  """
107
  signed_dict = LoadJson(txt)
108
  if not isinstance(signed_dict, dict):
109
    raise errors.SignatureError('Invalid external message')
110
  try:
111
    msg = signed_dict['msg']
112
    salt = signed_dict['salt']
113
    hmac_sign = signed_dict['hmac']
114
  except KeyError:
115
    raise errors.SignatureError('Invalid external message')
116

  
117
  if salt and not salt_verifier:
118
    raise errors.SignatureError('Salted message is not verified')
119
  elif salt_verifier is not None:
120
    if not salt_verifier(salt):
121
      raise errors.SignatureError('Invalid salt')
122

  
123
  if hmac.new(key, salt + msg, hashlib.sha256).hexdigest() != hmac_sign:
124
    raise errors.SignatureError('Invalid Signature')
125
  return LoadJson(msg)
126

  
127

  
128
def SaltEqualTo(expected):
129
  """Helper salt verifier function that checks for equality.
130

  
131
  @type expected: string
132
  @param expected: expected salt
133
  @rtype: function
134
  @return: salt verifier that returns True if the target salt is "x"
135

  
136
  """
137
  return lambda salt: salt == expected
138

  
139

  
140
def SaltIn(expected):
141
  """Helper salt verifier function that checks for equality.
142

  
143
  @type expected: collection
144
  @param expected: collection of possible valid salts
145
  @rtype: function
146
  @return: salt verifier that returns True if the salt is in the collection
147

  
148
  """
149
  return lambda salt: salt in expected
150

  
151

  
152
def SaltInRange(min, max):
153
  """Helper salt verifier function that checks for equality.
154

  
155
  @type min: integer
156
  @param min: minimum salt value
157
  @type max: integer
158
  @param max: maximum salt value
159
  @rtype: function
160
  @return: salt verifier that returns True if the salt is in the min,max range
161

  
162
  """
163
  def _CheckSaltInRange(salt):
164
    try:
165
      i_salt = int(salt)
166
    except (TypeError, ValueError), err:
167
      return False
168

  
169
    return i_salt > min and i_salt < max
170

  
171
  return _CheckSaltInRange
172

  
173

  
73 174
Dump = DumpJson
74 175
Load = LoadJson
176
DumpSigned = DumpSignedJson
177
LoadSigned = LoadSignedJson
b/test/ganeti.serializer_unittest.py
25 25
import unittest
26 26

  
27 27
from ganeti import serializer
28
from ganeti import errors
28 29

  
29 30

  
30 31
class SimplejsonMock(object):
......
59 60
  def testJson(self):
60 61
    return self._TestSerializer(serializer.DumpJson, serializer.LoadJson)
61 62

  
63
  def testSignedMessage(self):
64
    LoadSigned = serializer.LoadSigned
65
    DumpSigned = serializer.DumpSigned
66
    SaltEqualTo = serializer.SaltEqualTo
67
    SaltIn = serializer.SaltIn
68
    SaltInRange = serializer.SaltInRange
69

  
70
    for data in self._TESTDATA:
71
      self.assertEqual(LoadSigned(DumpSigned(data, "mykey"), "mykey"), data)
72
      self.assertEqual(LoadSigned(
73
                         DumpSigned(data, "myprivatekey", "mysalt"),
74
                         "myprivatekey", SaltEqualTo("mysalt")), data)
75
      self.assertEqual(LoadSigned(
76
                         DumpSigned(data, "myprivatekey", "mysalt"),
77
                         "myprivatekey", SaltIn(["notmysalt", "mysalt"])), data)
78
      self.assertEqual(LoadSigned(
79
                         DumpSigned(data, "myprivatekey", "12345"),
80
                         "myprivatekey", SaltInRange(12340, 12346)), data)
81
    self.assertRaises(errors.SignatureError, serializer.LoadSigned,
82
                      serializer.DumpSigned("test", "myprivatekey"),
83
                      "myotherkey")
84
    self.assertRaises(errors.SignatureError, serializer.LoadSigned,
85
                      serializer.DumpSigned("test", "myprivatekey", "salt"),
86
                      "myprivatekey")
87
    self.assertRaises(errors.SignatureError, serializer.LoadSigned,
88
                      serializer.DumpSigned("test", "myprivatekey", "salt"),
89
                      "myprivatekey", SaltIn(["notmysalt", "morenotmysalt"]))
90
    self.assertRaises(errors.SignatureError, serializer.LoadSigned,
91
                      serializer.DumpSigned("test", "myprivatekey", "salt"),
92
                      "myprivatekey", SaltInRange(1, 2))
93
    self.assertRaises(errors.SignatureError, serializer.LoadSigned,
94
                      serializer.DumpSigned("test", "myprivatekey", "12345"),
95
                      "myprivatekey", SaltInRange(1, 2))
96

  
62 97
  def _TestSerializer(self, dump_fn, load_fn):
63 98
    for data in self._TESTDATA:
64 99
      self.failUnless(dump_fn(data).endswith("\n"))

Also available in: Unified diff