|
|
@@ -0,0 +1,359 @@
|
|
|
+# coding=utf-8
|
|
|
+"""
|
|
|
+HMAC-SHA256 Authorization 函数单元测试
|
|
|
+
|
|
|
+针对 ddns.provider._base.hmac_sha256_authorization 函数的完整测试套件。
|
|
|
+测试覆盖多种典型使用场景,包括各大云服务商的认证模式,
|
|
|
+所有期望结果都是预先计算好的,确保测试结果的可复现性。
|
|
|
+
|
|
|
+Test suite for ddns.provider._base.hmac_sha256_authorization function.
|
|
|
+Covers various typical use cases including authentication patterns of major cloud providers.
|
|
|
+All expected results are pre-calculated to ensure reproducible test results.
|
|
|
+"""
|
|
|
+
|
|
|
+import unittest
|
|
|
+from ddns.provider._base import hmac_sha256_authorization, sha256_hash, hmac_sha256
|
|
|
+
|
|
|
+
|
|
|
+class TestHmacSha256Authorization(unittest.TestCase):
|
|
|
+ """HMAC-SHA256 Authorization 函数测试类"""
|
|
|
+
|
|
|
+ def test_basic_functionality(self):
|
|
|
+ """测试基本功能 - 简单的签名生成"""
|
|
|
+ secret_key = "test_secret_key"
|
|
|
+ method = "GET"
|
|
|
+ path = "/api/test"
|
|
|
+ query = ""
|
|
|
+ headers = {"Host": "api.example.com", "Date": "20231201T120000Z"}
|
|
|
+ body_hash = sha256_hash("") # 空请求体的哈希
|
|
|
+
|
|
|
+ auth_header_template = "TEST {SignedHeaders} {Signature}"
|
|
|
+ signing_string_template = "TEST\n{HashedCanonicalRequest}"
|
|
|
+
|
|
|
+ result = hmac_sha256_authorization(
|
|
|
+ secret_key=secret_key,
|
|
|
+ method=method,
|
|
|
+ path=path,
|
|
|
+ query=query,
|
|
|
+ headers=headers,
|
|
|
+ body_hash=body_hash,
|
|
|
+ authorization_format=auth_header_template,
|
|
|
+ signing_string_format=signing_string_template,
|
|
|
+ )
|
|
|
+
|
|
|
+ # 严格验证基本功能测试的完整结果 - 精确匹配
|
|
|
+ expected_result = "TEST date;host b5b3ee3c39b1c749faa31c1b5bd3a5609a3e5408dfb7f90eca5ea17d8aeb1ba2"
|
|
|
+ self.assertEqual(result, expected_result)
|
|
|
+
|
|
|
+ def test_alibaba_cloud_official_example(self):
|
|
|
+ """测试阿里云官方文档示例 - ACS3-HMAC-SHA256签名算法"""
|
|
|
+ # 使用阿里云官方文档中的固定参数示例
|
|
|
+ # 来源: https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature
|
|
|
+ secret_key = "YourAccessKeySecret"
|
|
|
+ method = "POST"
|
|
|
+ path = "/"
|
|
|
+ query = "ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai"
|
|
|
+ headers = {
|
|
|
+ "host": "ecs.cn-shanghai.aliyuncs.com",
|
|
|
+ "x-acs-action": "RunInstances",
|
|
|
+ "x-acs-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
|
+ "x-acs-date": "2023-10-26T10:22:32Z",
|
|
|
+ "x-acs-signature-nonce": "3156853299f313e23d1673dc12e1703d",
|
|
|
+ "x-acs-version": "2014-05-26",
|
|
|
+ }
|
|
|
+ body_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
|
+
|
|
|
+ auth_header_template = (
|
|
|
+ "ACS3-HMAC-SHA256 Credential=YourAccessKeyId," "SignedHeaders={SignedHeaders},Signature={Signature}"
|
|
|
+ )
|
|
|
+ signing_string_template = "ACS3-HMAC-SHA256\n{HashedCanonicalRequest}"
|
|
|
+
|
|
|
+ result = hmac_sha256_authorization(
|
|
|
+ secret_key=secret_key,
|
|
|
+ method=method,
|
|
|
+ path=path,
|
|
|
+ query=query,
|
|
|
+ headers=headers,
|
|
|
+ body_hash=body_hash,
|
|
|
+ authorization_format=auth_header_template,
|
|
|
+ signing_string_format=signing_string_template,
|
|
|
+ )
|
|
|
+
|
|
|
+ # 严格验证阿里云官方示例的完整授权头 - 精确匹配
|
|
|
+ expected_result = (
|
|
|
+ "ACS3-HMAC-SHA256 Credential=YourAccessKeyId,"
|
|
|
+ "SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,"
|
|
|
+ "Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0"
|
|
|
+ )
|
|
|
+ self.assertEqual(result, expected_result)
|
|
|
+
|
|
|
+ def test_huawei_cloud_official_example(self):
|
|
|
+ """测试华为云官方文档示例 - SDK-HMAC-SHA256签名算法"""
|
|
|
+ # 使用华为云官方文档中的查询VPC列表示例
|
|
|
+ # 来源: https://support.huaweicloud.com/devg-apisign/api-sign-algorithm-002.html
|
|
|
+ secret_key = "your_secret_access_key"
|
|
|
+ method = "GET"
|
|
|
+ path = "/v1/77b6a44cba5143ab91d13ab9a8ff44fd/vpcs/" # 注意官方示例要求以/结尾
|
|
|
+ query = "limit=2&marker=13551d6b-755d-4757-b956-536f674975c0"
|
|
|
+ headers = {
|
|
|
+ "content-type": "application/json",
|
|
|
+ "host": "service.region.example.com",
|
|
|
+ "x-sdk-date": "20191115T033655Z",
|
|
|
+ }
|
|
|
+ body_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
|
+
|
|
|
+ auth_header_template = (
|
|
|
+ "SDK-HMAC-SHA256 Access=your_access_key_id, " "SignedHeaders={SignedHeaders}, Signature={Signature}"
|
|
|
+ )
|
|
|
+ signing_string_template = "SDK-HMAC-SHA256\n20191115T033655Z\n{HashedCanonicalRequest}"
|
|
|
+
|
|
|
+ result = hmac_sha256_authorization(
|
|
|
+ secret_key=secret_key,
|
|
|
+ method=method,
|
|
|
+ path=path,
|
|
|
+ query=query,
|
|
|
+ headers=headers,
|
|
|
+ body_hash=body_hash,
|
|
|
+ authorization_format=auth_header_template,
|
|
|
+ signing_string_format=signing_string_template,
|
|
|
+ )
|
|
|
+
|
|
|
+ # 严格验证华为云官方示例的完整授权头 - 精确匹配
|
|
|
+ expected_result = (
|
|
|
+ "SDK-HMAC-SHA256 Access=your_access_key_id, "
|
|
|
+ "SignedHeaders=content-type;host;x-sdk-date, "
|
|
|
+ "Signature=8037a231336d8904f667c082dd84fc06d7e6c7c278c2d8599db31d14e8ee19f9"
|
|
|
+ )
|
|
|
+ self.assertEqual(result, expected_result)
|
|
|
+
|
|
|
+ def test_tencent_cloud_official_example(self):
|
|
|
+ """测试腾讯云官方文档示例 - TC3-HMAC-SHA256签名算法"""
|
|
|
+ # 使用腾讯云官方文档中的固定参数示例
|
|
|
+ # 来源: https://cloud.tencent.com/document/api/213/30654
|
|
|
+ # 注意:腾讯云使用派生密钥,这里模拟最终的签名密钥
|
|
|
+ # Python 2/3 compatible hex to bytes conversion
|
|
|
+ hex_string = "b596b923aad85185e2d1f6659d2a062e0a86731226e021e61bfe06f7ed05f5af"
|
|
|
+ try:
|
|
|
+ final_signing_key = bytes.fromhex(hex_string)
|
|
|
+ except AttributeError: # Python 2.7
|
|
|
+ final_signing_key = hex_string.decode("hex") # type: ignore[attr-defined]
|
|
|
+ method = "POST"
|
|
|
+ path = "/"
|
|
|
+ query = "" # POST请求,查询字符串为空
|
|
|
+ headers = {
|
|
|
+ "content-type": "application/json; charset=utf-8",
|
|
|
+ "host": "cvm.tencentcloudapi.com",
|
|
|
+ "x-tc-action": "describeinstances",
|
|
|
+ }
|
|
|
+ # 官方示例中的请求体
|
|
|
+ # body = '{"Limit": 1, "Filters": [{"Values": ["\\u672a\\u547d\\u540d"], "Name": "instance-name"}]}'
|
|
|
+ body_hash = "35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e89887f1398934f064"
|
|
|
+
|
|
|
+ auth_header_template = (
|
|
|
+ "TC3-HMAC-SHA256 Credential=AKID********************************/2019-02-25/cvm/tc3_request, "
|
|
|
+ "SignedHeaders={SignedHeaders}, Signature={Signature}"
|
|
|
+ )
|
|
|
+ signing_string_template = "TC3-HMAC-SHA256\n1551113065\n2019-02-25/cvm/tc3_request\n{HashedCanonicalRequest}"
|
|
|
+
|
|
|
+ result = hmac_sha256_authorization(
|
|
|
+ secret_key=final_signing_key,
|
|
|
+ method=method,
|
|
|
+ path=path,
|
|
|
+ query=query,
|
|
|
+ headers=headers,
|
|
|
+ body_hash=body_hash,
|
|
|
+ authorization_format=auth_header_template,
|
|
|
+ signing_string_format=signing_string_template,
|
|
|
+ )
|
|
|
+
|
|
|
+ # 严格验证腾讯云官方示例的完整授权头 - 精确匹配
|
|
|
+ expected_result = (
|
|
|
+ "TC3-HMAC-SHA256 Credential=AKID********************************/2019-02-25/cvm/tc3_request, "
|
|
|
+ "SignedHeaders=content-type;host;x-tc-action, "
|
|
|
+ "Signature=10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f"
|
|
|
+ )
|
|
|
+ self.assertEqual(result, expected_result)
|
|
|
+
|
|
|
+ def test_edge_cases(self):
|
|
|
+ """测试边界情况"""
|
|
|
+
|
|
|
+ # 测试空字符串参数 - 严格验证
|
|
|
+ result1 = hmac_sha256_authorization(
|
|
|
+ secret_key="key",
|
|
|
+ method="GET",
|
|
|
+ path="",
|
|
|
+ query="",
|
|
|
+ headers={},
|
|
|
+ body_hash=sha256_hash(""),
|
|
|
+ authorization_format="AUTH {Signature}",
|
|
|
+ signing_string_format="{HashedCanonicalRequest}",
|
|
|
+ )
|
|
|
+ expected_result1 = "AUTH 1d29fda5ce641f10c7e16b1e607ce10f1cad4fd4b071f1b3a4465e051b9a7d6d"
|
|
|
+ self.assertEqual(result1, expected_result1)
|
|
|
+
|
|
|
+ # 测试包含特殊字符的参数 - 严格验证
|
|
|
+ result2 = hmac_sha256_authorization(
|
|
|
+ secret_key="special!@#$%^&*()key",
|
|
|
+ method="POST",
|
|
|
+ path="/path/with spaces",
|
|
|
+ query="param1=value with spaces¶m2=value%20encoded",
|
|
|
+ headers={"Custom-Header": "value with spaces and symbols!@#"},
|
|
|
+ body_hash=sha256_hash("body with 中文 and symbols"),
|
|
|
+ authorization_format="SPECIAL {SignedHeaders} {Signature}",
|
|
|
+ signing_string_format="SPECIAL\n{HashedCanonicalRequest}",
|
|
|
+ )
|
|
|
+ expected_result2 = "SPECIAL custom-header edbf4d68ebb33f305e8d0e2f72c012997543a0bdc6a9f42142d1dfa236fa1dd5"
|
|
|
+ self.assertEqual(result2, expected_result2)
|
|
|
+
|
|
|
+ def test_header_normalization(self):
|
|
|
+ """测试头部规范化处理"""
|
|
|
+ secret_key = "test_key"
|
|
|
+ method = "GET"
|
|
|
+ path = "/test"
|
|
|
+ query = ""
|
|
|
+ body_hash = sha256_hash("")
|
|
|
+
|
|
|
+ # 测试头部大小写混合和值的前后空格处理
|
|
|
+ headers = {
|
|
|
+ "Host": " example.com ", # 前后有空格
|
|
|
+ "X-Custom-Header": "value",
|
|
|
+ "x-another-header": "another_value",
|
|
|
+ "CONTENT-TYPE": "application/json",
|
|
|
+ }
|
|
|
+
|
|
|
+ auth_header_template = "TEST {SignedHeaders} {Signature}"
|
|
|
+ signing_string_template = "{HashedCanonicalRequest}"
|
|
|
+
|
|
|
+ result = hmac_sha256_authorization(
|
|
|
+ secret_key=secret_key,
|
|
|
+ method=method,
|
|
|
+ path=path,
|
|
|
+ query=query,
|
|
|
+ headers=headers,
|
|
|
+ body_hash=body_hash,
|
|
|
+ authorization_format=auth_header_template,
|
|
|
+ signing_string_format=signing_string_template,
|
|
|
+ )
|
|
|
+
|
|
|
+ # 验证头部已被正确规范化(小写且按字母顺序排列)
|
|
|
+ # 模板格式是 "TEST {SignedHeaders} {Signature}",所以直接检查signed headers部分
|
|
|
+ self.assertIn("content-type;host;x-another-header;x-custom-header", result)
|
|
|
+
|
|
|
+ def test_reproducible_signatures(self):
|
|
|
+ """测试签名结果的可复现性"""
|
|
|
+ params = {
|
|
|
+ "secret_key": "reproducible_test_key",
|
|
|
+ "method": "POST",
|
|
|
+ "path": "/api/v1/test",
|
|
|
+ "query": "param1=value1¶m2=value2",
|
|
|
+ "headers": {"Host": "api.test.com", "Content-Type": "application/json", "Date": "20231201T120000Z"},
|
|
|
+ "body_hash": sha256_hash('{"test": "data"}'),
|
|
|
+ "authorization_format": "REPRO {SignedHeaders} {Signature}",
|
|
|
+ "signing_string_format": "REPRO\n{HashedCanonicalRequest}",
|
|
|
+ }
|
|
|
+
|
|
|
+ # 多次调用应该产生相同的结果
|
|
|
+ result1 = hmac_sha256_authorization(**params)
|
|
|
+ result2 = hmac_sha256_authorization(**params)
|
|
|
+ result3 = hmac_sha256_authorization(**params)
|
|
|
+
|
|
|
+ self.assertEqual(result1, result2)
|
|
|
+ self.assertEqual(result2, result3)
|
|
|
+
|
|
|
+ # 验证结果包含预期的组件
|
|
|
+ self.assertIn("REPRO", result1)
|
|
|
+ self.assertIn("content-type;date;host", result1)
|
|
|
+
|
|
|
+ # 提取签名部分进行验证 - 格式是 "REPRO {SignedHeaders} {Signature}"
|
|
|
+ parts = result1.split()
|
|
|
+ self.assertEqual(len(parts), 3) # "REPRO", signed_headers, signature
|
|
|
+ signature_part = parts[2]
|
|
|
+ self.assertEqual(len(signature_part), 64) # SHA256签名应该是64个十六进制字符
|
|
|
+ self.assertTrue(all(c in "0123456789abcdef" for c in signature_part))
|
|
|
+
|
|
|
+ def test_different_key_types(self):
|
|
|
+ """测试不同类型的密钥输入"""
|
|
|
+ base_params = {
|
|
|
+ "method": "GET",
|
|
|
+ "path": "/test",
|
|
|
+ "query": "",
|
|
|
+ "headers": {"Host": "example.com"},
|
|
|
+ "body_hash": sha256_hash(""),
|
|
|
+ "authorization_format": "TYPE {Signature}",
|
|
|
+ "signing_string_format": "{HashedCanonicalRequest}",
|
|
|
+ }
|
|
|
+
|
|
|
+ # 字符串密钥
|
|
|
+ result_str = hmac_sha256_authorization(secret_key="string_key", **base_params)
|
|
|
+
|
|
|
+ # 字节密钥
|
|
|
+ result_bytes = hmac_sha256_authorization(secret_key=b"string_key", **base_params)
|
|
|
+
|
|
|
+ # 相同内容的字符串和字节密钥应该产生相同的签名
|
|
|
+ self.assertEqual(result_str, result_bytes)
|
|
|
+
|
|
|
+ def test_hmac_sha256_basic_functionality(self):
|
|
|
+ """测试 hmac_sha256 基础功能"""
|
|
|
+ key = "test_key"
|
|
|
+ message = "test_message"
|
|
|
+
|
|
|
+ # 测试返回的是 HMAC 对象
|
|
|
+ hmac_obj = hmac_sha256(key, message)
|
|
|
+
|
|
|
+ # 验证可以调用 digest() 和 hexdigest() 方法
|
|
|
+ digest_result = hmac_obj.digest()
|
|
|
+ hexdigest_result = hmac_obj.hexdigest()
|
|
|
+
|
|
|
+ # 验证结果类型
|
|
|
+ self.assertIsInstance(digest_result, bytes)
|
|
|
+ self.assertIsInstance(hexdigest_result, str)
|
|
|
+
|
|
|
+ # 验证 hexdigest 结果长度 (SHA256 产生64个十六进制字符)
|
|
|
+ self.assertEqual(len(hexdigest_result), 64)
|
|
|
+
|
|
|
+ # 验证结果的可复现性
|
|
|
+ hmac_obj2 = hmac_sha256(key, message)
|
|
|
+ self.assertEqual(hmac_obj.hexdigest(), hmac_obj2.hexdigest())
|
|
|
+
|
|
|
+ def test_hmac_sha256_different_key_types(self):
|
|
|
+ """测试 hmac_sha256 不同密钥类型"""
|
|
|
+ message = "test_message"
|
|
|
+
|
|
|
+ # 字符串密钥
|
|
|
+ hmac_str = hmac_sha256("test_key", message)
|
|
|
+
|
|
|
+ # 字节密钥
|
|
|
+ hmac_bytes = hmac_sha256(b"test_key", message)
|
|
|
+
|
|
|
+ # 相同内容的字符串和字节密钥应该产生相同的结果
|
|
|
+ self.assertEqual(hmac_str.hexdigest(), hmac_bytes.hexdigest())
|
|
|
+
|
|
|
+ def test_hmac_sha256_different_message_types(self):
|
|
|
+ """测试 hmac_sha256 不同消息类型"""
|
|
|
+ key = "test_key"
|
|
|
+
|
|
|
+ # 字符串消息
|
|
|
+ hmac_str = hmac_sha256(key, "test_message")
|
|
|
+
|
|
|
+ # 字节消息
|
|
|
+ hmac_bytes = hmac_sha256(key, b"test_message")
|
|
|
+
|
|
|
+ # 相同内容的字符串和字节消息应该产生相同的结果
|
|
|
+ self.assertEqual(hmac_str.hexdigest(), hmac_bytes.hexdigest())
|
|
|
+
|
|
|
+ def test_hmac_sha256_known_vector(self):
|
|
|
+ """测试 hmac_sha256 已知测试向量"""
|
|
|
+ # 使用已知的测试向量验证实现正确性
|
|
|
+ key = "key"
|
|
|
+ message = "The quick brown fox jumps over the lazy dog"
|
|
|
+
|
|
|
+ hmac_obj = hmac_sha256(key, message)
|
|
|
+ result = hmac_obj.hexdigest()
|
|
|
+
|
|
|
+ # 预期结果(可以通过其他实现验证)
|
|
|
+ expected = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"
|
|
|
+ self.assertEqual(result, expected)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ unittest.main()
|