Revision f21bb4b7

b/Makefile.am
214 214
utils_PYTHON = \
215 215
	lib/utils/__init__.py \
216 216
	lib/utils/algo.py \
217
	lib/utils/hash.py \
217 218
	lib/utils/log.py \
218 219
	lib/utils/mlock.py \
219 220
	lib/utils/retry.py \
......
484 485
	test/ganeti.ssh_unittest.py \
485 486
	test/ganeti.uidpool_unittest.py \
486 487
	test/ganeti.utils.algo_unittest.py \
488
	test/ganeti.utils.hash_unittest.py \
487 489
	test/ganeti.utils.mlock_unittest.py \
488 490
	test/ganeti.utils.retry_unittest.py \
489 491
	test/ganeti.utils.text_unittest.py \
b/lib/utils/__init__.py
46 46
import OpenSSL
47 47
import datetime
48 48
import calendar
49
import hmac
50 49

  
51 50
from cStringIO import StringIO
52 51

  
......
59 58
from ganeti.utils.text import * # pylint: disable-msg=W0401
60 59
from ganeti.utils.mlock import * # pylint: disable-msg=W0401
61 60
from ganeti.utils.log import * # pylint: disable-msg=W0401
61
from ganeti.utils.hash import * # pylint: disable-msg=W0401
62 62

  
63 63
_locksheld = []
64 64

  
......
850 850
                     " '_once_lock' and '_name_sequence' attributes")
851 851

  
852 852

  
853
def _FingerprintFile(filename):
854
  """Compute the fingerprint of a file.
855

  
856
  If the file does not exist, a None will be returned
857
  instead.
858

  
859
  @type filename: str
860
  @param filename: the filename to checksum
861
  @rtype: str
862
  @return: the hex digest of the sha checksum of the contents
863
      of the file
864

  
865
  """
866
  if not (os.path.exists(filename) and os.path.isfile(filename)):
867
    return None
868

  
869
  f = open(filename)
870

  
871
  fp = compat.sha1_hash()
872
  while True:
873
    data = f.read(4096)
874
    if not data:
875
      break
876

  
877
    fp.update(data)
878

  
879
  return fp.hexdigest()
880

  
881

  
882
def FingerprintFiles(files):
883
  """Compute fingerprints for a list of files.
884

  
885
  @type files: list
886
  @param files: the list of filename to fingerprint
887
  @rtype: dict
888
  @return: a dictionary filename: fingerprint, holding only
889
      existing files
890

  
891
  """
892
  ret = {}
893

  
894
  for filename in files:
895
    cksum = _FingerprintFile(filename)
896
    if cksum:
897
      ret[filename] = cksum
898

  
899
  return ret
900

  
901

  
902 853
def ForceDictType(target, key_types, allowed_values=None):
903 854
  """Force the values of a dict to have certain types.
904 855

  
......
2541 2492
  return (cert, salt)
2542 2493

  
2543 2494

  
2544
def Sha1Hmac(key, text, salt=None):
2545
  """Calculates the HMAC-SHA1 digest of a text.
2546

  
2547
  HMAC is defined in RFC2104.
2548

  
2549
  @type key: string
2550
  @param key: Secret key
2551
  @type text: string
2552

  
2553
  """
2554
  if salt:
2555
    salted_text = salt + text
2556
  else:
2557
    salted_text = text
2558

  
2559
  return hmac.new(key, salted_text, compat.sha1).hexdigest()
2560

  
2561

  
2562
def VerifySha1Hmac(key, text, digest, salt=None):
2563
  """Verifies the HMAC-SHA1 digest of a text.
2564

  
2565
  HMAC is defined in RFC2104.
2566

  
2567
  @type key: string
2568
  @param key: Secret key
2569
  @type text: string
2570
  @type digest: string
2571
  @param digest: Expected digest
2572
  @rtype: bool
2573
  @return: Whether HMAC-SHA1 digest matches
2574

  
2575
  """
2576
  return digest.lower() == Sha1Hmac(key, text, salt=salt).lower()
2577

  
2578

  
2579 2495
def FindMatch(data, name):
2580 2496
  """Tries to find an item in a dictionary matching a name.
2581 2497

  
b/lib/utils/hash.py
1
#
2
#
3

  
4
# Copyright (C) 2006, 2007, 2010, 2011 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
"""Utility functions for hashing.
22

  
23
"""
24

  
25
import os
26
import hmac
27

  
28
from ganeti import compat
29

  
30

  
31
def Sha1Hmac(key, text, salt=None):
32
  """Calculates the HMAC-SHA1 digest of a text.
33

  
34
  HMAC is defined in RFC2104.
35

  
36
  @type key: string
37
  @param key: Secret key
38
  @type text: string
39

  
40
  """
41
  if salt:
42
    salted_text = salt + text
43
  else:
44
    salted_text = text
45

  
46
  return hmac.new(key, salted_text, compat.sha1).hexdigest()
