volcengine-python-sdk-aicc 0.0.41__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. bytedance/__init__.py +4 -0
  2. bytedance/jeddak_secure_channel/__init__.py +28 -0
  3. bytedance/jeddak_secure_channel/client.py +305 -0
  4. bytedance/jeddak_secure_channel/config.py +171 -0
  5. bytedance/jeddak_secure_channel/consts.py +5 -0
  6. bytedance/jeddak_secure_channel/crypto/__init__.py +14 -0
  7. bytedance/jeddak_secure_channel/crypto/aes.py +174 -0
  8. bytedance/jeddak_secure_channel/crypto/envelope.py +395 -0
  9. bytedance/jeddak_secure_channel/crypto/rsa.py +164 -0
  10. bytedance/jeddak_secure_channel/error.py +131 -0
  11. bytedance/jeddak_secure_channel/log/__init__.py +23 -0
  12. bytedance/jeddak_secure_channel/log/config.py +31 -0
  13. bytedance/jeddak_secure_channel/log/logger.py +88 -0
  14. bytedance/jeddak_secure_channel/logger_factory.py +134 -0
  15. bytedance/jeddak_secure_channel/ra.py +772 -0
  16. bytedance/jeddak_secure_channel/server.py +745 -0
  17. bytedance/jeddak_secure_channel/service/__init__.py +16 -0
  18. bytedance/jeddak_secure_channel/service/eps.py +133 -0
  19. bytedance/jeddak_secure_channel/service/eps_models.py +47 -0
  20. bytedance/jeddak_secure_channel/service/ras.py +88 -0
  21. bytedance/jeddak_secure_channel/service/tks.py +51 -0
  22. bytedance/jeddak_secure_channel/service/tks_models.py +334 -0
  23. bytedance/jeddak_secure_channel/utils.py +469 -0
  24. bytedance/jeddak_secure_model/model_automation_tool.py +181 -0
  25. bytedance/jeddak_secure_model/model_encryption.py +416 -0
  26. bytedance/jeddak_secure_model/model_hub_admin.py +116 -0
  27. bytedance/jeddak_secure_model/pcc_utils.py +279 -0
  28. bytedance/jeddak_secure_model/rds_utils.py +63 -0
  29. bytedance/tks/attester.py +41 -0
  30. bytedance/tks/client.py +629 -0
  31. bytedance/tks/consts.py +122 -0
  32. bytedance/tks/crypto/aes.py +47 -0
  33. bytedance/tks/crypto/ec.py +39 -0
  34. bytedance/tks/exceptions.py +22 -0
  35. bytedance/volcengine_aicc_sdk/__init__.py +14 -0
  36. bytedance/volcengine_aicc_sdk/aicc_client.py +277 -0
  37. bytedance/volcengine_aicc_sdk/aicc_inference.py +64 -0
  38. bytedance/volcengine_aicc_sdk/ark_inference_client.py +343 -0
  39. bytedance/volcengine_aicc_sdk/asr_inference_client.py +158 -0
  40. bytedance/volcengine_aicc_sdk/asr_websocket_client.py +705 -0
  41. bytedance/volcengine_aicc_sdk/auc_inference_client.py +305 -0
  42. bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
  43. bytedance/volcengine_aicc_sdk/common/aes.py +136 -0
  44. bytedance/volcengine_aicc_sdk/common/constant.py +4 -0
  45. bytedance/volcengine_aicc_sdk/common/error.py +21 -0
  46. bytedance/volcengine_aicc_sdk/common/log/__init__.py +24 -0
  47. bytedance/volcengine_aicc_sdk/common/log/config.py +31 -0
  48. bytedance/volcengine_aicc_sdk/common/log/logger.py +88 -0
  49. bytedance/volcengine_aicc_sdk/common/tos_utils.py +341 -0
  50. bytedance/volcengine_aicc_sdk/common/utils.py +45 -0
  51. bytedance/volcengine_aicc_sdk/cu_inference_client.py +292 -0
  52. bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +501 -0
  53. bytedance/volcengine_aicc_sdk/secure_http_client.py +406 -0
  54. bytedance/volcengine_aicc_sdk/seedance_inference_client.py +500 -0
  55. bytedance/volcengine_aicc_sdk/seedream_inference_client.py +213 -0
  56. volcengine_python_sdk_aicc-0.0.41.dist-info/METADATA +79 -0
  57. volcengine_python_sdk_aicc-0.0.41.dist-info/RECORD +60 -0
  58. volcengine_python_sdk_aicc-0.0.41.dist-info/WHEEL +5 -0
  59. volcengine_python_sdk_aicc-0.0.41.dist-info/licenses/LICENSE +202 -0
  60. volcengine_python_sdk_aicc-0.0.41.dist-info/top_level.txt +1 -0
