test_hmac_sha256_authorization.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. # coding=utf-8
  2. """
  3. HMAC-SHA256 Authorization 函数单元测试
  4. 针对 ddns.provider._base.hmac_sha256_authorization 函数的完整测试套件。
  5. 测试覆盖多种典型使用场景,包括各大云服务商的认证模式,
  6. 所有期望结果都是预先计算好的,确保测试结果的可复现性。
  7. Test suite for ddns.provider._base.hmac_sha256_authorization function.
  8. Covers various typical use cases including authentication patterns of major cloud providers.
  9. All expected results are pre-calculated to ensure reproducible test results.
  10. """
  11. import unittest
  12. from ddns.provider._base import hmac_sha256_authorization, sha256_hash, hmac_sha256
  13. class TestHmacSha256Authorization(unittest.TestCase):
  14. """HMAC-SHA256 Authorization 函数测试类"""
  15. def test_basic_functionality(self):
  16. """测试基本功能 - 简单的签名生成"""
  17. secret_key = "test_secret_key"
  18. method = "GET"
  19. path = "/api/test"
  20. query = ""
  21. headers = {"Host": "api.example.com", "Date": "20231201T120000Z"}
  22. body_hash = sha256_hash("") # 空请求体的哈希
  23. auth_header_template = "TEST {SignedHeaders} {Signature}"
  24. signing_string_template = "TEST\n{HashedCanonicalRequest}"
  25. result = hmac_sha256_authorization(
  26. secret_key=secret_key,
  27. method=method,
  28. path=path,
  29. query=query,
  30. headers=headers,
  31. body_hash=body_hash,
  32. authorization_format=auth_header_template,
  33. signing_string_format=signing_string_template,
  34. )
  35. # 严格验证基本功能测试的完整结果 - 精确匹配
  36. expected_result = "TEST date;host b5b3ee3c39b1c749faa31c1b5bd3a5609a3e5408dfb7f90eca5ea17d8aeb1ba2"
  37. self.assertEqual(result, expected_result)
  38. def test_alibaba_cloud_official_example(self):
  39. """测试阿里云官方文档示例 - ACS3-HMAC-SHA256签名算法"""
  40. # 使用阿里云官方文档中的固定参数示例
  41. # 来源: https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature
  42. secret_key = "YourAccessKeySecret"
  43. method = "POST"
  44. path = "/"
  45. query = "ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai"
  46. headers = {
  47. "host": "ecs.cn-shanghai.aliyuncs.com",
  48. "x-acs-action": "RunInstances",
  49. "x-acs-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  50. "x-acs-date": "2023-10-26T10:22:32Z",
  51. "x-acs-signature-nonce": "3156853299f313e23d1673dc12e1703d",
  52. "x-acs-version": "2014-05-26",
  53. }
  54. body_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  55. auth_header_template = (
  56. "ACS3-HMAC-SHA256 Credential=YourAccessKeyId," "SignedHeaders={SignedHeaders},Signature={Signature}"
  57. )
  58. signing_string_template = "ACS3-HMAC-SHA256\n{HashedCanonicalRequest}"
  59. result = hmac_sha256_authorization(
  60. secret_key=secret_key,
  61. method=method,
  62. path=path,
  63. query=query,
  64. headers=headers,
  65. body_hash=body_hash,
  66. authorization_format=auth_header_template,
  67. signing_string_format=signing_string_template,
  68. )
  69. # 严格验证阿里云官方示例的完整授权头 - 精确匹配
  70. expected_result = (
  71. "ACS3-HMAC-SHA256 Credential=YourAccessKeyId,"
  72. "SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,"
  73. "Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0"
  74. )
  75. self.assertEqual(result, expected_result)
  76. def test_huawei_cloud_official_example(self):
  77. """测试华为云官方文档示例 - SDK-HMAC-SHA256签名算法"""
  78. # 使用华为云官方文档中的查询VPC列表示例
  79. # 来源: https://support.huaweicloud.com/devg-apisign/api-sign-algorithm-002.html
  80. secret_key = "your_secret_access_key"
  81. method = "GET"
  82. path = "/v1/77b6a44cba5143ab91d13ab9a8ff44fd/vpcs/" # 注意官方示例要求以/结尾
  83. query = "limit=2&marker=13551d6b-755d-4757-b956-536f674975c0"
  84. headers = {
  85. "content-type": "application/json",
  86. "host": "service.region.example.com",
  87. "x-sdk-date": "20191115T033655Z",
  88. }
  89. body_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  90. auth_header_template = (
  91. "SDK-HMAC-SHA256 Access=your_access_key_id, " "SignedHeaders={SignedHeaders}, Signature={Signature}"
  92. )
  93. signing_string_template = "SDK-HMAC-SHA256\n20191115T033655Z\n{HashedCanonicalRequest}"
  94. result = hmac_sha256_authorization(
  95. secret_key=secret_key,
  96. method=method,
  97. path=path,
  98. query=query,
  99. headers=headers,
  100. body_hash=body_hash,
  101. authorization_format=auth_header_template,
  102. signing_string_format=signing_string_template,
  103. )
  104. # 严格验证华为云官方示例的完整授权头 - 精确匹配
  105. expected_result = (
  106. "SDK-HMAC-SHA256 Access=your_access_key_id, "
  107. "SignedHeaders=content-type;host;x-sdk-date, "
  108. "Signature=8037a231336d8904f667c082dd84fc06d7e6c7c278c2d8599db31d14e8ee19f9"
  109. )
  110. self.assertEqual(result, expected_result)
  111. def test_tencent_cloud_official_example(self):
  112. """测试腾讯云官方文档示例 - TC3-HMAC-SHA256签名算法"""
  113. # 使用腾讯云官方文档中的固定参数示例
  114. # 来源: https://cloud.tencent.com/document/api/213/30654
  115. # 注意:腾讯云使用派生密钥,这里模拟最终的签名密钥
  116. # Python 2/3 compatible hex to bytes conversion
  117. hex_string = "b596b923aad85185e2d1f6659d2a062e0a86731226e021e61bfe06f7ed05f5af"
  118. try:
  119. final_signing_key = bytes.fromhex(hex_string)
  120. except AttributeError: # Python 2.7
  121. final_signing_key = hex_string.decode("hex") # type: ignore[attr-defined]
  122. method = "POST"
  123. path = "/"
  124. query = "" # POST请求,查询字符串为空
  125. headers = {
  126. "content-type": "application/json; charset=utf-8",
  127. "host": "cvm.tencentcloudapi.com",
  128. "x-tc-action": "describeinstances",
  129. }
  130. # 官方示例中的请求体
  131. # body = '{"Limit": 1, "Filters": [{"Values": ["\\u672a\\u547d\\u540d"], "Name": "instance-name"}]}'
  132. body_hash = "35e9c5b0e3ae67532d3c9f17ead6c90222632e5b1ff7f6e89887f1398934f064"
  133. auth_header_template = (
  134. "TC3-HMAC-SHA256 Credential=AKID********************************/2019-02-25/cvm/tc3_request, "
  135. "SignedHeaders={SignedHeaders}, Signature={Signature}"
  136. )
  137. signing_string_template = "TC3-HMAC-SHA256\n1551113065\n2019-02-25/cvm/tc3_request\n{HashedCanonicalRequest}"
  138. result = hmac_sha256_authorization(
  139. secret_key=final_signing_key,
  140. method=method,
  141. path=path,
  142. query=query,
  143. headers=headers,
  144. body_hash=body_hash,
  145. authorization_format=auth_header_template,
  146. signing_string_format=signing_string_template,
  147. )
  148. # 严格验证腾讯云官方示例的完整授权头 - 精确匹配
  149. expected_result = (
  150. "TC3-HMAC-SHA256 Credential=AKID********************************/2019-02-25/cvm/tc3_request, "
  151. "SignedHeaders=content-type;host;x-tc-action, "
  152. "Signature=10b1a37a7301a02ca19a647ad722d5e43b4b3cff309d421d85b46093f6ab6c4f"
  153. )
  154. self.assertEqual(result, expected_result)
  155. def test_edge_cases(self):
  156. """测试边界情况"""
  157. # 测试空字符串参数 - 严格验证
  158. result1 = hmac_sha256_authorization(
  159. secret_key="key",
  160. method="GET",
  161. path="",
  162. query="",
  163. headers={},
  164. body_hash=sha256_hash(""),
  165. authorization_format="AUTH {Signature}",
  166. signing_string_format="{HashedCanonicalRequest}",
  167. )
  168. expected_result1 = "AUTH 1d29fda5ce641f10c7e16b1e607ce10f1cad4fd4b071f1b3a4465e051b9a7d6d"
  169. self.assertEqual(result1, expected_result1)
  170. # 测试包含特殊字符的参数 - 严格验证
  171. result2 = hmac_sha256_authorization(
  172. secret_key="special!@#$%^&*()key",
  173. method="POST",
  174. path="/path/with spaces",
  175. query="param1=value with spaces&param2=value%20encoded",
  176. headers={"Custom-Header": "value with spaces and symbols!@#"},
  177. body_hash=sha256_hash("body with 中文 and symbols"),
  178. authorization_format="SPECIAL {SignedHeaders} {Signature}",
  179. signing_string_format="SPECIAL\n{HashedCanonicalRequest}",
  180. )
  181. expected_result2 = "SPECIAL custom-header edbf4d68ebb33f305e8d0e2f72c012997543a0bdc6a9f42142d1dfa236fa1dd5"
  182. self.assertEqual(result2, expected_result2)
  183. def test_header_normalization(self):
  184. """测试头部规范化处理"""
  185. secret_key = "test_key"
  186. method = "GET"
  187. path = "/test"
  188. query = ""
  189. body_hash = sha256_hash("")
  190. # 测试头部大小写混合和值的前后空格处理
  191. headers = {
  192. "Host": " example.com ", # 前后有空格
  193. "X-Custom-Header": "value",
  194. "x-another-header": "another_value",
  195. "CONTENT-TYPE": "application/json",
  196. }
  197. auth_header_template = "TEST {SignedHeaders} {Signature}"
  198. signing_string_template = "{HashedCanonicalRequest}"
  199. result = hmac_sha256_authorization(
  200. secret_key=secret_key,
  201. method=method,
  202. path=path,
  203. query=query,
  204. headers=headers,
  205. body_hash=body_hash,
  206. authorization_format=auth_header_template,
  207. signing_string_format=signing_string_template,
  208. )
  209. # 验证头部已被正确规范化(小写且按字母顺序排列)
  210. # 模板格式是 "TEST {SignedHeaders} {Signature}",所以直接检查signed headers部分
  211. self.assertIn("content-type;host;x-another-header;x-custom-header", result)
  212. def test_reproducible_signatures(self):
  213. """测试签名结果的可复现性"""
  214. params = {
  215. "secret_key": "reproducible_test_key",
  216. "method": "POST",
  217. "path": "/api/v1/test",
  218. "query": "param1=value1&param2=value2",
  219. "headers": {"Host": "api.test.com", "Content-Type": "application/json", "Date": "20231201T120000Z"},
  220. "body_hash": sha256_hash('{"test": "data"}'),
  221. "authorization_format": "REPRO {SignedHeaders} {Signature}",
  222. "signing_string_format": "REPRO\n{HashedCanonicalRequest}",
  223. }
  224. # 多次调用应该产生相同的结果
  225. result1 = hmac_sha256_authorization(**params)
  226. result2 = hmac_sha256_authorization(**params)
  227. result3 = hmac_sha256_authorization(**params)
  228. self.assertEqual(result1, result2)
  229. self.assertEqual(result2, result3)
  230. # 验证结果包含预期的组件
  231. self.assertIn("REPRO", result1)
  232. self.assertIn("content-type;date;host", result1)
  233. # 提取签名部分进行验证 - 格式是 "REPRO {SignedHeaders} {Signature}"
  234. parts = result1.split()
  235. self.assertEqual(len(parts), 3) # "REPRO", signed_headers, signature
  236. signature_part = parts[2]
  237. self.assertEqual(len(signature_part), 64) # SHA256签名应该是64个十六进制字符
  238. self.assertTrue(all(c in "0123456789abcdef" for c in signature_part))
  239. def test_different_key_types(self):
  240. """测试不同类型的密钥输入"""
  241. base_params = {
  242. "method": "GET",
  243. "path": "/test",
  244. "query": "",
  245. "headers": {"Host": "example.com"},
  246. "body_hash": sha256_hash(""),
  247. "authorization_format": "TYPE {Signature}",
  248. "signing_string_format": "{HashedCanonicalRequest}",
  249. }
  250. # 字符串密钥
  251. result_str = hmac_sha256_authorization(secret_key="string_key", **base_params)
  252. # 字节密钥
  253. result_bytes = hmac_sha256_authorization(secret_key=b"string_key", **base_params)
  254. # 相同内容的字符串和字节密钥应该产生相同的签名
  255. self.assertEqual(result_str, result_bytes)
  256. def test_hmac_sha256_basic_functionality(self):
  257. """测试 hmac_sha256 基础功能"""
  258. key = "test_key"
  259. message = "test_message"
  260. # 测试返回的是 HMAC 对象
  261. hmac_obj = hmac_sha256(key, message)
  262. # 验证可以调用 digest() 和 hexdigest() 方法
  263. digest_result = hmac_obj.digest()
  264. hexdigest_result = hmac_obj.hexdigest()
  265. # 验证结果类型
  266. self.assertIsInstance(digest_result, bytes)
  267. self.assertIsInstance(hexdigest_result, str)
  268. # 验证 hexdigest 结果长度 (SHA256 产生64个十六进制字符)
  269. self.assertEqual(len(hexdigest_result), 64)
  270. # 验证结果的可复现性
  271. hmac_obj2 = hmac_sha256(key, message)
  272. self.assertEqual(hmac_obj.hexdigest(), hmac_obj2.hexdigest())
  273. def test_hmac_sha256_different_key_types(self):
  274. """测试 hmac_sha256 不同密钥类型"""
  275. message = "test_message"
  276. # 字符串密钥
  277. hmac_str = hmac_sha256("test_key", message)
  278. # 字节密钥
  279. hmac_bytes = hmac_sha256(b"test_key", message)
  280. # 相同内容的字符串和字节密钥应该产生相同的结果
  281. self.assertEqual(hmac_str.hexdigest(), hmac_bytes.hexdigest())
  282. def test_hmac_sha256_different_message_types(self):
  283. """测试 hmac_sha256 不同消息类型"""
  284. key = "test_key"
  285. # 字符串消息
  286. hmac_str = hmac_sha256(key, "test_message")
  287. # 字节消息
  288. hmac_bytes = hmac_sha256(key, b"test_message")
  289. # 相同内容的字符串和字节消息应该产生相同的结果
  290. self.assertEqual(hmac_str.hexdigest(), hmac_bytes.hexdigest())
  291. def test_hmac_sha256_known_vector(self):
  292. """测试 hmac_sha256 已知测试向量"""
  293. # 使用已知的测试向量验证实现正确性
  294. key = "key"
  295. message = "The quick brown fox jumps over the lazy dog"
  296. hmac_obj = hmac_sha256(key, message)
  297. result = hmac_obj.hexdigest()
  298. # 预期结果(可以通过其他实现验证)
  299. expected = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"
  300. self.assertEqual(result, expected)
  301. if __name__ == "__main__":
  302. unittest.main()