Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hmac_compute (Repost of #54164) #55506

Merged
merged 5 commits into from Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 21 additions & 6 deletions salt/modules/hashutil.py
Expand Up @@ -115,13 +115,13 @@ def base64_b64decode(instr):

def base64_encodestring(instr):
'''
Encode a string as base64 using the "legacy" Python interface.
Encode a byte-like object as base64 using the "modern" Python interface.

Among other possible differences, the "legacy" encoder includes
Among other possible differences, the "modern" encoder includes
a newline ('\\n') character after every 76 characters and always
at the end of the encoded string.
at the end of the encoded byte-like object.

.. versionadded:: 2014.7.0
.. versionadded:: Sodium
Ajnbro marked this conversation as resolved.
Show resolved Hide resolved

CLI Example:

Expand Down Expand Up @@ -167,9 +167,9 @@ def base64_encodefile(fname):

def base64_decodestring(instr):
'''
Decode a base64-encoded string using the "legacy" Python interface
Decode a base64-encoded byte-like object using the "modern" Python interface

.. versionadded:: 2014.7.0
.. versionadded:: Sodium
Ajnbro marked this conversation as resolved.
Show resolved Hide resolved

CLI Example:

Expand Down Expand Up @@ -263,6 +263,21 @@ def hmac_signature(string, shared_secret, challenge_hmac):
return salt.utils.hashutils.hmac_signature(string, shared_secret, challenge_hmac)


def hmac_compute(string, shared_secret):
'''
.. versionadded:: Sodium

Compute a HMAC SHA256 digest using a string and secret.

CLI Example:

.. code-block:: bash

salt '*' hashutil.hmac_compute 'get salted' 'shared secret'
'''
return salt.utils.hashutils.hmac_compute(string, shared_secret)


def github_signature(string, shared_secret, challenge_hmac):
'''
Verify a challenging hmac signature against a string / shared-secret for
Expand Down
45 changes: 33 additions & 12 deletions salt/utils/hashutils.py
Expand Up @@ -51,29 +51,39 @@ def base64_b64decode(instr):

def base64_encodestring(instr):
'''
Encode a string as base64 using the "legacy" Python interface.
Encode a byte-like object as base64 using the "modern" Python interface.

Among other possible differences, the "legacy" encoder includes
Among other possible differences, the "modern" encoder includes
a newline ('\\n') character after every 76 characters and always
at the end of the encoded string.
'''
# Handles PY2
if six.PY2:
return salt.utils.stringutils.to_unicode(
base64.encodestring(salt.utils.stringutils.to_bytes(instr)),
encoding='utf8' if salt.utils.platform.is_windows() else None
)

# Handles PY3
return salt.utils.stringutils.to_unicode(
base64.encodestring(salt.utils.stringutils.to_bytes(instr)),
base64.encodebytes(salt.utils.stringutils.to_bytes(instr)),
encoding='utf8' if salt.utils.platform.is_windows() else None
)


def base64_decodestring(instr):
'''
Decode a base64-encoded string using the "legacy" Python interface.
Decode a base64-encoded byte-like object using the "modern" Python interface.
'''
b = salt.utils.stringutils.to_bytes(instr)
try:
# PY3
decoded = base64.decodebytes(b)
except AttributeError:
# PY2
decoded = base64.decodestring(b)
bvalue = salt.utils.stringutils.to_bytes(instr)

# Handles PY2
if six.PY2:
Ajnbro marked this conversation as resolved.
Show resolved Hide resolved
decoded = base64.decodestring(bvalue)

# Handles PY3
if six.PY3:
decoded = base64.decodebytes(bvalue)
try:
return salt.utils.stringutils.to_unicode(
decoded,
Expand All @@ -93,6 +103,7 @@ def md5_digest(instr):
)


@jinja_filter('sha1')
def sha1_digest(instr):
'''
Generate an sha1 hash of a given string.
Expand Down Expand Up @@ -137,7 +148,17 @@ def hmac_signature(string, shared_secret, challenge_hmac):
return valid_hmac == challenge


@jinja_filter('rand_str') # Remove this for Neon
@jinja_filter('hmac_compute')
def hmac_compute(string, shared_secret):
'''
Create an hmac digest.
'''
msg = salt.utils.stringutils.to_bytes(string)
key = salt.utils.stringutils.to_bytes(shared_secret)
hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest()
return hmac_hash


@jinja_filter('random_hash')
def random_hash(size=9999999999, hash_type=None):
'''
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/modules/test_hashutil.py
Expand Up @@ -20,6 +20,7 @@ class HashutilTestCase(ModuleCase):
the_string_sha256 = 'd49859ccbc854fa68d800b5734efc70d72383e6479d545468bc300263164ff33'
the_string_sha512 = 'a8c174a7941c64a068e686812a2fafd7624c840fde800f5965fbeca675f2f6e37061ffe41e17728c919bdea290eab7a21e13c04ae71661955a87f2e0e04bb045'
the_string_hmac = 'eBWf9bstXg+NiP5AOwppB5HMvZiYMPzEM9W5YMm/AmQ='
the_string_hmac_compute = '78159ff5bb2d5e0f8d88fe403b0a690791ccbd989830fcc433d5b960c9bf0264'
the_string_github = 'sha1=b06aa56bdf4935eec82c4e53e83ed03f03fdb32d'

def setUp(self):
Expand Down Expand Up @@ -53,6 +54,10 @@ def test_hmac_signature(self):
self.the_string_hmac)
self.assertTrue(ret)

def test_hmac_compute(self):
ret = self.hashutil['hashutil.hmac_compute'](self.the_string, 'shared secret')
self.assertEqual(ret, self.the_string_hmac_compute)

def test_github_signature(self):
ret = self.hashutil['hashutil.github_signature'](
self.the_string,
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/utils/test_hashutils.py
Expand Up @@ -23,6 +23,7 @@ class HashutilsTestCase(TestCase):
str_sha256 = '095291ffa3d361436d4617879e22c1da06c6ab61a3fb081321ec854a27a091ac'
str_sha512 = '12efd90e507289f1f21e5dcfe2e92cf0bb4904abccb55c3ce9177670c711981501054b32b807c37058675590d1c484bd2b72a4215a2fa397aa4f2b12f298b1f0'
str_hmac_challenge = b'qz2k0t1aevKEme3JGsNQJX/xpmf+/w3q6qmWDk1ZqbY='
str_hmac_compute = 'ab3da4d2dd5a7af28499edc91ac350257ff1a667feff0deaeaa9960e4d59a9b6'

# 16 bytes of random data
bytes = b'b\x19\xf6\x86\x0e\x1a\x1cs\x0c\xda&zv\xfc\xa2\xdd'
Expand All @@ -32,6 +33,7 @@ class HashutilsTestCase(TestCase):
bytes_sha256 = '25711a31c2673a48f3d1f29b25add574697872968e546d266f441de63b17954a'
bytes_sha512 = '69f1524e602c1599fc374e1e3e2941e6f6949f4f7fe7321304e4e67bb850f3204dd5cbf9c13e231814540c2f5cd370c24ea257771d9fbf311d8f6085bad12b24'
bytes_hmac_challenge = b'lQibiD9r1Hpo+5JYknaudIKfTx1L5J3U58M9yQOd04c='
bytes_hmac_compute = '95089b883f6bd47a68fb92589276ae74829f4f1d4be49dd4e7c33dc9039dd387'

def test_base64_b64encode(self):
'''
Expand Down Expand Up @@ -157,6 +159,21 @@ def test_hmac_signature(self):
)
)

def test_hmac_compute(self):
'''
Ensure that this function converts the value passed to bytes before
attempting to encode, avoiding a UnicodeEncodeError on Python 2 and a
TypeError on Python 3.
'''
self.assertEqual(
salt.utils.hashutils.hmac_compute(self.str, self.hmac_secret),
self.str_hmac_compute
)
self.assertEqual(
salt.utils.hashutils.hmac_compute(self.bytes, self.hmac_secret),
self.bytes_hmac_compute
)

def test_get_hash_exception(self):
self.assertRaises(
ValueError,
Expand Down