diff --git a/tests/test_utility.py b/tests/test_utility.py index db9d8fe8..5dcd77ce 100644 --- a/tests/test_utility.py +++ b/tests/test_utility.py @@ -48,3 +48,38 @@ def example_function(*args, **kwargs): mock_is_github_ip.assert_called_once() self.assertEqual(mock_abort.call_count, 7) self.assertEqual(response, "Test Success") + + def test_is_valid_signature(self): + """Test is_valid_signature with valid and invalid inputs.""" + import hashlib + import hmac + from utility import is_valid_signature + + key = "secret_key_123" + payload = b'{"ref": "refs/heads/master"}' + mac = hmac.new(b"secret_key_123", payload, hashlib.sha256) + valid_header = f"sha256={mac.hexdigest()}" + + # Valid signature with bytes and str payload + self.assertTrue(is_valid_signature(valid_header, payload, key)) + self.assertTrue( + is_valid_signature(valid_header, payload.decode(), key)) + + # Invalid signatures / tamper + self.assertFalse(is_valid_signature("sha256=wrong", payload, key)) + + # Missing or invalid signature formats + self.assertFalse(is_valid_signature(None, payload, key)) + self.assertFalse(is_valid_signature("", payload, key)) + self.assertFalse(is_valid_signature(12345, payload, key)) + self.assertFalse(is_valid_signature("no_equal_sign", payload, key)) + + # Unknown or non-callable algorithms + self.assertFalse(is_valid_signature("unknown=1234", payload, key)) + self.assertFalse(is_valid_signature("__dict__=1234", payload, key)) + self.assertFalse( + is_valid_signature("algorithms_guaranteed=1", payload, key)) + + # None data or key + self.assertFalse(is_valid_signature(valid_header, None, key)) + self.assertFalse(is_valid_signature(valid_header, payload, None)) diff --git a/utility.py b/utility.py index ecc8df7e..fdd70990 100644 --- a/utility.py +++ b/utility.py @@ -156,13 +156,42 @@ def is_valid_signature(x_hub_signature, data, private_key): :param x_hub_signature: Signature to check :type x_hub_signature: str :param data: Signature's data - :type data: bytearray + :type data: bytearray or bytes or str :param private_key: Signature's token - :type private_key: str + :type private_key: str or bytes """ + if not x_hub_signature or not isinstance(x_hub_signature, str): + return False + + if '=' not in x_hub_signature: + return False + hash_algorithm, github_signature = x_hub_signature.split('=', 1) - algorithm = hashlib.__dict__.get(hash_algorithm) - encoded_key = bytes(private_key, 'latin-1') - mac = hmac.new(encoded_key, msg=data, digestmod=algorithm) + hash_algorithm = hash_algorithm.lower() + + if hash_algorithm not in hashlib.algorithms_available: + return False + + algorithm = getattr(hashlib, hash_algorithm, None) + if not callable(algorithm): + return False - return hmac.compare_digest(mac.hexdigest(), github_signature) + if private_key is None: + return False + if isinstance(private_key, str): + encoded_key = private_key.encode('latin-1') + elif isinstance(private_key, (bytes, bytearray)): + encoded_key = bytes(private_key) + else: + return False + + if data is None: + return False + if isinstance(data, str): + data = data.encode('utf-8') + + try: + mac = hmac.new(encoded_key, msg=data, digestmod=algorithm) + return hmac.compare_digest(mac.hexdigest(), github_signature) + except (ValueError, TypeError): + return False