47

  
48

  
49
def VerifySha1Hmac(key, text, digest, salt=None):
50
  """Verifies the HMAC-SHA1 digest of a text.
51

  
52
  HMAC is defined in RFC2104.
53

  
54
  @type key: string
55
  @param key: Secret key
56
  @type text: string
57
  @type digest: string
58
  @param digest: Expected digest
59
  @rtype: bool
60
  @return: Whether HMAC-SHA1 digest matches
61

  
62
  """
63
  return digest.lower() == Sha1Hmac(key, text, salt=salt).lower()
64

  
65

  
66
def _FingerprintFile(filename):
67
  """Compute the fingerprint of a file.
68

  
69
  If the file does not exist, a None will be returned
70
  instead.
71

  
72
  @type filename: str
73
  @param filename: the filename to checksum
74
  @rtype: str
75
  @return: the hex digest of the sha checksum of the contents
76
      of the file
77

  
78
  """
79
  if not (os.path.exists(filename) and os.path.isfile(filename)):
80
    return None
81

  
82
  f = open(filename)
83

  
84
  fp = compat.sha1_hash()
85
  while True:
86
    data = f.read(4096)
87
    if not data:
88
      break
89

  
90
    fp.update(data)
91

  
92
  return fp.hexdigest()
93

  
94

  
95
def FingerprintFiles(files):
96
  """Compute fingerprints for a list of files.
97

  
98
  @type files: list
99
  @param files: the list of filename to fingerprint
100
  @rtype: dict
101
  @return: a dictionary filename: fingerprint, holding only
102
      existing files
103

  
104
  """
105
  ret = {}
106

  
107
  for filename in files:
108
    cksum = _FingerprintFile(filename)
109
    if cksum:
110
      ret[filename] = cksum
111

  
112
  return ret
b/test/ganeti.utils.hash_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007, 2010, 2011 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

  
22
"""Script for testing ganeti.utils.hash"""
23

  
24
import unittest
25
import random
26
import operator
27
import tempfile
28

  
29
from ganeti import constants
30
from ganeti import utils
31

  
32
import testutils
33

  
34

  
35
class TestHmacFunctions(unittest.TestCase):
36
  # Digests can be checked with "openssl sha1 -hmac $key"
37
  def testSha1Hmac(self):
38
    self.assertEqual(utils.Sha1Hmac("", ""),
39
                     "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d")
40
    self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World"),
41
                     "ef4f3bda82212ecb2f7ce868888a19092481f1fd")
42
    self.assertEqual(utils.Sha1Hmac("TguMTA2K", ""),
43
                     "f904c2476527c6d3e6609ab683c66fa0652cb1dc")
44

  
45
    longtext = 1500 * "The quick brown fox jumps over the lazy dog\n"
46
    self.assertEqual(utils.Sha1Hmac("3YzMxZWE", longtext),
47
                     "35901b9a3001a7cdcf8e0e9d7c2e79df2223af54")
48

  
49
  def testSha1HmacSalt(self):
50
    self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc0"),
51
                     "4999bf342470eadb11dfcd24ca5680cf9fd7cdce")
52
    self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc9"),
53
                     "17a4adc34d69c0d367d4ffbef96fd41d4df7a6e8")
54
    self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World", salt="xyz0"),
55
                     "7f264f8114c9066afc9bb7636e1786d996d3cc0d")
56

  
57
  def testVerifySha1Hmac(self):
58
    self.assert_(utils.VerifySha1Hmac("", "", ("fbdb1d1b18aa6c08324b"
59
                                               "7d64b71fb76370690e1d")))
60
    self.assert_(utils.VerifySha1Hmac("TguMTA2K", "",
61
                                      ("f904c2476527c6d3e660"
62
                                       "9ab683c66fa0652cb1dc")))
63

  
64
    digest = "ef4f3bda82212ecb2f7ce868888a19092481f1fd"
65
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", digest))
66
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
67
                                      digest.lower()))
68
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
69
                                      digest.upper()))
70
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
71
                                      digest.title()))
72

  
73
  def testVerifySha1HmacSalt(self):
74
    self.assert_(utils.VerifySha1Hmac("TguMTA2K", "",
75
                                      ("17a4adc34d69c0d367d4"
76
                                       "ffbef96fd41d4df7a6e8"),
77
                                      salt="abc9"))
78
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
79
                                      ("7f264f8114c9066afc9b"
80
                                       "b7636e1786d996d3cc0d"),
81
                                      salt="xyz0"))
82

  
83

  
84
class TestFingerprintFiles(unittest.TestCase):
85
  def setUp(self):
86
    self.tmpfile = tempfile.NamedTemporaryFile()
87
    self.tmpfile2 = tempfile.NamedTemporaryFile()
88
    utils.WriteFile(self.tmpfile2.name, data="Hello World\n")
89
    self.results = {
90
      self.tmpfile.name: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
91
      self.tmpfile2.name: "648a6a6ffffdaa0badb23b8baf90b6168dd16b3a",
92
      }
93

  
94
  def testSingleFile(self):
95
    self.assertEqual(utils.hash._FingerprintFile(self.tmpfile.name),
96
                     self.results[self.tmpfile.name])
97

  
98
    self.assertEqual(utils.hash._FingerprintFile("/no/such/file"), None)
99

  
100
  def testBigFile(self):
