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,772 @@
|
|
|
1
|
+
"""
|
|
2
|
+
安全通信远程证明功能.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"RaConfig",
|
|
7
|
+
"RaPod",
|
|
8
|
+
"RaRequest",
|
|
9
|
+
"RaResponse",
|
|
10
|
+
"RaResponseMetadata",
|
|
11
|
+
"RaResponsePod",
|
|
12
|
+
"RaResponsePods",
|
|
13
|
+
"attest_server",
|
|
14
|
+
"prepare_ra_request",
|
|
15
|
+
"validata_ra_request",
|
|
16
|
+
"attest_client",
|
|
17
|
+
"generate_nonce",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
import time
|
|
21
|
+
import uuid
|
|
22
|
+
import base64
|
|
23
|
+
import json
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
from typing import Tuple
|
|
27
|
+
import jwt
|
|
28
|
+
import hashlib
|
|
29
|
+
from pymerkle import verify_inclusion, MerkleProof
|
|
30
|
+
from jwcrypto import jwk
|
|
31
|
+
from jwt.exceptions import ExpiredSignatureError, InvalidIssuerError, InvalidSignatureError
|
|
32
|
+
from volcenginesdkcore.rest import ApiException
|
|
33
|
+
import requests
|
|
34
|
+
from typing_extensions import (
|
|
35
|
+
Dict,
|
|
36
|
+
List,
|
|
37
|
+
Literal,
|
|
38
|
+
NotRequired,
|
|
39
|
+
Optional,
|
|
40
|
+
TypedDict,
|
|
41
|
+
Union,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
from . import error
|
|
45
|
+
from .crypto import PublicKey
|
|
46
|
+
from .log import logger
|
|
47
|
+
from .utils import request_bytedance_gateway
|
|
48
|
+
|
|
49
|
+
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
50
|
+
from cryptography.hazmat.primitives import hashes
|
|
51
|
+
from cryptography.hazmat.backends import default_backend
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
RA_NONCE_LEN = 12
|
|
55
|
+
RA_TYPE_LOCAL = "local"
|
|
56
|
+
RA_TYPE_TCA = "tca"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RaPod(TypedDict):
|
|
60
|
+
cluster_id: str
|
|
61
|
+
|
|
62
|
+
namespace: str
|
|
63
|
+
|
|
64
|
+
deployment: str
|
|
65
|
+
|
|
66
|
+
pod: NotRequired[Literal["random", "all"]]
|
|
67
|
+
"""默认为 random, 如果想要全部则为 all."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class RaConfig:
|
|
72
|
+
"""
|
|
73
|
+
访问远程证明功能的配置.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
ra_url: str = ""
|
|
77
|
+
"""远程证明端点的 HTTP 地址."""
|
|
78
|
+
|
|
79
|
+
ra_type: str = RA_TYPE_TCA
|
|
80
|
+
"""ra的类型,local:本地RA, tca:TCA的RAS_adapter接口(默认)."""
|
|
81
|
+
|
|
82
|
+
ra_service_name: str = ""
|
|
83
|
+
"""数据接收端(服务端)的服务名. ,用于验证通过机密容器部署的服务"""
|
|
84
|
+
|
|
85
|
+
ra_pods_info: str = ""
|
|
86
|
+
"""
|
|
87
|
+
待验证的节点的pod信息,用于验证非通过机密容器部署的服务,
|
|
88
|
+
格式: {cluster_id:***, namespace:***, deployment:***}
|
|
89
|
+
ra_service_name 和 ra_pods_info 二选一
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
ra_uid: str = ""
|
|
93
|
+
"""用户的UID"""
|
|
94
|
+
|
|
95
|
+
ra_key_negotiation: bool = True
|
|
96
|
+
"""是否在验证的时候需要同步进行协商密钥."""
|
|
97
|
+
|
|
98
|
+
ra_need_token: bool = True # RA 功能稳定后要改成 False
|
|
99
|
+
"""是否要返回验证 token. 默认为是."""
|
|
100
|
+
|
|
101
|
+
attest_must: bool = False
|
|
102
|
+
"""是否一定要返回验证报告才能采用公钥."""
|
|
103
|
+
|
|
104
|
+
ra_policy_id: str = ""
|
|
105
|
+
"""进行验证所使用的策略, 如果 ra_need_token 为 True, 则必填."""
|
|
106
|
+
|
|
107
|
+
bytedance_top_info: str = ""
|
|
108
|
+
"""访问bytedance TOP网关的信息."""
|
|
109
|
+
|
|
110
|
+
ra_attested_pods: List[RaPod] = field(default_factory=list)
|
|
111
|
+
"""old 需要验证的节点信息."""
|
|
112
|
+
|
|
113
|
+
ra_policy_ids: List[str] = field(default_factory=list)
|
|
114
|
+
"""old 进行验证所使用的策略, 如果 ra_need_token 为 True, 则必填."""
|
|
115
|
+
|
|
116
|
+
auth_token: str = ""
|
|
117
|
+
"""用来进行鉴权的凭证"""
|
|
118
|
+
|
|
119
|
+
custom_headers: Optional[dict[str, str]] = None
|
|
120
|
+
"""自定义的请求头."""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class RaRequest(TypedDict):
|
|
124
|
+
"""数据发送方发出的远程证明请求."""
|
|
125
|
+
|
|
126
|
+
key_negotiation: bool
|
|
127
|
+
"""是否在验证的时候需要同步进行协商密钥."""
|
|
128
|
+
|
|
129
|
+
token: bool
|
|
130
|
+
"""是否要返回验证 token."""
|
|
131
|
+
|
|
132
|
+
policy_ids: List[str]
|
|
133
|
+
"""进行验证所使用的策略."""
|
|
134
|
+
|
|
135
|
+
attested_pods: List[RaPod]
|
|
136
|
+
"""需要验证的节点信息."""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class RaResponseMetadata(TypedDict):
|
|
140
|
+
Code: int
|
|
141
|
+
Message: str
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class RaResponsePod(TypedDict):
|
|
145
|
+
evidence: str
|
|
146
|
+
token: str
|
|
147
|
+
key_info: dict
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
RaResponsePods = Dict[str, RaResponsePod]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class RaResponse(TypedDict):
|
|
154
|
+
"""数据接收方发出的远程证明响应."""
|
|
155
|
+
|
|
156
|
+
ResponseMetadata: RaResponseMetadata
|
|
157
|
+
|
|
158
|
+
ResponseResult: RaResponsePods
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def prepare_ra_request(config: RaConfig) -> RaRequest:
|
|
162
|
+
"""
|
|
163
|
+
准备远程证明请求.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
远程证明请求. 可作为 JSON 发送给数据接收方远程证明端点.
|
|
167
|
+
"""
|
|
168
|
+
return RaRequest(
|
|
169
|
+
key_negotiation=config.ra_key_negotiation,
|
|
170
|
+
token=config.ra_need_token,
|
|
171
|
+
policy_ids=config.ra_policy_ids,
|
|
172
|
+
attested_pods=config.ra_attested_pods,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def validata_ra_request(request: RaRequest) -> None:
|
|
177
|
+
"""
|
|
178
|
+
验证远程证明请求有效性.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ParamError: 请求的 nonce 或 timestamp 字段无效.
|
|
182
|
+
"""
|
|
183
|
+
nonce = request.get("hex_runtime_data")
|
|
184
|
+
if nonce:
|
|
185
|
+
nonce = base64.b64decode(nonce)
|
|
186
|
+
if len(nonce) != RA_NONCE_LEN:
|
|
187
|
+
raise error.ParamError("nonce", "Invalid RA request")
|
|
188
|
+
|
|
189
|
+
# time = datetime.fromtimestamp(request["timestamp"], timezone.utc)
|
|
190
|
+
# now = datetime.now(timezone.utc)
|
|
191
|
+
# if time > now + timedelta(minutes=1) or time < now - timedelta(minutes=2):
|
|
192
|
+
# raise error.ParamError("timestamp", "Invalid RA request")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def generate_nonce():
|
|
196
|
+
return ''.join(str(uuid.uuid4()).split('-'))[-16:]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def request_tca(config: RaConfig, nonce: str = None):
|
|
200
|
+
try:
|
|
201
|
+
ra_url = config.ra_url
|
|
202
|
+
ra_service_name = config.ra_service_name
|
|
203
|
+
ra_pods_info = config.ra_pods_info
|
|
204
|
+
ra_key_negotiation = config.ra_key_negotiation
|
|
205
|
+
ra_policy_id = config.ra_policy_id
|
|
206
|
+
ra_uid = config.ra_uid
|
|
207
|
+
|
|
208
|
+
top_info = config.bytedance_top_info
|
|
209
|
+
if top_info and isinstance(top_info, str):
|
|
210
|
+
top_info = json.loads(top_info)
|
|
211
|
+
# assert isinstance(top_info, dict)
|
|
212
|
+
|
|
213
|
+
headers = {"UID": ra_uid}
|
|
214
|
+
|
|
215
|
+
if not nonce:
|
|
216
|
+
nonce = generate_nonce()
|
|
217
|
+
|
|
218
|
+
if ra_service_name:
|
|
219
|
+
body = {
|
|
220
|
+
"Nonce": nonce,
|
|
221
|
+
"PolicyID": ra_policy_id,
|
|
222
|
+
"ServiceName": ra_service_name,
|
|
223
|
+
"KeyNegotiation": ra_key_negotiation,
|
|
224
|
+
"Token": True,
|
|
225
|
+
"Version": 2
|
|
226
|
+
}
|
|
227
|
+
elif ra_pods_info:
|
|
228
|
+
if isinstance(ra_pods_info, str):
|
|
229
|
+
ra_pods_info = json.loads(ra_pods_info)
|
|
230
|
+
body = {
|
|
231
|
+
"Nonce": nonce,
|
|
232
|
+
"PolicyID": ra_policy_id,
|
|
233
|
+
"AttestedPods": [ra_pods_info],
|
|
234
|
+
"KeyNegotiation": ra_key_negotiation,
|
|
235
|
+
"Token": True,
|
|
236
|
+
"Version": 2
|
|
237
|
+
}
|
|
238
|
+
else:
|
|
239
|
+
raise error.ServiceError("RA", config.ra_url, None, "call TCA error")
|
|
240
|
+
|
|
241
|
+
if top_info.get("target_uid") and not top_info.get("aicc_saas_trn"):
|
|
242
|
+
from bytedance.jeddak_secure_channel.utils import top_request
|
|
243
|
+
return top_request(ra_url, body, headers, top_info)
|
|
244
|
+
else:
|
|
245
|
+
return request_bytedance_gateway(ra_url, body, headers, top_info)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.critical(f"Response is not JSON: service={config.ra_url}")
|
|
248
|
+
raise error.ServiceError("RA", config.ra_url, None, "call TCA error") from e
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def attest_server_local(token: Optional[str], config: RaConfig, nonce=None):
|
|
252
|
+
"""
|
|
253
|
+
对数据接收方进行远程证明(非TCA方式).
|
|
254
|
+
"""
|
|
255
|
+
timestamp = str(int(datetime.now(timezone.utc).timestamp()))
|
|
256
|
+
auth_token = token
|
|
257
|
+
if not auth_token:
|
|
258
|
+
auth_token = config.auth_token
|
|
259
|
+
|
|
260
|
+
headers = {
|
|
261
|
+
"Content-Type": "application/json",
|
|
262
|
+
"timestamp": f"{timestamp}",
|
|
263
|
+
"Authorization": f"Bearer {auth_token}"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if config.custom_headers:
|
|
267
|
+
headers.update(config.custom_headers)
|
|
268
|
+
|
|
269
|
+
if not nonce:
|
|
270
|
+
nonce = generate_nonce()
|
|
271
|
+
|
|
272
|
+
request = {"Nonce": nonce, "ra_service_name": config.ra_service_name}
|
|
273
|
+
try:
|
|
274
|
+
logger.info(
|
|
275
|
+
f"request ra, url={config.ra_url}, header={headers}, params={request}"
|
|
276
|
+
)
|
|
277
|
+
response = requests.post(config.ra_url, headers=headers, json=request)
|
|
278
|
+
return response
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.critical(f"RA Network error: url={config.ra_url}, e={e}")
|
|
282
|
+
raise error.NetworkError("RA", config.ra_url, endpoint=None) from e
|
|
283
|
+
|
|
284
|
+
# if response.status_code != 200:
|
|
285
|
+
# logger.error(
|
|
286
|
+
# f"Service error: service={config.ra_url} code={response.status_code}"
|
|
287
|
+
# )
|
|
288
|
+
# raise error.ServiceError(
|
|
289
|
+
# "RA", config.ra_url, None, f"code={response.status_code}"
|
|
290
|
+
# )
|
|
291
|
+
|
|
292
|
+
# try:
|
|
293
|
+
# response_json: RaResponse = response.json()
|
|
294
|
+
# logger.info(f"ra response={response_json}")
|
|
295
|
+
#
|
|
296
|
+
# res_meta_data = response_json.get("ResponseMetadata")
|
|
297
|
+
# res_result = response_json.get("ResponseResult")
|
|
298
|
+
#
|
|
299
|
+
# if not res_meta_data or not res_result:
|
|
300
|
+
# raise error.ServiceError("RA", config.ra_url, None, "wrong format")
|
|
301
|
+
#
|
|
302
|
+
# if res_meta_data.get("Code") != 0:
|
|
303
|
+
# raise error.ServiceError(
|
|
304
|
+
# "RA", config.ra_url, None, res_meta_data.get("Message", "")
|
|
305
|
+
# )
|
|
306
|
+
#
|
|
307
|
+
# for pod_name, pod_ra_info in res_result.items():
|
|
308
|
+
# # evidence = pod_ra_info.get("evidence")
|
|
309
|
+
# # token = pod_ra_info.get("token")
|
|
310
|
+
# key_info = pod_ra_info.get("key_info")
|
|
311
|
+
# if not key_info:
|
|
312
|
+
# raise error.ServiceError("RA", config.ra_url, "RSA key", "null")
|
|
313
|
+
#
|
|
314
|
+
# return True, key_info["pub_key_info"]
|
|
315
|
+
# except Exception as e:
|
|
316
|
+
# logger.critical(f"Response is not JSON: service={config.ra_url}")
|
|
317
|
+
# raise error.ServiceError("RA", config.ra_url, None, "not JSON") from e
|
|
318
|
+
#
|
|
319
|
+
# return False, ""
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def verify_jwt_token(token):
|
|
323
|
+
try:
|
|
324
|
+
tmp_payload = token.split(".")[1]
|
|
325
|
+
jwk_data = json.loads(base64.urlsafe_b64decode(tmp_payload + "=" * (4 - len(tmp_payload) % 4))).get('jwk')
|
|
326
|
+
|
|
327
|
+
public_key = jwk.JWK(**jwk_data)
|
|
328
|
+
|
|
329
|
+
# 转换为 PEM 格式,以便 PyJWT 使用
|
|
330
|
+
pem_public_key = public_key.export_to_pem()
|
|
331
|
+
|
|
332
|
+
header = jwt.get_unverified_header(token)
|
|
333
|
+
|
|
334
|
+
time.sleep(2)
|
|
335
|
+
|
|
336
|
+
# 验证 Token
|
|
337
|
+
payload = jwt.decode(token, pem_public_key, algorithms=[header.get('alg')],
|
|
338
|
+
issuer="Bytedance-Remote-Attestation-Service",
|
|
339
|
+
options={"verify_signature": True})
|
|
340
|
+
|
|
341
|
+
logger.debug(f"Token is valid. Payload:{payload}")
|
|
342
|
+
return True
|
|
343
|
+
except ExpiredSignatureError:
|
|
344
|
+
logger.warning("verify_ra_token: Token has expired.")
|
|
345
|
+
return False
|
|
346
|
+
except InvalidIssuerError:
|
|
347
|
+
logger.warning("verify_ra_token: Invalid issuer.")
|
|
348
|
+
return False
|
|
349
|
+
except InvalidSignatureError:
|
|
350
|
+
logger.warning("verify_ra_token: Invalid signature.")
|
|
351
|
+
return False
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.warning(f"verify_ra_token: {e}")
|
|
354
|
+
return False
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def base64url_decode(encoded_str):
|
|
358
|
+
padded_str = encoded_str + '=' * ((4 - len(encoded_str) % 4) % 4) # 补齐padding
|
|
359
|
+
return base64.urlsafe_b64decode(padded_str)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def jwk_to_rsa_public_key(jwk_info: dict):
|
|
363
|
+
"""将JWK格式的公钥转换为RSA公钥对象"""
|
|
364
|
+
# 从JWK中提取必要的参数
|
|
365
|
+
n = int.from_bytes(base64url_decode(jwk_info['n']), byteorder='big')
|
|
366
|
+
e = int.from_bytes(base64url_decode(jwk_info['e']), byteorder='big')
|
|
367
|
+
public_key = rsa.RSAPublicNumbers(e, n).public_key(default_backend())
|
|
368
|
+
return public_key
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def verify_jwt_token_new(token):
|
|
372
|
+
"""验证JWT token,不依赖JWT库"""
|
|
373
|
+
try:
|
|
374
|
+
# 分割JWT:header.payload.signature
|
|
375
|
+
parts = token.split(".")
|
|
376
|
+
if len(parts) != 3:
|
|
377
|
+
logger.error("verify_ra_token: Invalid token format")
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
header_encoded, payload_encoded, signature_encoded = parts
|
|
381
|
+
|
|
382
|
+
header = json.loads(base64url_decode(header_encoded).decode('utf-8'))
|
|
383
|
+
payload = json.loads(base64url_decode(payload_encoded).decode('utf-8'))
|
|
384
|
+
|
|
385
|
+
# 验证token是否过期
|
|
386
|
+
if 'exp' in payload:
|
|
387
|
+
if payload['exp'] < int(time.time()):
|
|
388
|
+
logger.warning("verify_ra_token: Token has expired.")
|
|
389
|
+
|
|
390
|
+
# 验证issuer
|
|
391
|
+
if payload.get('iss') != "Bytedance-Remote-Attestation-Service":
|
|
392
|
+
logger.error("verify_ra_token: Invalid issuer.")
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
# 签名验证
|
|
396
|
+
if 'jwk' in header:
|
|
397
|
+
public_key = jwk_to_rsa_public_key(header['jwk'])
|
|
398
|
+
alg = header.get("alg")
|
|
399
|
+
elif 'jwk' in payload:
|
|
400
|
+
public_key = jwk_to_rsa_public_key(payload['jwk'])
|
|
401
|
+
alg = payload.get('jwk').get("alg")
|
|
402
|
+
else:
|
|
403
|
+
public_key = None
|
|
404
|
+
alg = None
|
|
405
|
+
logger.warning("can not find pub_key from JWT token")
|
|
406
|
+
|
|
407
|
+
if public_key:
|
|
408
|
+
message = f"{header_encoded}.{payload_encoded}".encode('utf-8')
|
|
409
|
+
signature = base64url_decode(signature_encoded)
|
|
410
|
+
try:
|
|
411
|
+
if alg == "RS384":
|
|
412
|
+
public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA384())
|
|
413
|
+
else:
|
|
414
|
+
logger.warning(f"no support JWT sign algorithms: [{alg}]")
|
|
415
|
+
return False
|
|
416
|
+
except Exception as e:
|
|
417
|
+
logger.error(f"JWT Token Signature verification failed: {e}")
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
return True
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
logger.error(f"verify_jwt_token Exception={e}")
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def gen_report_data(nonce_up, nonce_down, dh_param, expiration_time):
|
|
428
|
+
try:
|
|
429
|
+
nonce_up_raw = bytes.fromhex(nonce_up)
|
|
430
|
+
nonce_down_raw = base64.b16decode(nonce_down.upper())
|
|
431
|
+
dh_param_bytes = base64.b64decode(dh_param)
|
|
432
|
+
expiration_time_bytes = str(expiration_time).encode()
|
|
433
|
+
report_data_input = nonce_up_raw + nonce_down_raw + dh_param_bytes + expiration_time_bytes
|
|
434
|
+
h = hashlib.sha256()
|
|
435
|
+
h.update(report_data_input)
|
|
436
|
+
report_data = h.hexdigest()
|
|
437
|
+
|
|
438
|
+
h = hashlib.sha512()
|
|
439
|
+
h.update(report_data.encode())
|
|
440
|
+
report_data_bytes = h.digest()
|
|
441
|
+
|
|
442
|
+
return report_data_bytes
|
|
443
|
+
|
|
444
|
+
except Exception as e:
|
|
445
|
+
logger.error(f"gen report data,msg={str(e)}")
|
|
446
|
+
raise Exception(f"gen report data,msg={str(e)}")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def verify_nonce(proof, report_data, nonce, pub_key_info):
|
|
450
|
+
try:
|
|
451
|
+
# 前64位是nonce的sha256哈希,后64位是公钥的sha256哈希
|
|
452
|
+
nonce_hash = hashlib.sha256(nonce.encode()).hexdigest()
|
|
453
|
+
|
|
454
|
+
# 计算pub_key_info的SHA256哈希
|
|
455
|
+
if pub_key_info:
|
|
456
|
+
pub_key_hash = hashlib.sha256(pub_key_info.encode()).hexdigest()
|
|
457
|
+
else:
|
|
458
|
+
# 如果公钥为空,用64个0替换
|
|
459
|
+
pub_key_hash = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
460
|
+
|
|
461
|
+
# 拼接两个哈希值
|
|
462
|
+
base = nonce_hash + pub_key_hash
|
|
463
|
+
logger.debug(f'base={base}')
|
|
464
|
+
|
|
465
|
+
if not proof:
|
|
466
|
+
logger.debug(f'report_data={report_data}')
|
|
467
|
+
return report_data == base
|
|
468
|
+
|
|
469
|
+
h = hashlib.sha512()
|
|
470
|
+
h.update(b'\x00' + base.encode())
|
|
471
|
+
base = h.hexdigest()
|
|
472
|
+
root = report_data
|
|
473
|
+
|
|
474
|
+
logger.debug(f"new base={base}")
|
|
475
|
+
|
|
476
|
+
proof = MerkleProof.deserialize(proof)
|
|
477
|
+
verify_inclusion(bytes.fromhex(base), bytes.fromhex(root), proof)
|
|
478
|
+
return True
|
|
479
|
+
except Exception as e:
|
|
480
|
+
logger.debug(f"verify nonce failed!!!, msg={str(e)}")
|
|
481
|
+
return False
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def check_pubkey_valid(pk_s: str, pk_r: str, timestamp: str, version: str, service_name: str, sign: str) -> bool:
|
|
485
|
+
"""
|
|
486
|
+
对pk_s的正确性进行检验
|
|
487
|
+
:param pk_s:服务公钥
|
|
488
|
+
:param pk_r:rar公钥
|
|
489
|
+
:param timestamp:时间戳
|
|
490
|
+
:param version:version
|
|
491
|
+
:param service_name:version
|
|
492
|
+
:param sign: sign(pk_s+timestamp, pr_r)
|
|
493
|
+
:return: True/False
|
|
494
|
+
"""
|
|
495
|
+
if not pk_s or not pk_r or not sign:
|
|
496
|
+
return False
|
|
497
|
+
|
|
498
|
+
rsa_pub_r = PublicKey.from_public_pem(pk_r)
|
|
499
|
+
|
|
500
|
+
if isinstance(sign, str):
|
|
501
|
+
try:
|
|
502
|
+
sign = base64.b16decode(sign.upper().encode())
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.warning(f"ra: check_pubkey_valid b16decode sign failed, Exception={e}")
|
|
505
|
+
sign = base64.b64decode(sign.encode())
|
|
506
|
+
|
|
507
|
+
msg = f"{pk_s}{timestamp}{service_name}".encode()
|
|
508
|
+
if version:
|
|
509
|
+
version_prefix = int(version).to_bytes(length=8, byteorder="big", signed=False)
|
|
510
|
+
msg = version_prefix + msg
|
|
511
|
+
|
|
512
|
+
ret = rsa_pub_r.verify(sign, msg)
|
|
513
|
+
|
|
514
|
+
logger.debug(f"check_pubkey_valid: pk_s={pk_s}, pk_r={pk_r}, version={version}, timestamp={timestamp},"
|
|
515
|
+
f"service_name={service_name}, msg={msg}, sign={sign}, ret={ret}")
|
|
516
|
+
return ret
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def attest_server(token: Optional[str], config: RaConfig, nonce: str = None) -> Tuple[bool, str]:
|
|
520
|
+
"""
|
|
521
|
+
对数据接收方进行远程证明.
|
|
522
|
+
:return (ra_status, pub_key)
|
|
523
|
+
"""
|
|
524
|
+
try:
|
|
525
|
+
if config.bytedance_top_info:
|
|
526
|
+
response = request_tca(config, nonce)
|
|
527
|
+
else:
|
|
528
|
+
response = attest_server_local(token, config, nonce)
|
|
529
|
+
except ApiException as e:
|
|
530
|
+
logger.error(f"attest_server error: config={config} ApiException={e}")
|
|
531
|
+
raise error.ServiceError("RA", config.ra_url, None, "NULL")
|
|
532
|
+
except Exception as e:
|
|
533
|
+
logger.error(f"attest_server error: config={config} Exception={e}")
|
|
534
|
+
raise error.ServiceError("RA", config.ra_url, None, "NULL")
|
|
535
|
+
|
|
536
|
+
if response is None:
|
|
537
|
+
logger.error(f"RA Service error: config={config} response=NULL")
|
|
538
|
+
raise error.ServiceError("RA", config.ra_url, None, "NULL")
|
|
539
|
+
|
|
540
|
+
if isinstance(response, requests.Response):
|
|
541
|
+
if response.status_code != 200:
|
|
542
|
+
logger.error(
|
|
543
|
+
f"RA Service error: code={response.status_code}, content={response.content}, config={config}"
|
|
544
|
+
)
|
|
545
|
+
raise error.ServiceError(
|
|
546
|
+
"RA", config.ra_url, None, f"code={response.status_code}"
|
|
547
|
+
)
|
|
548
|
+
else:
|
|
549
|
+
response_text = response.text
|
|
550
|
+
ra_res = json.loads(response_text).get("Result")
|
|
551
|
+
if not ra_res:
|
|
552
|
+
ra_res = json.loads(response_text).get("ResponseResult")
|
|
553
|
+
else:
|
|
554
|
+
ra_res = response
|
|
555
|
+
|
|
556
|
+
if not ra_res:
|
|
557
|
+
logger.error(f"RA response is NULL, config={config}")
|
|
558
|
+
return False, ""
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
attestation_status = False
|
|
562
|
+
public_key_info = ""
|
|
563
|
+
pub_r = ""
|
|
564
|
+
|
|
565
|
+
# 取第一个节点验证就可以
|
|
566
|
+
for cluster_info, res_info in ra_res.items():
|
|
567
|
+
# raw_evidence = res_info.get("evidence")
|
|
568
|
+
# if raw_evidence:
|
|
569
|
+
# tmp = json.loads(raw_evidence)
|
|
570
|
+
# raw_evidence_json = json.dumps(tmp, indent=4)
|
|
571
|
+
# logger.debug(f"get ra evidence:{raw_evidence_json}")
|
|
572
|
+
if config.ra_key_negotiation:
|
|
573
|
+
public_key_info = res_info.get("key_info").get("pub_key_info")
|
|
574
|
+
else:
|
|
575
|
+
public_key_info = ""
|
|
576
|
+
|
|
577
|
+
raw_token = res_info.get("token")
|
|
578
|
+
if not raw_token:
|
|
579
|
+
logger.critical("RA response token is NULL, conf={config}")
|
|
580
|
+
return attestation_status, public_key_info
|
|
581
|
+
else:
|
|
582
|
+
evidence_info = res_info.get("evidence")
|
|
583
|
+
evidence_info_json = json.loads(evidence_info)
|
|
584
|
+
proof = evidence_info_json.get('proof')
|
|
585
|
+
logger.debug(f"proof={proof}")
|
|
586
|
+
|
|
587
|
+
if config.ra_key_negotiation:
|
|
588
|
+
key_info = res_info.get("key_info")
|
|
589
|
+
pub_r = key_info.get("rar_pub_key")
|
|
590
|
+
timestamp = key_info.get("timestamp")
|
|
591
|
+
version = key_info.get("version")
|
|
592
|
+
sign = key_info.get("rar_signature")
|
|
593
|
+
service_name = config.ra_service_name.strip()
|
|
594
|
+
|
|
595
|
+
if sign:
|
|
596
|
+
if not check_pubkey_valid(public_key_info, pub_r, timestamp, version, service_name, sign):
|
|
597
|
+
logger.critical("verify sign failed")
|
|
598
|
+
return attestation_status, public_key_info
|
|
599
|
+
else:
|
|
600
|
+
logger.info("ra: get pub_key, verify sign success")
|
|
601
|
+
|
|
602
|
+
attestation_status = verify_ra_result(raw_token, public_key_info, pub_r, proof, nonce)
|
|
603
|
+
|
|
604
|
+
break
|
|
605
|
+
return attestation_status, public_key_info
|
|
606
|
+
except Exception as e:
|
|
607
|
+
logger.critical(f"Response is not JSON: conf={config}, Exception={e}")
|
|
608
|
+
raise error.ServiceError("RA", config.ra_url, None, "not JSON") from e
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def verify_ra_result(raw_token, pub_key_info, pub_r, proof, nonce, challenge_report_data: str = None) -> bool:
|
|
612
|
+
try:
|
|
613
|
+
report_data = ""
|
|
614
|
+
token_parts = raw_token.split(".")
|
|
615
|
+
if len(token_parts) >= 2:
|
|
616
|
+
token_data = token_parts[1]
|
|
617
|
+
try:
|
|
618
|
+
# 使用base64.urlsafe_b64decode进行URL安全模式的解码
|
|
619
|
+
decoded_data = base64.urlsafe_b64decode(
|
|
620
|
+
token_data + "=" * (4 - len(token_data) % 4)
|
|
621
|
+
).decode("utf-8")
|
|
622
|
+
token_dict = json.loads(decoded_data)
|
|
623
|
+
report_data = token_dict.get('tdx').get('report_data')
|
|
624
|
+
except Exception as e:
|
|
625
|
+
logger.error(f"解码出现错误: {e}")
|
|
626
|
+
raise Exception("decode token error")
|
|
627
|
+
logger.info(f"in ra token,report_data={report_data}")
|
|
628
|
+
|
|
629
|
+
try:
|
|
630
|
+
if challenge_report_data:
|
|
631
|
+
if report_data != challenge_report_data:
|
|
632
|
+
return False
|
|
633
|
+
else:
|
|
634
|
+
nonce_ret = verify_nonce(proof, report_data, nonce, pub_r) or \
|
|
635
|
+
verify_nonce(proof, report_data, nonce, pub_key_info)
|
|
636
|
+
if not nonce_ret:
|
|
637
|
+
logger.error("verify nonce failed")
|
|
638
|
+
return False
|
|
639
|
+
|
|
640
|
+
except Exception as e:
|
|
641
|
+
logger.error(f"verify nonce failed!!!,msg={str(e)}")
|
|
642
|
+
return False
|
|
643
|
+
|
|
644
|
+
if not verify_jwt_token(raw_token):
|
|
645
|
+
logger.warning("token verify failed")
|
|
646
|
+
return False
|
|
647
|
+
else:
|
|
648
|
+
token_dict = {}
|
|
649
|
+
token_parts = raw_token.split(".")
|
|
650
|
+
if len(token_parts) >= 2:
|
|
651
|
+
token_data = token_parts[1]
|
|
652
|
+
try:
|
|
653
|
+
# 使用base64.urlsafe_b64decode进行URL安全模式的解码
|
|
654
|
+
decoded_data = base64.urlsafe_b64decode(
|
|
655
|
+
token_data + "=" * (4 - len(token_data) % 4)
|
|
656
|
+
).decode("utf-8")
|
|
657
|
+
|
|
658
|
+
token_dict = json.loads(decoded_data)
|
|
659
|
+
except Exception as e:
|
|
660
|
+
logger.critical(f"token 解码出现错误: {e}")
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
attestation_status = len(token_dict.get("policies_matched")) > 0
|
|
664
|
+
return attestation_status
|
|
665
|
+
except Exception as e:
|
|
666
|
+
logger.critical("verify_ra_result error,msg={}".format(str(e)))
|
|
667
|
+
raise Exception("verify_ra_result error,msg={}".format(str(e)))
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def attest_client(client_ra_config: dict, client_bytedance_top_info: str, nonce: str = None) -> bool:
|
|
671
|
+
"""
|
|
672
|
+
对数据发送方进行远程证明.
|
|
673
|
+
"""
|
|
674
|
+
ra_service_name: Optional[str] = client_ra_config.get("ra_service_name")
|
|
675
|
+
ra_policy_id: Optional[str] = client_ra_config.get("ra_policy_id")
|
|
676
|
+
ra_pods_info = client_ra_config.get("ra_pods_info")
|
|
677
|
+
ra_key_negotiation: Optional[bool] = client_ra_config.get("ra_key_negotiation")
|
|
678
|
+
|
|
679
|
+
top_info = client_bytedance_top_info
|
|
680
|
+
if top_info and isinstance(top_info, str):
|
|
681
|
+
top_info = json.loads(top_info)
|
|
682
|
+
|
|
683
|
+
ra_url = client_ra_config.get("ra_url", "open.volcengineapi.com")
|
|
684
|
+
ra_uid = client_ra_config.get("ra_uid")
|
|
685
|
+
if ra_uid:
|
|
686
|
+
headers = {"UID": ra_uid}
|
|
687
|
+
else:
|
|
688
|
+
headers = {}
|
|
689
|
+
|
|
690
|
+
if not nonce:
|
|
691
|
+
nonce = generate_nonce()
|
|
692
|
+
|
|
693
|
+
if ra_service_name:
|
|
694
|
+
body = {
|
|
695
|
+
"Nonce": nonce,
|
|
696
|
+
"PolicyID": ra_policy_id,
|
|
697
|
+
"ServiceName": ra_service_name,
|
|
698
|
+
"KeyNegotiation": ra_key_negotiation,
|
|
699
|
+
"Token": True,
|
|
700
|
+
}
|
|
701
|
+
elif ra_pods_info:
|
|
702
|
+
if isinstance(ra_pods_info, str):
|
|
703
|
+
ra_pods_info = json.loads(ra_pods_info)
|
|
704
|
+
body = {
|
|
705
|
+
"Nonce": nonce,
|
|
706
|
+
"PolicyID": ra_policy_id,
|
|
707
|
+
"AttestedPods": [ra_pods_info],
|
|
708
|
+
"KeyNegotiation": ra_key_negotiation,
|
|
709
|
+
"Token": True,
|
|
710
|
+
}
|
|
711
|
+
else:
|
|
712
|
+
raise error.ServiceError("RA", ra_service_name, None, "call TCA error")
|
|
713
|
+
|
|
714
|
+
response = request_bytedance_gateway(ra_url, body, headers, top_info)
|
|
715
|
+
if not response:
|
|
716
|
+
logger.error("Client RA error: response NULL")
|
|
717
|
+
raise error.ServiceError("RA", ra_service_name, None, "NULL")
|
|
718
|
+
|
|
719
|
+
if response.status_code != 200:
|
|
720
|
+
logger.error(
|
|
721
|
+
f"client ra error: ra_service_name={ra_service_name} code={response.status_code}"
|
|
722
|
+
)
|
|
723
|
+
raise error.ServiceError(
|
|
724
|
+
"RA", ra_service_name, None, f"code={response.status_code}"
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
try:
|
|
728
|
+
ra_res = json.loads(response.text).get("Result")
|
|
729
|
+
if not ra_res:
|
|
730
|
+
logger.error(
|
|
731
|
+
f"attest_client failed, verify_result is NULL, ra interface response={response.text}"
|
|
732
|
+
)
|
|
733
|
+
return False
|
|
734
|
+
|
|
735
|
+
attestation_status = False
|
|
736
|
+
for cluster_info, res_info in ra_res.items():
|
|
737
|
+
# raw_evidence = res_info.get("evidence")
|
|
738
|
+
# tmp = json.loads(raw_evidence)
|
|
739
|
+
# raw_evidence_json = json.dumps(tmp, indent=4)
|
|
740
|
+
# logger.debug(f"get ra evidence:{raw_evidence_json}")
|
|
741
|
+
|
|
742
|
+
raw_token = res_info.get("token")
|
|
743
|
+
if not raw_token:
|
|
744
|
+
logger.critical("token is None")
|
|
745
|
+
return False
|
|
746
|
+
|
|
747
|
+
if not verify_jwt_token(raw_token):
|
|
748
|
+
logger.critical("token verify failed")
|
|
749
|
+
return False
|
|
750
|
+
|
|
751
|
+
token_dict = {}
|
|
752
|
+
token_parts = raw_token.split(".")
|
|
753
|
+
if len(token_parts) >= 2:
|
|
754
|
+
token_data = token_parts[1]
|
|
755
|
+
try:
|
|
756
|
+
# 使用base64.urlsafe_b64decode进行URL安全模式的解码
|
|
757
|
+
decoded_data = base64.urlsafe_b64decode(
|
|
758
|
+
token_data + "=" * (4 - len(token_data) % 4)
|
|
759
|
+
).decode("utf-8")
|
|
760
|
+
|
|
761
|
+
token_dict = json.loads(decoded_data)
|
|
762
|
+
except Exception as e:
|
|
763
|
+
logger.critical(f"token 解码出现错误: {e}")
|
|
764
|
+
return False
|
|
765
|
+
|
|
766
|
+
attestation_status = len(token_dict.get("policies_matched")) > 0
|
|
767
|
+
break
|
|
768
|
+
|
|
769
|
+
return attestation_status
|
|
770
|
+
except Exception as e:
|
|
771
|
+
logger.critical(f"Response is not JSON: client_service_name={ra_service_name}")
|
|
772
|
+
raise error.ServiceError("RA", ra_service_name, None, "not JSON") from e
|