@@ -0,0 +1,745 @@
1
+ """
2
+ 数据接收方接口.
3
+ """
4
+
5
+ __all__ = ["Server"]
6
+
7
+ import base64
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import random
12
+ import time
13
+ import uuid
14
+ from dataclasses import dataclass, field
15
+ from datetime import datetime, timedelta, timezone
16
+ from threading import Lock
17
+
18
+ import requests
19
+ from typing_extensions import TYPE_CHECKING, Dict, Optional, Tuple, Union
20
+
21
+ if TYPE_CHECKING:
22
+ from _typeshed import GenericPath
23
+ else:
24
+ GenericPath = str
25
+
26
+ from cryptography.hazmat.primitives import serialization
27
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
28
+
29
+ from bytedance.tks.client import TKSClient, TKSConfig, utc8_timestamp
30
+ from . import error
31
+ from .config import ServerConfig
32
+ from .crypto import FileMode, PublicKey, ResponseKey, ServerSessionKey
33
+ from .log import LogConfig, logger
34
+ from .ra import (
35
+ RaRequest,
36
+ RaResponse,
37
+ RaResponseMetadata,
38
+ RaResponsePod,
39
+ RaResponsePods,
40
+ attest_client,
41
+ validata_ra_request,
42
+ )
43
+ from .service import EpsClient, RasClient
44
+ from .utils import RepeatTimer, request_bytedance_gateway, weak_repeat_timer
45
+
46
+
47
+ @dataclass(eq=False)
48
+ class Server:
49
+ """
50
+ 数据接收方接口.
51
+
52
+ 本类的所有方法是线程安全的.
53
+ """
54
+
55
+ config: ServerConfig
56
+
57
+ ras_client: RasClient = field(init=False, repr=False)
58
+
59
+ eps_client: EpsClient = field(init=False, repr=False)
60
+
61
+ refresh_timer: Optional[RepeatTimer] = field(init=False, default=None)
62
+
63
+ session_key: Optional[ServerSessionKey] = field(init=False, default=None)
64
+
65
+ old_session_key: Optional[ServerSessionKey] = field(init=False, default=None)
66
+
67
+ session_key_lock: Lock = field(init=False, default_factory=Lock)
68
+ """
69
+ 线程安全: 修改 session_key 应该加锁.
70
+ """
71
+
72
+ root_key_info: Dict[str, PublicKey] = field(init=False, default_factory=dict)
73
+
74
+ def __post_init__(self):
75
+ try:
76
+ # 处理环境变量
77
+ # need_evidence = os.environ.get("NEED_EVIDENCE")
78
+ # if need_evidence != None:
79
+ # if str(need_evidence).lower() == "false":
80
+ # self.config.need_evidence = False
81
+ #
82
+ # attest_gpu = os.environ.get("ATTEST_GPU")
83
+ # if attest_gpu != None:
84
+ # if str(attest_gpu).lower() == "false":
85
+ # self.config.attest_gpu = False
86
+ self._init_log()
87
+
88
+ self.ras_client = RasClient(self.config)
89
+ self.eps_client = EpsClient(self.config)
90
+
91
+ if self.config.root_key_config:
92
+ self._root_key_parse()
93
+
94
+ if self.config.refresh_interval is not None:
95
+ refresh_interval = self.config.refresh_interval
96
+
97
+ # 避免多实例集中访问TKS,所以这里加了一个随机数
98
+ refresh_interval = refresh_interval + random.randint(1, 60)
99
+ logger.info(
100
+ f"start auto update key, refresh_interval={refresh_interval}"
101
+ )
102
+
103
+ self.refresh_timer = timer = weak_repeat_timer(
104
+ refresh_interval, self._auto_update
105
+ )
106
+ timer.start()
107
+
108
+ self._auto_update()
109
+ except Exception as e:
110
+ logger.critical(f"__post_init__ failed, e={e}")
111
+
112
+ def _auto_update(self):
113
+ """
114
+ :return:
115
+ """
116
+ try:
117
+ self._auto_load_key()
118
+ except Exception as e:
119
+ logger.critical(f"load tks key failed, e={e}")
120
+
121
+ try:
122
+ if self.config.client_ra_config is not None:
123
+ if not self.attest_client():
124
+ logger.error("attest client failed, Please check your config !")
125
+
126
+ except Exception as e:
127
+ logger.critical(f"attest client failed, e={e}")
128
+
129
+ def _auto_load_key(self):
130
+ try:
131
+ logger.info("start load_key")
132
+ if self.config.key_file:
133
+ if not self.load_file_key():
134
+ logger.error("load key from local key file failed, Please check your config !")
135
+ elif self.config.key_type == "tks":
136
+ if not self.load_tks_key():
137
+ logger.error("load key from tks failed, Please check your config !")
138
+ elif self.config.key_type == "random":
139
+ if not self.generate_key():
140
+ logger.error("generate key failed !")
141
+ else:
142
+ logger.critical("key config error, Both TKS keyid or key file is empty")
143
+
144
+ except Exception as e:
145
+ logger.critical(f"load key failed, e={e}")
146
+
147
+ def _add_key(self, private_pem: Union[str, bytes], tks_version: Optional[int]):
148
+ """
149
+ 添加密钥对.
150
+ """
151
+ # 判断是否需要更新密钥
152
+ if isinstance(private_pem, str):
153
+ new_md5 = hashlib.md5(private_pem.encode()).hexdigest()
154
+ else:
155
+ new_md5 = hashlib.md5(private_pem).hexdigest()
156
+
157
+ if self.session_key:
158
+ old_md5 = self.session_key.pem_md5
159
+ else:
160
+ old_md5 = ""
161
+
162
+ if old_md5 != new_md5:
163
+ expire_at = datetime.now(timezone.utc) + timedelta(
164
+ seconds=self.config.valid_time
165
+ )
166
+ session_key = ServerSessionKey.load(
167
+ private_pem, expire_at, tks_version, new_md5
168
+ )
169
+ with self.session_key_lock:
170
+ self.old_session_key = self.session_key
171
+ self.session_key = session_key
172
+
173
+ def _pcc_login(self) -> Dict[str, str]:
174
+ """
175
+ 访问pcc_controller模块,获取cookie
176
+ :return:
177
+ """
178
+
179
+ if self.config.pcc_login_url is None:
180
+ raise error.ConfigError("pcc_login_url", "PCC login url is missing")
181
+ if self.config.pcc_password is None:
182
+ raise error.ConfigError("pcc_password", "PCC password is missing")
183
+
184
+ ts = str(utc8_timestamp())
185
+ req_body = {
186
+ "Username": self.config.pcc_app_id,
187
+ "Password": hashlib.md5(
188
+ self.config.pcc_password.encode() + ts.encode()
189
+ ).hexdigest(),
190
+ }
191
+
192
+ response = requests.post(
193
+ self.config.pcc_login_url, headers={"TimeStamp": ts}, json=req_body
194
+ )
195
+ cookies = response.cookies
196
+ jeddak_pcc_session = cookies.get("jeddak_pcc_session")
197
+ assert jeddak_pcc_session is not None
198
+ cookies = {"jeddak_pcc_session": jeddak_pcc_session}
199
+ return cookies
200
+
201
+ def _get_pairkey_from_pem(self, private_key: bytes) -> Optional[Dict[str, str]]:
202
+ """
203
+ 从私钥pem中产生公私钥对 for peixuan
204
+ :param private_key:
205
+ :return:
206
+ """
207
+ private_key_pem = serialization.load_pem_private_key(private_key, password=None)
208
+ if not isinstance(private_key_pem, RSAPrivateKey):
209
+ logger.error(f"not RSAPrivateKey")
210
+ return None
211
+
212
+ pub_key = private_key_pem.public_key()
213
+ pub_key_str = pub_key.public_bytes(
214
+ encoding=serialization.Encoding.PEM,
215
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
216
+ ).decode("utf-8")
217
+
218
+ ret = dict()
219
+ ret["pub_key"] = pub_key_str
220
+ ret["pri_key"] = private_key.decode()
221
+
222
+ return ret
223
+
224
+ def _get_tks_info_from_pcc(self, service_id: str, app_id: str):
225
+ try:
226
+ if not service_id:
227
+ return None
228
+
229
+ if self.config.bytedance_top_info:
230
+ top_conf = json.loads(self.config.bytedance_top_info)
231
+ top_conf["action"] = "ListJscKey"
232
+
233
+ url = top_conf.get("url", "open.volcengineapi.com")
234
+ body = {"ServiceId": service_id, "AccountId": app_id}
235
+ response = request_bytedance_gateway(url, body, {}, top_conf)
236
+ else:
237
+ url = os.getenv("PCC_CONTROLLER_ADDR", "http://pcc-controller.aicc-private:9799")
238
+ url = url + "/api/jsc/v1/key/list"
239
+ headers = {
240
+ "Content-Type": "application/json",
241
+ "X-Top-Account-Id": app_id,
242
+ }
243
+ body = {"ServiceId": service_id, "AccountId": app_id}
244
+ response = requests.post(url, json=body, headers=headers)
245
+ if not response or response.status_code != 200:
246
+ logger.error("get tks info from pcc_controller failed")
247
+ return None
248
+
249
+ ra_res = json.loads(response.text).get("Result")
250
+ if not ra_res:
251
+ logger.error(f"get key info from pcc_controller error: ServiceId={service_id}")
252
+ return None
253
+
254
+ key_list = json.loads(response.text).get("Result").get("KeyList")
255
+ if len(key_list) == 1:
256
+ ring_id = key_list[0].get("RingID")
257
+ key_id = key_list[0].get("KeyID")
258
+ return ring_id, key_id
259
+ else:
260
+ logger.error(f"get key info from pcc_controller error: key_list={key_list}")
261
+ return None
262
+
263
+ except Exception as e:
264
+ logger.critical("_get_tks_info_from_pcc exception={}".format(e))
265
+ return None
266
+
267
+ def load_tks_key(self) -> bool:
268
+ """
269
+ 从 TKS 获取或更新密钥对.
270
+ """
271
+ logger.info("start load TKS key")
272
+
273
+ app_id, ring_id, key_id = (
274
+ self.config.tks_app_id,
275
+ self.config.tks_ring_id,
276
+ self.config.tks_key_id,
277
+ )
278
+
279
+ pcc_conf = None
280
+ top_conf = None
281
+ if self.config.bytedance_pcc_info:
282
+ pcc_conf = json.loads(self.config.bytedance_pcc_info)
283
+ elif self.config.bytedance_top_info:
284
+ top_conf = json.loads(self.config.bytedance_top_info)
285
+
286
+ enable_tls = True
287
+ if enable_tls_env := os.environ.get("enable_tls"):
288
+ enable_tls = enable_tls_env.lower() == "true"
289
+ elif enable_tls_env := os.environ.get("ENABLE_TLS"):
290
+ enable_tls = enable_tls_env.lower() == "true"
291
+
292
+ cfg = TKSConfig(
293
+ addr=self.config.tks_url,
294
+ enable_tls=enable_tls,
295
+ pcc_config=pcc_conf,
296
+ top_config=top_conf,
297
+ )
298
+
299
+ tks_client = TKSClient(app_id, cfg)
300
+
301
+ need_evidence_conf = self.config.need_evidence
302
+ need_evidence = self.config.need_evidence
303
+ need_evidence_env = os.environ.get("NEED_EVIDENCE")
304
+ if need_evidence_env:
305
+ need_evidence = not (need_evidence_env.lower() == "false")
306
+ logger.info(
307
+ f"need_evidence: conf={need_evidence_conf}, env={need_evidence_env}, use={need_evidence}"
308
+ )
309
+
310
+ attest_gpu_conf = self.config.attest_gpu
311
+ attest_gpu = attest_gpu_conf
312
+ attest_gpu_env = os.environ.get("ATTEST_GPU")
313
+ if attest_gpu_env:
314
+ attest_gpu = not (attest_gpu_env.lower() == "false")
315
+ logger.info(
316
+ f"attest_gpu: conf={attest_gpu_conf}, env={attest_gpu_env}, use={attest_gpu}"
317
+ )
318
+
319
+ max_try_count = 3
320
+ for _ in range(max_try_count):
321
+ try:
322
+ service_id = self.config.service_id
323
+ if not service_id:
324
+ service_id = os.getenv("JPCC_JCK_SERVICE_ID")
325
+
326
+ key_info = self._get_tks_info_from_pcc(service_id, self.config.tks_app_id)
327
+ if key_info:
328
+ ring_id = key_info[0]
329
+ key_id = key_info[1]
330
+
331
+ if self.config.pcc_login_url:
332
+ cookies = self._pcc_login()
333
+ private_key = tks_client.get_key(
334
+ ring_id=ring_id,
335
+ key_id=key_id,
336
+ need_evidence=need_evidence,
337
+ attest_gpu=attest_gpu,
338
+ cookies=cookies,
339
+ )
340
+ else:
341
+ private_key = tks_client.get_key(
342
+ ring_id=ring_id,
343
+ key_id=key_id,
344
+ need_evidence=need_evidence,
345
+ attest_gpu=attest_gpu,
346
+ policy_id=self.config.tks_policy_id,
347
+ tks_url=self.config.tks_url,
348
+ tks_uid=self.config.tks_app_id,
349
+ tks_service_name=self.config.tks_service_name,
350
+ )
351
+
352
+ pair_key_info = self._get_pairkey_from_pem(private_key)
353
+ logger.info("get key from TKS success")
354
+ if pair_key_info:
355
+ logger.debug(f"TKS public key={pair_key_info.get('pub_key')}")
356
+
357
+ self._add_key(private_key, None)
358
+
359
+ return True
360
+ except Exception as e:
361
+ logger.critical(f"load_tks_key exception={e}")
362
+ logger.critical("load_tks_key failed, try again ....")
363
+ time.sleep(5)
364
+
365
+ return False
366
+
367
+ def load_file_key(self, path: Optional[GenericPath] = None):
368
+ """
369
+ 从文件读取 RSA 密钥.
370
+
371
+ Args:
372
+ path: 密钥文件路径. 默认为配置中的 `key_file`.
373
+ """
374
+
375
+ path = path or self.config.key_file
376
+ if path is None:
377
+ raise error.ParamError("path", "Key file is not specified")
378
+
379
+ with open(path) as f:
380
+ self.load_key(f.read())
381
+
382
+ return True
383
+
384
+ def load_key(self, private_key: Union[str, bytes]):
385
+ """
386
+ 导入 RSA 密钥.
387
+
388
+ Args:
389
+ private_key: PEM 格式的 RSA 私钥.
390
+ """
391
+ self._add_key(private_key, tks_version=None)
392
+
393
+ def generate_key(self):
394
+ """
395
+ 随机生成密钥对.
396
+ """
397
+ expire_at = datetime.now(timezone.utc) + timedelta(
398
+ seconds=self.config.valid_time
399
+ )
400
+
401
+ session_key = ServerSessionKey.generate(expire_at)
402
+ # 修改时加锁
403
+ with self.session_key_lock:
404
+ self.old_session_key = self.session_key
405
+ self.session_key = session_key
406
+
407
+ return True
408
+
409
+ def remove_expired_keys(self):
410
+ """
411
+ 删除已过期的密钥对.
412
+ """
413
+ # 修改时加锁
414
+ with self.session_key_lock:
415
+ if self.session_key is not None and self.session_key.is_expired():
416
+ self.session_key = None
417
+
418
+ if self.old_session_key is not None and self.old_session_key.is_expired():
419
+ self.old_session_key = None
420
+
421
+ def _get_public_key(self) -> str:
422
+ """
423
+ 获取已保存的最新的公钥.
424
+
425
+ Raises:
426
+ KeyMissingError: 没有可用的密钥. 应通过 `load_tks_key`, `load_key`, 或
427
+ `generate_key` 添加密钥.
428
+ """
429
+ with self.session_key_lock:
430
+ current_key = self.session_key
431
+
432
+ if not current_key:
433
+ raise error.KeyMissingError() from None
434
+
435
+ return current_key.get_public_key()
436
+
437
+ def handle_ra_request(self, request: RaRequest) -> RaResponse:
438
+ """
439
+ 处理来自数据发送方的远程证明请求.
440
+ 应当在调用本函数前, 验证请求中 `token` 身份令牌的有效性.
441
+
442
+ Returns:
443
+ 远程证明响应. 可作为 JSON 响应给数据发送方.
444
+ Raises:
445
+ NetworkError: 访问证明生成服务 (EPS) 或远程证明服务 (RAS) 时出现问题.
446
+ KeyMissingError: 没有可用的密钥. 应通过 `load_tks_key`, `load_key`, 或
447
+ `generate_key` 添加密钥.
448
+ """
449
+ validata_ra_request(request)
450
+
451
+ # quote = self.eps_client.get_quote(request["nonce"])
452
+ # token = self.ras_client.get_attestation_evaluation("TDX", quote)
453
+
454
+ res_meta_data = RaResponseMetadata(Code=0, Message="success")
455
+ res_result: RaResponsePods = {
456
+ "pod_1": RaResponsePod(
457
+ evidence="evidence",
458
+ token="token",
459
+ key_info={"pub_key_info": self._get_public_key()},
460
+ ),
461
+ }
462
+
463
+ return RaResponse(ResponseMetadata=res_meta_data, ResponseResult=res_result)
464
+
465
+ def decrypt(
466
+ self,
467
+ message: Union[str, bytes],
468
+ app_info: Optional[str] = None,
469
+ sign: Optional[str] = None,
470
+ ) -> bytes:
471
+ """
472
+ 解密二进制数据.
473
+
474
+ Raises:
475
+ DecryptionError: 加密消息不合法, 或者加密使用的密钥不在当前可用的密钥中.
476
+ KeyMissingError: 没有可用的密钥. 应通过 `load_tks_key`, `load_key`, 或
477
+ `generate_key` 添加密钥.
478
+ """
479
+ if app_info and sign:
480
+ if not self.verify_sign(app_info, message, sign):
481
+ raise error.SignatureError("verify sign failed")
482
+
483
+ plaintext, _ = self.decrypt_with_response(message)
484
+ return plaintext
485
+
486
+ def decrypt_with_response(
487
+ self,
488
+ message: Union[str, bytes],
489
+ app_info: Optional[str] = None,
490
+ sign: Optional[str] = None,
491
+ ) -> Tuple[bytes, ResponseKey]:
492
+ """
493
+ 解密二进制数据, 并获取用于加密响应的密钥.
494
+
495
+ Raises:
496
+ DecryptionError: 加密消息不合法, 或者加密使用的密钥不在当前可用的密钥中.
497
+ KeyMissingError: 没有可用的密钥. 应通过 `load_tks_key`, `load_key`, 或
498
+ `generate_key` 添加密钥.
499
+ """
500
+ last_error: Optional[Exception] = None
501
+
502
+ if app_info and sign:
503
+ if not self.verify_sign(app_info, message, sign):
504
+ raise error.SignatureError("verify sign failed")
505
+
506
+ with self.session_key_lock:
507
+ current_key = self.session_key
508
+ old_key = self.old_session_key
509
+
510
+ for key in [current_key, old_key]:
511
+ if key is None:
512
+ continue
513
+ try:
514
+ response_key = key.decrypt(message)
515
+ return response_key
516
+ except Exception as e:
517
+ last_error = e
518
+ continue
519
+
520
+ if last_error:
521
+ # 尝试更新一下密钥,因为上面的失败可能是本地密钥版本晚于客户端的密钥版本,等TKS支持密钥版本后可用密钥版本号来比较
522
+ self._auto_load_key()
523
+ if self.session_key is not None:
524
+ try:
525
+ response_key = self.session_key.decrypt(message)
526
+ return response_key
527
+ except Exception as e:
528
+ last_error = e
529
+
530
+ raise last_error or error.KeyMissingError()
531
+
532
+ def decrypt_file(
533
+ self,
534
+ metadata: Union[str, bytes],
535
+ source_path: GenericPath,
536
+ dest_path: GenericPath,
537
+ mode: FileMode,
538
+ app_info: Optional[str] = None,
539
+ sign: Optional[str] = None,
540
+ ) -> ResponseKey:
541
+ """
542
+ 解密文件数据.
543
+
544
+ Returns:
545
+ 用于加密响应的密钥.
546
+ """
547
+ if mode not in ["b", "t"]:
548
+ raise error.ParamError("mode", "Mode should be 'b' or 't'")
549
+
550
+ if app_info and sign:
551
+ # TODO: fill in message
552
+ if not self.verify_sign(app_info, metadata, sign):
553
+ raise error.SignatureError("verify sign failed")
554
+
555
+ with self.session_key_lock:
556
+ current_key = self.session_key
557
+ old_key = self.old_session_key
558
+
559
+ last_error: Optional[Exception] = None
560
+ for key in [current_key, old_key]:
561
+ if key is None:
562
+ continue
563
+ try:
564
+ with open(source_path, f"r{mode}") as source, open(
565
+ dest_path, f"w{mode}"
566
+ ) as dest:
567
+ response_key = key.decrypt_stream(metadata, source, dest, mode)
568
+ return response_key
569
+ except Exception as e:
570
+ last_error = e
571
+ continue
572
+
573
+ if last_error:
574
+ # 尝试更新一下密钥,因为上面的失败可能是本地密钥版本晚于客户端的密钥版本,等TKS支持密钥版本后可用密钥版本号来比较
575
+ self._auto_load_key()
576
+ if self.session_key is not None:
577
+ try:
578
+ with open(source_path, f"r{mode}") as source, open(
579
+ dest_path, f"w{mode}"
580
+ ) as dest:
581
+ response_key = self.session_key.decrypt_stream(
582
+ metadata, source, dest, mode
583
+ )
584
+ return response_key
585
+ except Exception as e:
586
+ last_error = e
587
+
588
+ raise last_error or error.KeyMissingError()
589
+
590
+ def attest_client(self) -> bool:
591
+ """
592
+ 对客户端进行远程证明, 判断客户端是否可信.
593
+
594
+ Returns:
595
+ True 为可信, False 为不可信.
596
+ """
597
+ client_ra_config = self.config.client_ra_config
598
+ if not client_ra_config:
599
+ return True
600
+
601
+ client_ra_config = json.loads(client_ra_config)
602
+ if not client_ra_config.get("open"):
603
+ return True
604
+
605
+ logger.info("start attest client")
606
+ nonce = ''.join(str(uuid.uuid4()).split('-'))[-16:]
607
+ max_try_count = 3
608
+ for _ in range(max_try_count):
609
+ ret = attest_client(client_ra_config, self.config.bytedance_top_info, nonce)
610
+ if ret:
611
+ logger.info("attest client success")
612
+ return True
613
+ else:
614
+ logger.info("attest client failed, try again...")
615
+
616
+ return False
617
+
618
+ def _root_key_parse(self):
619
+ """
620
+ [{
621
+ "app_info": "lenovo_xiao_tian",
622
+ "pub_key": "******"
623
+ }]
624
+ :return:
625
+ """
626
+ try:
627
+ root_key_config = self.config.root_key_config
628
+ if not root_key_config:
629
+ return
630
+
631
+ if isinstance(root_key_config, str):
632
+ root_key_config = json.loads(root_key_config)
633
+
634
+ for tmp_info in root_key_config:
635
+ app_info = tmp_info.get("app_info")
636
+ pub_key = tmp_info.get("pub_key")
637
+ if pub_key:
638
+ with open(pub_key) as f:
639
+ public_pem = f.read()
640
+ self.root_key_info[app_info] = PublicKey.from_public_pem(public_pem)
641
+ else:
642
+ # 从pcc_controller中读取
643
+ public_pem = self._get_app_key_from_pcc(app_info)
644
+ if public_pem:
645
+ self.root_key_info[app_info] = PublicKey.from_public_pem(
646
+ public_pem
647
+ )
648
+ else:
649
+ logger.error("get app pubkey from pcc_controller failed")
650
+
651
+ logger.info("root_key_parse success")
652
+ except Exception as e:
653
+ logger.critical("root_key_parse exception={}".format(e))
654
+ raise error.ConfigError("RootKey conf", "RootPubKey conf error")
655
+
656
+ def verify_sign(
657
+ self, app_info: str, msg: Union[str, bytes], sign: Union[str, bytes]
658
+ ) -> bool:
659
+ """
660
+ 签名验证
661
+ :param app_info: app_id+app_name
662
+ :param msg:
663
+ :param sign:
664
+ :return:
665
+ """
666
+ try:
667
+ if not app_info or not msg or not sign:
668
+ logger.info(f"verify_sign error, app_info={app_info}, sign={sign}")
669
+ raise error.SignatureError("RSA signing failed") from None
670
+
671
+ if app_info not in self.root_key_info:
672
+ logger.info(
673
+ f"verify_sign error, can not find app_info in root key conf, app_info={app_info}"
674
+ )
675
+ raise error.SignatureError(
676
+ f"can not find app_info={app_info}"
677
+ ) from None
678
+
679
+ if isinstance(sign, str):
680
+ sign = base64.b64decode(sign.encode())
681
+
682
+ return self.root_key_info[app_info].verify(sign, msg)
683
+
684
+ except Exception as e:
685
+ logger.critical("verify_sign exception={}".format(e))
686
+ raise error.SignatureError("can not find app_info") from e
687
+
688
+ def _get_app_key_from_pcc(self, app_info):
689
+ try:
690
+ if not app_info:
691
+ return None
692
+
693
+ top_conf = json.loads(self.config.bytedance_top_info)
694
+ top_conf["action"] = "ListJscAppInfo"
695
+
696
+ url = top_conf.get("top_url", "open.volcengineapi.com")
697
+ body = {"PageSize": -1}
698
+ headers = {}
699
+ response = request_bytedance_gateway(url, body, headers, top_conf)
700
+ if not response or response.status_code != 200:
701
+ logger.error("get app key from pcc_controller failed")
702
+ return None
703
+
704
+ ra_res = json.loads(response.text).get("Result")
705
+ if not ra_res:
706
+ logger.error(
707
+ f"get app key from pcc_controller error: app_info={app_info}"
708
+ )
709
+ return None
710
+
711
+ app_list = json.loads(response.text).get("Result").get("AppList")
712
+ for app in app_list:
713
+ if app_info == app.get("AppInfo"):
714
+ return app.get("AppKey")
715
+
716
+ return None
717
+ except Exception as e:
718
+ logger.critical("get_app_key_from_pcc exception={}".format(e))
719
+ return None
720
+
721
+ def _init_log(self):
722
+ config = LogConfig()
723
+
724
+ log_config = dict()
725
+ if self.config.log_config:
726
+ log_config = json.loads(self.config.log_config)
727
+
728
+ if config_dir := log_config.get("dir"):
729
+ config.dir = config_dir
730
+
731
+ if config_filename := log_config.get("filename"):
732
+ config.filename = config_filename
733
+ # else:
734
+ # config.filename = "server.log"
735
+
736
+ if config_rotation := log_config.get("rotation"):
737
+ config.rotation = config_rotation
738
+
739
+ if config_retention := log_config.get("retention"):
740
+ config.retention = config_retention
741
+
742
+ if config_level := log_config.get("level"):
743
+ config.level = config_level
744
+
745
+ logger.init_log_config(config)