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.
- bytedance/__init__.py +4 -0
- bytedance/jeddak_secure_channel/__init__.py +28 -0
- bytedance/jeddak_secure_channel/client.py +305 -0
- bytedance/jeddak_secure_channel/config.py +171 -0
- bytedance/jeddak_secure_channel/consts.py +5 -0
- bytedance/jeddak_secure_channel/crypto/__init__.py +14 -0
- bytedance/jeddak_secure_channel/crypto/aes.py +174 -0
- bytedance/jeddak_secure_channel/crypto/envelope.py +395 -0
- bytedance/jeddak_secure_channel/crypto/rsa.py +164 -0
- bytedance/jeddak_secure_channel/error.py +131 -0
- bytedance/jeddak_secure_channel/log/__init__.py +23 -0
- bytedance/jeddak_secure_channel/log/config.py +31 -0
- bytedance/jeddak_secure_channel/log/logger.py +88 -0
- bytedance/jeddak_secure_channel/logger_factory.py +134 -0
- bytedance/jeddak_secure_channel/ra.py +772 -0
- bytedance/jeddak_secure_channel/server.py +745 -0
- bytedance/jeddak_secure_channel/service/__init__.py +16 -0
- bytedance/jeddak_secure_channel/service/eps.py +133 -0
- bytedance/jeddak_secure_channel/service/eps_models.py +47 -0
- bytedance/jeddak_secure_channel/service/ras.py +88 -0
- bytedance/jeddak_secure_channel/service/tks.py +51 -0
- bytedance/jeddak_secure_channel/service/tks_models.py +334 -0
- bytedance/jeddak_secure_channel/utils.py +469 -0
- bytedance/jeddak_secure_model/model_automation_tool.py +181 -0
- bytedance/jeddak_secure_model/model_encryption.py +416 -0
- bytedance/jeddak_secure_model/model_hub_admin.py +116 -0
- bytedance/jeddak_secure_model/pcc_utils.py +279 -0
- bytedance/jeddak_secure_model/rds_utils.py +63 -0
- bytedance/tks/attester.py +41 -0
- bytedance/tks/client.py +629 -0
- bytedance/tks/consts.py +122 -0
- bytedance/tks/crypto/aes.py +47 -0
- bytedance/tks/crypto/ec.py +39 -0
- bytedance/tks/exceptions.py +22 -0
- bytedance/volcengine_aicc_sdk/__init__.py +14 -0
- bytedance/volcengine_aicc_sdk/aicc_client.py +277 -0
- bytedance/volcengine_aicc_sdk/aicc_inference.py +64 -0
- bytedance/volcengine_aicc_sdk/ark_inference_client.py +343 -0
- bytedance/volcengine_aicc_sdk/asr_inference_client.py +158 -0
- bytedance/volcengine_aicc_sdk/asr_websocket_client.py +705 -0
- bytedance/volcengine_aicc_sdk/auc_inference_client.py +305 -0
- bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
- bytedance/volcengine_aicc_sdk/common/aes.py +136 -0
- bytedance/volcengine_aicc_sdk/common/constant.py +4 -0
- bytedance/volcengine_aicc_sdk/common/error.py +21 -0
- bytedance/volcengine_aicc_sdk/common/log/__init__.py +24 -0
- bytedance/volcengine_aicc_sdk/common/log/config.py +31 -0
- bytedance/volcengine_aicc_sdk/common/log/logger.py +88 -0
- bytedance/volcengine_aicc_sdk/common/tos_utils.py +341 -0
- bytedance/volcengine_aicc_sdk/common/utils.py +45 -0
- bytedance/volcengine_aicc_sdk/cu_inference_client.py +292 -0
- bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +501 -0
- bytedance/volcengine_aicc_sdk/secure_http_client.py +406 -0
- bytedance/volcengine_aicc_sdk/seedance_inference_client.py +500 -0
- bytedance/volcengine_aicc_sdk/seedream_inference_client.py +213 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/METADATA +79 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/RECORD +60 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/WHEEL +5 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/licenses/LICENSE +202 -0
- 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)
|