101
    self.tmpfile.write("A" * 8192)
102
    self.tmpfile.flush()
103
    self.assertEqual(utils.hash._FingerprintFile(self.tmpfile.name),
104
                     "35b6795ca20d6dc0aff8c7c110c96cd1070b8c38")
105

  
106
  def testMultiple(self):
107
    all_files = self.results.keys()
108
    all_files.append("/no/such/file")
109
    self.assertEqual(utils.FingerprintFiles(self.results.keys()), self.results)
110

  
111

  
112
if __name__ == "__main__":
113
  testutils.GanetiTestProgram()
b/test/ganeti.utils_unittest.py
1471 1471
                      utils.RunInSeparateProcess, _exc)
1472 1472

  
1473 1473

  
1474
class TestFingerprintFiles(unittest.TestCase):
1475
  def setUp(self):
1476
    self.tmpfile = tempfile.NamedTemporaryFile()
1477
    self.tmpfile2 = tempfile.NamedTemporaryFile()
1478
    utils.WriteFile(self.tmpfile2.name, data="Hello World\n")
1479
    self.results = {
1480
      self.tmpfile.name: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
1481
      self.tmpfile2.name: "648a6a6ffffdaa0badb23b8baf90b6168dd16b3a",
1482
      }
1483

  
1484
  def testSingleFile(self):
1485
    self.assertEqual(utils._FingerprintFile(self.tmpfile.name),
1486
                     self.results[self.tmpfile.name])
1487

  
1488
    self.assertEqual(utils._FingerprintFile("/no/such/file"), None)
1489

  
1490
  def testBigFile(self):
1491
    self.tmpfile.write("A" * 8192)
1492
    self.tmpfile.flush()
1493
    self.assertEqual(utils._FingerprintFile(self.tmpfile.name),
1494
                     "35b6795ca20d6dc0aff8c7c110c96cd1070b8c38")
1495

  
1496
  def testMultiple(self):
1497
    all_files = self.results.keys()
1498
    all_files.append("/no/such/file")
1499
    self.assertEqual(utils.FingerprintFiles(self.results.keys()), self.results)
1500

  
1501

  
1502 1474
class TestGenerateSelfSignedX509Cert(unittest.TestCase):
1503 1475
  def setUp(self):
1504 1476
    self.tmpdir = tempfile.mkdtemp()
......
1829 1801
    self.assertEqual(errcode, utils.CERT_ERROR)
1830 1802

  
1831 1803

  
1832
class TestHmacFunctions(unittest.TestCase):
1833
  # Digests can be checked with "openssl sha1 -hmac $key"
1834
  def testSha1Hmac(self):
1835
    self.assertEqual(utils.Sha1Hmac("", ""),
1836
                     "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d")
1837
    self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World"),
1838
                     "ef4f3bda82212ecb2f7ce868888a19092481f1fd")
1839
    self.assertEqual(utils.Sha1Hmac("TguMTA2K", ""),
1840
                     "f904c2476527c6d3e6609ab683c66fa0652cb1dc")
1841

  
1842
    longtext = 1500 * "The quick brown fox jumps over the lazy dog\n"
1843
    self.assertEqual(utils.Sha1Hmac("3YzMxZWE", longtext),
1844
                     "35901b9a3001a7cdcf8e0e9d7c2e79df2223af54")
1845

  
1846
  def testSha1HmacSalt(self):
1847
    self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc0"),
1848
                     "4999bf342470eadb11dfcd24ca5680cf9fd7cdce")
1849
    self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc9"),
1850
                     "17a4adc34d69c0d367d4ffbef96fd41d4df7a6e8")
1851
    self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World", salt="xyz0"),
1852
                     "7f264f8114c9066afc9bb7636e1786d996d3cc0d")
1853

  
1854
  def testVerifySha1Hmac(self):
1855
    self.assert_(utils.VerifySha1Hmac("", "", ("fbdb1d1b18aa6c08324b"
1856
                                               "7d64b71fb76370690e1d")))
1857
    self.assert_(utils.VerifySha1Hmac("TguMTA2K", "",
1858
                                      ("f904c2476527c6d3e660"
1859
                                       "9ab683c66fa0652cb1dc")))
1860

  
1861
    digest = "ef4f3bda82212ecb2f7ce868888a19092481f1fd"
1862
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", digest))
1863
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
1864
                                      digest.lower()))
1865
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
1866
                                      digest.upper()))
1867
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
1868
                                      digest.title()))
1869

  
1870
  def testVerifySha1HmacSalt(self):
1871
    self.assert_(utils.VerifySha1Hmac("TguMTA2K", "",
1872
                                      ("17a4adc34d69c0d367d4"
1873
                                       "ffbef96fd41d4df7a6e8"),
1874
                                      salt="abc9"))
1875
    self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World",
1876
                                      ("7f264f8114c9066afc9b"
1877
                                       "b7636e1786d996d3cc0d"),
1878
                                      salt="xyz0"))
1879

  
1880

  
1881 1804
class TestIgnoreSignals(unittest.TestCase):
1882 1805
  """Test the IgnoreSignals decorator"""
1883 1806

  

Also available in: Unified diff