From f21bb4b779544359a65bb7ef459df5a121d59b4c Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Mon, 10 Jan 2011 16:13:53 +0100 Subject: [PATCH] utils: Move hashing-related code into separate file Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- Makefile.am | 2 + lib/utils/__init__.py | 86 +--------------------- lib/utils/hash.py | 112 ++++++++++++++++++++++++++++ test/ganeti.utils.hash_unittest.py | 113 +++++++++++++++++++++++++++++ test/ganeti.utils_unittest.py | 77 -------------------- 5 files changed, 228 insertions(+), 162 deletions(-) create mode 100644 lib/utils/hash.py create mode 100755 test/ganeti.utils.hash_unittest.py diff --git a/Makefile.am b/Makefile.am index a8f133e99..7704dd97f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -214,6 +214,7 @@ server_PYTHON = \ utils_PYTHON = \ lib/utils/__init__.py \ lib/utils/algo.py \ + lib/utils/hash.py \ lib/utils/log.py \ lib/utils/mlock.py \ lib/utils/retry.py \ @@ -484,6 +485,7 @@ python_tests = \ test/ganeti.ssh_unittest.py \ test/ganeti.uidpool_unittest.py \ test/ganeti.utils.algo_unittest.py \ + test/ganeti.utils.hash_unittest.py \ test/ganeti.utils.mlock_unittest.py \ test/ganeti.utils.retry_unittest.py \ test/ganeti.utils.text_unittest.py \ diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py index c1cec2fd0..b8769656e 100644 --- a/lib/utils/__init__.py +++ b/lib/utils/__init__.py @@ -46,7 +46,6 @@ import signal import OpenSSL import datetime import calendar -import hmac from cStringIO import StringIO @@ -59,6 +58,7 @@ from ganeti.utils.retry import * # pylint: disable-msg=W0401 from ganeti.utils.text import * # pylint: disable-msg=W0401 from ganeti.utils.mlock import * # pylint: disable-msg=W0401 from ganeti.utils.log import * # pylint: disable-msg=W0401 +from ganeti.utils.hash import * # pylint: disable-msg=W0401 _locksheld = [] @@ -850,55 +850,6 @@ def ResetTempfileModule(): " '_once_lock' and '_name_sequence' attributes") -def _FingerprintFile(filename): - """Compute the fingerprint of a file. - - If the file does not exist, a None will be returned - instead. - - @type filename: str - @param filename: the filename to checksum - @rtype: str - @return: the hex digest of the sha checksum of the contents - of the file - - """ - if not (os.path.exists(filename) and os.path.isfile(filename)): - return None - - f = open(filename) - - fp = compat.sha1_hash() - while True: - data = f.read(4096) - if not data: - break - - fp.update(data) - - return fp.hexdigest() - - -def FingerprintFiles(files): - """Compute fingerprints for a list of files. - - @type files: list - @param files: the list of filename to fingerprint - @rtype: dict - @return: a dictionary filename: fingerprint, holding only - existing files - - """ - ret = {} - - for filename in files: - cksum = _FingerprintFile(filename) - if cksum: - ret[filename] = cksum - - return ret - - def ForceDictType(target, key_types, allowed_values=None): """Force the values of a dict to have certain types. @@ -2541,41 +2492,6 @@ def LoadSignedX509Certificate(cert_pem, key): return (cert, salt) -def Sha1Hmac(key, text, salt=None): - """Calculates the HMAC-SHA1 digest of a text. - - HMAC is defined in RFC2104. - - @type key: string - @param key: Secret key - @type text: string - - """ - if salt: - salted_text = salt + text - else: - salted_text = text - - return hmac.new(key, salted_text, compat.sha1).hexdigest() - - -def VerifySha1Hmac(key, text, digest, salt=None): - """Verifies the HMAC-SHA1 digest of a text. - - HMAC is defined in RFC2104. - - @type key: string - @param key: Secret key - @type text: string - @type digest: string - @param digest: Expected digest - @rtype: bool - @return: Whether HMAC-SHA1 digest matches - - """ - return digest.lower() == Sha1Hmac(key, text, salt=salt).lower() - - def FindMatch(data, name): """Tries to find an item in a dictionary matching a name. diff --git a/lib/utils/hash.py b/lib/utils/hash.py new file mode 100644 index 000000000..2358762f2 --- /dev/null +++ b/lib/utils/hash.py @@ -0,0 +1,112 @@ +# +# + +# Copyright (C) 2006, 2007, 2010, 2011 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. + +"""Utility functions for hashing. + +""" + +import os +import hmac + +from ganeti import compat + + +def Sha1Hmac(key, text, salt=None): + """Calculates the HMAC-SHA1 digest of a text. + + HMAC is defined in RFC2104. + + @type key: string + @param key: Secret key + @type text: string + + """ + if salt: + salted_text = salt + text + else: + salted_text = text + + return hmac.new(key, salted_text, compat.sha1).hexdigest() + + +def VerifySha1Hmac(key, text, digest, salt=None): + """Verifies the HMAC-SHA1 digest of a text. + + HMAC is defined in RFC2104. + + @type key: string + @param key: Secret key + @type text: string + @type digest: string + @param digest: Expected digest + @rtype: bool + @return: Whether HMAC-SHA1 digest matches + + """ + return digest.lower() == Sha1Hmac(key, text, salt=salt).lower() + + +def _FingerprintFile(filename): + """Compute the fingerprint of a file. + + If the file does not exist, a None will be returned + instead. + + @type filename: str + @param filename: the filename to checksum + @rtype: str + @return: the hex digest of the sha checksum of the contents + of the file + + """ + if not (os.path.exists(filename) and os.path.isfile(filename)): + return None + + f = open(filename) + + fp = compat.sha1_hash() + while True: + data = f.read(4096) + if not data: + break + + fp.update(data) + + return fp.hexdigest() + + +def FingerprintFiles(files): + """Compute fingerprints for a list of files. + + @type files: list + @param files: the list of filename to fingerprint + @rtype: dict + @return: a dictionary filename: fingerprint, holding only + existing files + + """ + ret = {} + + for filename in files: + cksum = _FingerprintFile(filename) + if cksum: + ret[filename] = cksum + + return ret diff --git a/test/ganeti.utils.hash_unittest.py b/test/ganeti.utils.hash_unittest.py new file mode 100755 index 000000000..3095935ba --- /dev/null +++ b/test/ganeti.utils.hash_unittest.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# + +# Copyright (C) 2006, 2007, 2010, 2011 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. + + +"""Script for testing ganeti.utils.hash""" + +import unittest +import random +import operator +import tempfile + +from ganeti import constants +from ganeti import utils + +import testutils + + +class TestHmacFunctions(unittest.TestCase): + # Digests can be checked with "openssl sha1 -hmac $key" + def testSha1Hmac(self): + self.assertEqual(utils.Sha1Hmac("", ""), + "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d") + self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World"), + "ef4f3bda82212ecb2f7ce868888a19092481f1fd") + self.assertEqual(utils.Sha1Hmac("TguMTA2K", ""), + "f904c2476527c6d3e6609ab683c66fa0652cb1dc") + + longtext = 1500 * "The quick brown fox jumps over the lazy dog\n" + self.assertEqual(utils.Sha1Hmac("3YzMxZWE", longtext), + "35901b9a3001a7cdcf8e0e9d7c2e79df2223af54") + + def testSha1HmacSalt(self): + self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc0"), + "4999bf342470eadb11dfcd24ca5680cf9fd7cdce") + self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc9"), + "17a4adc34d69c0d367d4ffbef96fd41d4df7a6e8") + self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World", salt="xyz0"), + "7f264f8114c9066afc9bb7636e1786d996d3cc0d") + + def testVerifySha1Hmac(self): + self.assert_(utils.VerifySha1Hmac("", "", ("fbdb1d1b18aa6c08324b" + "7d64b71fb76370690e1d"))) + self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", + ("f904c2476527c6d3e660" + "9ab683c66fa0652cb1dc"))) + + digest = "ef4f3bda82212ecb2f7ce868888a19092481f1fd" + self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", digest)) + self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", + digest.lower())) + self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", + digest.upper())) + self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", + digest.title())) + + def testVerifySha1HmacSalt(self): + self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", + ("17a4adc34d69c0d367d4" + "ffbef96fd41d4df7a6e8"), + salt="abc9")) + self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", + ("7f264f8114c9066afc9b" + "b7636e1786d996d3cc0d"), + salt="xyz0")) + + +class TestFingerprintFiles(unittest.TestCase): + def setUp(self): + self.tmpfile = tempfile.NamedTemporaryFile() + self.tmpfile2 = tempfile.NamedTemporaryFile() + utils.WriteFile(self.tmpfile2.name, data="Hello World\n") + self.results = { + self.tmpfile.name: "da39a3ee5e6b4b0d3255bfef95601890afd80709", + self.tmpfile2.name: "648a6a6ffffdaa0badb23b8baf90b6168dd16b3a", + } + + def testSingleFile(self): + self.assertEqual(utils.hash._FingerprintFile(self.tmpfile.name), + self.results[self.tmpfile.name]) + + self.assertEqual(utils.hash._FingerprintFile("/no/such/file"), None) + + def testBigFile(self): + self.tmpfile.write("A" * 8192) + self.tmpfile.flush() + self.assertEqual(utils.hash._FingerprintFile(self.tmpfile.name), + "35b6795ca20d6dc0aff8c7c110c96cd1070b8c38") + + def testMultiple(self): + all_files = self.results.keys() + all_files.append("/no/such/file") + self.assertEqual(utils.FingerprintFiles(self.results.keys()), self.results) + + +if __name__ == "__main__": + testutils.GanetiTestProgram() diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index 2f8b00c72..574edf906 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -1471,34 +1471,6 @@ class RunInSeparateProcess(unittest.TestCase): utils.RunInSeparateProcess, _exc) -class TestFingerprintFiles(unittest.TestCase): - def setUp(self): - self.tmpfile = tempfile.NamedTemporaryFile() - self.tmpfile2 = tempfile.NamedTemporaryFile() - utils.WriteFile(self.tmpfile2.name, data="Hello World\n") - self.results = { - self.tmpfile.name: "da39a3ee5e6b4b0d3255bfef95601890afd80709", - self.tmpfile2.name: "648a6a6ffffdaa0badb23b8baf90b6168dd16b3a", - } - - def testSingleFile(self): - self.assertEqual(utils._FingerprintFile(self.tmpfile.name), - self.results[self.tmpfile.name]) - - self.assertEqual(utils._FingerprintFile("/no/such/file"), None) - - def testBigFile(self): - self.tmpfile.write("A" * 8192) - self.tmpfile.flush() - self.assertEqual(utils._FingerprintFile(self.tmpfile.name), - "35b6795ca20d6dc0aff8c7c110c96cd1070b8c38") - - def testMultiple(self): - all_files = self.results.keys() - all_files.append("/no/such/file") - self.assertEqual(utils.FingerprintFiles(self.results.keys()), self.results) - - class TestGenerateSelfSignedX509Cert(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() @@ -1829,55 +1801,6 @@ class TestVerifyCertificateInner(unittest.TestCase): self.assertEqual(errcode, utils.CERT_ERROR) -class TestHmacFunctions(unittest.TestCase): - # Digests can be checked with "openssl sha1 -hmac $key" - def testSha1Hmac(self): - self.assertEqual(utils.Sha1Hmac("", ""), - "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d") - self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World"), - "ef4f3bda82212ecb2f7ce868888a19092481f1fd") - self.assertEqual(utils.Sha1Hmac("TguMTA2K", ""), - "f904c2476527c6d3e6609ab683c66fa0652cb1dc") - - longtext = 1500 * "The quick brown fox jumps over the lazy dog\n" - self.assertEqual(utils.Sha1Hmac("3YzMxZWE", longtext), - "35901b9a3001a7cdcf8e0e9d7c2e79df2223af54") - - def testSha1HmacSalt(self): - self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc0"), - "4999bf342470eadb11dfcd24ca5680cf9fd7cdce") - self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc9"), - "17a4adc34d69c0d367d4ffbef96fd41d4df7a6e8") - self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World", salt="xyz0"), - "7f264f8114c9066afc9bb7636e1786d996d3cc0d") - - def testVerifySha1Hmac(self): - self.assert_(utils.VerifySha1Hmac("", "", ("fbdb1d1b18aa6c08324b" - "7d64b71fb76370690e1d"))) - self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", - ("f904c2476527c6d3e660" - "9ab683c66fa0652cb1dc"))) - - digest = "ef4f3bda82212ecb2f7ce868888a19092481f1fd" - self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", digest)) - self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", - digest.lower())) - self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", - digest.upper())) - self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", - digest.title())) - - def testVerifySha1HmacSalt(self): - self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", - ("17a4adc34d69c0d367d4" - "ffbef96fd41d4df7a6e8"), - salt="abc9")) - self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", - ("7f264f8114c9066afc9b" - "b7636e1786d996d3cc0d"), - salt="xyz0")) - - class TestIgnoreSignals(unittest.TestCase): """Test the IgnoreSignals decorator""" -- GitLab