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,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
信封加密算法.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ClientSessionKey",
|
|
7
|
+
"EncryptedMessage",
|
|
8
|
+
"ResponseKey",
|
|
9
|
+
"ServerSessionKey",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
import base64
|
|
13
|
+
import dataclasses
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from io import BytesIO
|
|
19
|
+
from threading import Lock
|
|
20
|
+
|
|
21
|
+
from typing_extensions import IO, TYPE_CHECKING, Optional, Self, Set, Tuple, Union
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from _typeshed import GenericPath
|
|
25
|
+
else:
|
|
26
|
+
GenericPath = str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
from .. import error
|
|
30
|
+
from .aes import AES_KEY_LEN, AES_MAC_LEN, AES_NONCE_LEN, AesKey, FileMode
|
|
31
|
+
from .rsa import PrivateKey, PublicKey
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(eq=False, frozen=True)
|
|
37
|
+
class EncryptedMessage:
|
|
38
|
+
"""
|
|
39
|
+
信封加密数据.
|
|
40
|
+
可作为数据发送方的请求, 及数据接收方的响应.
|
|
41
|
+
|
|
42
|
+
本类不含可变状态, 所有方法是线程安全的.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
nonce: bytes
|
|
46
|
+
"""单次随机数."""
|
|
47
|
+
|
|
48
|
+
mac: bytes
|
|
49
|
+
"""校验码."""
|
|
50
|
+
|
|
51
|
+
key: Optional[bytes] = None
|
|
52
|
+
"""
|
|
53
|
+
用公钥加密的对称密钥.
|
|
54
|
+
在数据发送方的加密请求中使用, 在数据接收方的加密响应中为空.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
ciphertext: Optional[bytes] = None
|
|
58
|
+
"""
|
|
59
|
+
加密数据.
|
|
60
|
+
在字符串 / 二进制数据加密中使用, 在文件加密中为空.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def serialize(self) -> str:
|
|
64
|
+
"""
|
|
65
|
+
将消息序列化为字符串.
|
|
66
|
+
"""
|
|
67
|
+
return json.dumps(
|
|
68
|
+
{
|
|
69
|
+
k: base64.b64encode(v).decode()
|
|
70
|
+
for k, v in self.__dict__.items()
|
|
71
|
+
if v is not None
|
|
72
|
+
},
|
|
73
|
+
separators=(",", ":"),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def deserialize(cls, data: Union[str, bytes]) -> Self:
|
|
78
|
+
"""
|
|
79
|
+
从字符串反序列化消息.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
return cls(**{k: base64.b64decode(v) for k, v in json.loads(data).items()})
|
|
83
|
+
except Exception:
|
|
84
|
+
raise error.DecryptionError("Invalid encoding") from None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(eq=False, frozen=True)
|
|
88
|
+
class ResponseKey:
|
|
89
|
+
"""
|
|
90
|
+
对响应加解密的密钥.
|
|
91
|
+
|
|
92
|
+
本类不含可变状态, 所有方法是线程安全的.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
_key: AesKey
|
|
96
|
+
|
|
97
|
+
def encrypt(self, plaintext: Union[str, bytes]) -> str:
|
|
98
|
+
"""
|
|
99
|
+
加密响应 (由服务端使用).
|
|
100
|
+
"""
|
|
101
|
+
if isinstance(plaintext, str):
|
|
102
|
+
plaintext = plaintext.encode("utf-8")
|
|
103
|
+
|
|
104
|
+
nonce, ciphertext, mac = self._key.encrypt(plaintext)
|
|
105
|
+
return EncryptedMessage(nonce=nonce, mac=mac, ciphertext=ciphertext).serialize()
|
|
106
|
+
|
|
107
|
+
def decrypt(self, response: Union[str, bytes]) -> bytes:
|
|
108
|
+
"""
|
|
109
|
+
解密响应 (由客户端使用).
|
|
110
|
+
"""
|
|
111
|
+
message = EncryptedMessage.deserialize(response)
|
|
112
|
+
|
|
113
|
+
if message.ciphertext is None or message.key is not None:
|
|
114
|
+
raise error.ParamError("response", "Message is not server response")
|
|
115
|
+
if len(message.nonce) != AES_NONCE_LEN or len(message.mac) != AES_MAC_LEN:
|
|
116
|
+
raise error.DecryptionError("Invalid data length")
|
|
117
|
+
|
|
118
|
+
return self._key.decrypt(message.nonce, message.ciphertext, message.mac)
|
|
119
|
+
|
|
120
|
+
def encrypt_file(
|
|
121
|
+
self,
|
|
122
|
+
source_path: GenericPath,
|
|
123
|
+
dest_path: GenericPath,
|
|
124
|
+
mode: FileMode,
|
|
125
|
+
nonce: Optional[bytes] = None,
|
|
126
|
+
) -> str:
|
|
127
|
+
"""
|
|
128
|
+
加密响应文件 (由服务端使用).
|
|
129
|
+
"""
|
|
130
|
+
with open(source_path, f"r{mode}") as source, open(
|
|
131
|
+
dest_path, f"w{mode}"
|
|
132
|
+
) as dest:
|
|
133
|
+
nonce, mac = self._key.encrypt_stream(source, dest, mode, nonce)
|
|
134
|
+
|
|
135
|
+
metadata = EncryptedMessage(nonce=nonce, mac=mac)
|
|
136
|
+
|
|
137
|
+
return metadata.serialize()
|
|
138
|
+
|
|
139
|
+
def decrypt_file(
|
|
140
|
+
self,
|
|
141
|
+
metadata: str,
|
|
142
|
+
source_path: GenericPath,
|
|
143
|
+
dest_path: GenericPath,
|
|
144
|
+
mode: FileMode,
|
|
145
|
+
):
|
|
146
|
+
"""
|
|
147
|
+
解密响应文件 (由客户端使用).
|
|
148
|
+
"""
|
|
149
|
+
message = EncryptedMessage.deserialize(metadata)
|
|
150
|
+
|
|
151
|
+
if message.ciphertext is not None or message.key is not None:
|
|
152
|
+
raise error.ParamError("response", "Message is not server response")
|
|
153
|
+
if len(message.nonce) != AES_NONCE_LEN or len(message.mac) != AES_MAC_LEN:
|
|
154
|
+
raise error.DecryptionError("Invalid data length")
|
|
155
|
+
|
|
156
|
+
return self.decrypt_file_with_nonce(
|
|
157
|
+
source_path, dest_path, message.nonce, message.mac, mode
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def decrypt_file_with_nonce(
|
|
161
|
+
self,
|
|
162
|
+
source_path: GenericPath,
|
|
163
|
+
dest_path: GenericPath,
|
|
164
|
+
nonce: bytes,
|
|
165
|
+
mac: Optional[bytes],
|
|
166
|
+
mode: FileMode,
|
|
167
|
+
):
|
|
168
|
+
"""
|
|
169
|
+
解密响应文件 (由客户端使用).
|
|
170
|
+
"""
|
|
171
|
+
with open(source_path, f"r{mode}") as source, open(
|
|
172
|
+
dest_path, f"w{mode}"
|
|
173
|
+
) as dest:
|
|
174
|
+
self._key.decrypt_stream(source, dest, nonce, mac, mode)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass(eq=False, frozen=True)
|
|
178
|
+
class ClientSessionKey:
|
|
179
|
+
"""
|
|
180
|
+
数据发送方持有的密钥.
|
|
181
|
+
|
|
182
|
+
本类不含可变状态, 所有方法是线程安全的.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
server_public_key: PublicKey
|
|
186
|
+
"""数据接收方公钥."""
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def load(cls, public_pem: Union[str, bytes]) -> Self:
|
|
190
|
+
"""
|
|
191
|
+
通过数据接收方的公钥产生发送方密钥.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
private_pem: PEM 格式的 RSA 公钥.
|
|
195
|
+
"""
|
|
196
|
+
public_key = PublicKey.from_public_pem(public_pem)
|
|
197
|
+
|
|
198
|
+
return cls(public_key)
|
|
199
|
+
|
|
200
|
+
def encrypt(
|
|
201
|
+
self, plaintext: Union[str, bytes], key: Optional[AesKey] = None
|
|
202
|
+
) -> EncryptedMessage:
|
|
203
|
+
"""
|
|
204
|
+
信封加密.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
key: 指定加密用的对称密钥. 默认为随机生成 (一次一密).
|
|
208
|
+
Returns:
|
|
209
|
+
信封加密数据.
|
|
210
|
+
"""
|
|
211
|
+
if isinstance(plaintext, str):
|
|
212
|
+
plaintext = plaintext.encode("utf-8")
|
|
213
|
+
|
|
214
|
+
with BytesIO(plaintext) as source, BytesIO() as dest:
|
|
215
|
+
message = self.encrypt_stream(source, dest, "b", key)
|
|
216
|
+
message = dataclasses.replace(message, ciphertext=dest.getvalue())
|
|
217
|
+
|
|
218
|
+
return message
|
|
219
|
+
|
|
220
|
+
def encrypt_with_response(
|
|
221
|
+
self, plaintext: Union[str, bytes], aes_key: AesKey = None
|
|
222
|
+
) -> Tuple[EncryptedMessage, ResponseKey]:
|
|
223
|
+
"""
|
|
224
|
+
信封加密, 并获取用于解密响应的密钥.
|
|
225
|
+
"""
|
|
226
|
+
if aes_key is None:
|
|
227
|
+
key = AesKey.generate()
|
|
228
|
+
else:
|
|
229
|
+
key = aes_key
|
|
230
|
+
|
|
231
|
+
return self.encrypt(plaintext, key), ResponseKey(key)
|
|
232
|
+
|
|
233
|
+
def encrypt_stream(
|
|
234
|
+
self, source: IO, dest: IO, mode: FileMode, key: Optional[AesKey] = None
|
|
235
|
+
) -> EncryptedMessage:
|
|
236
|
+
"""
|
|
237
|
+
信封加密数据流.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
信封加密元数据.
|
|
241
|
+
"""
|
|
242
|
+
symmetric_key = key or AesKey.generate()
|
|
243
|
+
|
|
244
|
+
nonce, mac = symmetric_key.encrypt_stream(source, dest, mode)
|
|
245
|
+
|
|
246
|
+
encrypted_key = self.server_public_key.encrypt(symmetric_key.key)
|
|
247
|
+
|
|
248
|
+
return EncryptedMessage(key=encrypted_key, nonce=nonce, mac=mac)
|
|
249
|
+
|
|
250
|
+
def encrypt_stream_with_response(
|
|
251
|
+
self, source: IO, dest: IO, mode: FileMode
|
|
252
|
+
) -> Tuple[EncryptedMessage, ResponseKey]:
|
|
253
|
+
"""
|
|
254
|
+
信封加密数据流, 并获取用于解密响应的密钥.
|
|
255
|
+
"""
|
|
256
|
+
key = AesKey.generate()
|
|
257
|
+
|
|
258
|
+
# return self.encrypt_stream(source, dest, mode), ResponseKey(key)
|
|
259
|
+
return self.encrypt_stream(source, dest, mode, key), ResponseKey(key)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass(eq=False)
|
|
263
|
+
class ServerSessionKey:
|
|
264
|
+
"""
|
|
265
|
+
数据接收方持有的密钥.
|
|
266
|
+
|
|
267
|
+
本类的所有方法是线程安全的.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
private_key: PrivateKey
|
|
271
|
+
"""私钥."""
|
|
272
|
+
|
|
273
|
+
expire_at: datetime
|
|
274
|
+
"""【未启用】密钥对在此时间后无效."""
|
|
275
|
+
|
|
276
|
+
tks_version: Optional[int]
|
|
277
|
+
"""【未启用】密钥版本, 用于判断获取的密钥是否变化. """
|
|
278
|
+
|
|
279
|
+
pem_md5: Optional[str] = None
|
|
280
|
+
"""pem的MD5值."""
|
|
281
|
+
|
|
282
|
+
used_nonce: Set[bytes] = field(default_factory=set)
|
|
283
|
+
"""已使用的单次随机数列表."""
|
|
284
|
+
|
|
285
|
+
used_nonce_lock: Lock = field(init=False, default_factory=Lock)
|
|
286
|
+
"""线程安全: 修改 used_nonce 应该加锁."""
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def load(
|
|
290
|
+
cls,
|
|
291
|
+
private_pem: Union[str, bytes],
|
|
292
|
+
expire_at: datetime,
|
|
293
|
+
tks_version: Optional[int],
|
|
294
|
+
pem_md5: Optional[str],
|
|
295
|
+
) -> Self:
|
|
296
|
+
"""
|
|
297
|
+
通过 PEM 格式的 RSA 私钥产生接收方密钥.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
private_pem: PEM 格式的 RSA 私钥.
|
|
301
|
+
:param pem_md5:
|
|
302
|
+
:param private_pem:
|
|
303
|
+
:param expire_at:
|
|
304
|
+
:param tks_version:
|
|
305
|
+
"""
|
|
306
|
+
private_key = PrivateKey.from_private_pem(private_pem)
|
|
307
|
+
|
|
308
|
+
return cls(private_key, expire_at, tks_version, pem_md5)
|
|
309
|
+
|
|
310
|
+
@classmethod
|
|
311
|
+
def generate(cls, expire_at: datetime) -> Self:
|
|
312
|
+
"""
|
|
313
|
+
随机生成接收方密钥.
|
|
314
|
+
"""
|
|
315
|
+
private_key = PrivateKey.generate()
|
|
316
|
+
|
|
317
|
+
return cls(private_key, expire_at, tks_version=None, pem_md5=None)
|
|
318
|
+
|
|
319
|
+
def is_expired(self) -> bool:
|
|
320
|
+
"""
|
|
321
|
+
是否已超过有效期.
|
|
322
|
+
"""
|
|
323
|
+
now = datetime.now(timezone.utc)
|
|
324
|
+
if now > self.expire_at:
|
|
325
|
+
logger.warning("session key maybe expired")
|
|
326
|
+
|
|
327
|
+
return False # 服务端密钥暂时不考虑有效期
|
|
328
|
+
|
|
329
|
+
def get_public_key(self) -> str:
|
|
330
|
+
"""
|
|
331
|
+
获取 PEM 格式的公钥.
|
|
332
|
+
"""
|
|
333
|
+
return self.private_key.public_pem()
|
|
334
|
+
|
|
335
|
+
def decrypt(
|
|
336
|
+
self, message: Union[str, bytes, EncryptedMessage]
|
|
337
|
+
) -> Tuple[bytes, ResponseKey]:
|
|
338
|
+
"""
|
|
339
|
+
信封解密.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
明文数据, 及用于加密响应的密钥.
|
|
343
|
+
|
|
344
|
+
Raises:
|
|
345
|
+
DecryptionError: 加密消息不合法, 或者加密使用的密钥不是本密钥.
|
|
346
|
+
"""
|
|
347
|
+
if isinstance(message, (str, bytes)):
|
|
348
|
+
message = EncryptedMessage.deserialize(message)
|
|
349
|
+
if message.ciphertext is None:
|
|
350
|
+
raise error.ParamError("message", "Message has no data")
|
|
351
|
+
|
|
352
|
+
with BytesIO(message.ciphertext) as source, BytesIO() as dest:
|
|
353
|
+
response_key = self.decrypt_stream(message, source, dest, "b")
|
|
354
|
+
plaintext = dest.getvalue()
|
|
355
|
+
|
|
356
|
+
return plaintext, response_key
|
|
357
|
+
|
|
358
|
+
def decrypt_stream(
|
|
359
|
+
self,
|
|
360
|
+
metadata: Union[str, bytes, EncryptedMessage],
|
|
361
|
+
source: IO,
|
|
362
|
+
dest: IO,
|
|
363
|
+
mode: FileMode,
|
|
364
|
+
) -> ResponseKey:
|
|
365
|
+
"""
|
|
366
|
+
信封解密数据流.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
用于加密响应的密钥.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
DecryptionError: 加密消息不合法, 或者加密使用的密钥不是本密钥.
|
|
373
|
+
"""
|
|
374
|
+
if isinstance(metadata, (str, bytes)):
|
|
375
|
+
metadata = EncryptedMessage.deserialize(metadata)
|
|
376
|
+
if metadata.key is None:
|
|
377
|
+
raise error.ParamError("metadata", "Message has no key")
|
|
378
|
+
|
|
379
|
+
# with self.used_nonce_lock:
|
|
380
|
+
# if metadata.nonce in self.used_nonce:
|
|
381
|
+
# raise error.NonceReusedError()
|
|
382
|
+
# self.used_nonce.add(metadata.nonce)
|
|
383
|
+
|
|
384
|
+
symmetric_key = self.private_key.decrypt(metadata.key)
|
|
385
|
+
if (
|
|
386
|
+
len(symmetric_key) != AES_KEY_LEN
|
|
387
|
+
or len(metadata.nonce) != AES_NONCE_LEN
|
|
388
|
+
or len(metadata.mac) != AES_MAC_LEN
|
|
389
|
+
):
|
|
390
|
+
raise error.DecryptionError("Invalid data length")
|
|
391
|
+
|
|
392
|
+
symmetric_key = AesKey(symmetric_key)
|
|
393
|
+
symmetric_key.decrypt_stream(source, dest, metadata.nonce, metadata.mac, mode)
|
|
394
|
+
|
|
395
|
+
return ResponseKey(symmetric_key)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RSA 加密和签名算法.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__all__ = ["PublicKey", "PrivateKey"]
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
10
|
+
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
11
|
+
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
|
|
12
|
+
from typing_extensions import Self, Union
|
|
13
|
+
|
|
14
|
+
from .. import error
|
|
15
|
+
|
|
16
|
+
ENCRYPT_PADDING = padding.OAEP(
|
|
17
|
+
mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None
|
|
18
|
+
)
|
|
19
|
+
SIGN_PADDING = padding.PSS(
|
|
20
|
+
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
|
|
21
|
+
)
|
|
22
|
+
SIGN_HASH = hashes.SHA256()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(eq=False, frozen=True)
|
|
26
|
+
class PublicKey:
|
|
27
|
+
"""
|
|
28
|
+
RSA 公钥.
|
|
29
|
+
|
|
30
|
+
本类不含可变状态, 所有方法是线程安全的.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
public_key: RSAPublicKey
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_public_pem(cls, public_pem: Union[str, bytes]) -> Self:
|
|
37
|
+
"""
|
|
38
|
+
读取 PEM 格式的 RSA 公钥.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
private_pem: PEM 格式的 RSA 公钥.
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(public_pem, str):
|
|
44
|
+
public_pem = public_pem.encode("utf-8")
|
|
45
|
+
try:
|
|
46
|
+
public_key = serialization.load_pem_public_key(public_pem)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise error.ParamError("public_pem", "Invalid RSA public key") from e
|
|
49
|
+
if not isinstance(public_key, RSAPublicKey):
|
|
50
|
+
raise error.ParamError("public_pem", "Key is not RSA")
|
|
51
|
+
|
|
52
|
+
return cls(public_key)
|
|
53
|
+
|
|
54
|
+
def encrypt(self, plaintext: bytes) -> bytes:
|
|
55
|
+
"""
|
|
56
|
+
RSA 加密.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
EncryptionError:
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
return self.public_key.encrypt(plaintext, ENCRYPT_PADDING)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise error.EncryptionError("RSA encryption failed") from e
|
|
65
|
+
|
|
66
|
+
def public_pem(self) -> str:
|
|
67
|
+
"""
|
|
68
|
+
获取 PEM 格式的公钥.
|
|
69
|
+
"""
|
|
70
|
+
return self.public_key.public_bytes(
|
|
71
|
+
encoding=serialization.Encoding.PEM,
|
|
72
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
73
|
+
).decode("utf-8")
|
|
74
|
+
|
|
75
|
+
def verify(self, signature: bytes, message: Union[str, bytes]) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
公钥验签.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
SignatureError:
|
|
81
|
+
"""
|
|
82
|
+
if isinstance(message, str):
|
|
83
|
+
message = message.encode("utf-8")
|
|
84
|
+
try:
|
|
85
|
+
self.public_key.verify(signature, message, SIGN_PADDING, SIGN_HASH)
|
|
86
|
+
return True
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(e)
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(init=False, eq=False, frozen=True)
|
|
93
|
+
class PrivateKey(PublicKey):
|
|
94
|
+
"""
|
|
95
|
+
RSA 密钥对.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
private_key: RSAPrivateKey
|
|
99
|
+
|
|
100
|
+
def __init__(self, private_key: RSAPrivateKey):
|
|
101
|
+
# Workaround frozen class
|
|
102
|
+
object.__setattr__(self, "private_key", private_key)
|
|
103
|
+
object.__setattr__(self, "public_key", private_key.public_key())
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_private_pem(cls, private_pem: Union[str, bytes]) -> Self:
|
|
107
|
+
"""
|
|
108
|
+
读取 PEM 格式的 RSA 密钥对.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
private_pem: PEM 格式的 RSA 私钥.
|
|
112
|
+
"""
|
|
113
|
+
if isinstance(private_pem, str):
|
|
114
|
+
private_pem = private_pem.encode("utf-8")
|
|
115
|
+
private_key = serialization.load_pem_private_key(private_pem, password=None)
|
|
116
|
+
if not isinstance(private_key, RSAPrivateKey):
|
|
117
|
+
raise error.ParamError("private_pem", "Key is not RSA")
|
|
118
|
+
|
|
119
|
+
return cls(private_key)
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def generate(cls) -> Self:
|
|
123
|
+
"""
|
|
124
|
+
随机生成密钥对.
|
|
125
|
+
"""
|
|
126
|
+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
|
|
127
|
+
return cls(private_key)
|
|
128
|
+
|
|
129
|
+
def private_pem(self) -> str:
|
|
130
|
+
"""
|
|
131
|
+
获取 PEM 格式的私钥.
|
|
132
|
+
"""
|
|
133
|
+
return self.private_key.private_bytes(
|
|
134
|
+
encoding=serialization.Encoding.PEM,
|
|
135
|
+
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
136
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
137
|
+
).decode("utf-8")
|
|
138
|
+
|
|
139
|
+
def decrypt(self, plaintext: bytes) -> bytes:
|
|
140
|
+
"""
|
|
141
|
+
RSA 解密.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
DecryptionError:
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
return self.private_key.decrypt(plaintext, ENCRYPT_PADDING)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
raise error.DecryptionError("RSA decryption failed") from e
|
|
150
|
+
|
|
151
|
+
def sign(self, message: Union[str, bytes]) -> bytes:
|
|
152
|
+
"""
|
|
153
|
+
私钥签名.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
SignatureError:
|
|
157
|
+
"""
|
|
158
|
+
if isinstance(message, str):
|
|
159
|
+
message = message.encode("utf-8")
|
|
160
|
+
try:
|
|
161
|
+
signature = self.private_key.sign(message, SIGN_PADDING, SIGN_HASH)
|
|
162
|
+
return signature
|
|
163
|
+
except Exception as e:
|
|
164
|
+
raise error.SignatureError("RSA signing failed") from e
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
安全通信错误类型.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
from typing_extensions import Literal, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ErrorKind(Enum):
|
|
12
|
+
CONFIG = "config"
|
|
13
|
+
PARAM = "param"
|
|
14
|
+
|
|
15
|
+
NETWORK = "network"
|
|
16
|
+
SERVICE = "service"
|
|
17
|
+
|
|
18
|
+
ENCRYPTION = "encryption"
|
|
19
|
+
DECRYPTION = "decryption"
|
|
20
|
+
SIGNATURE = "signature"
|
|
21
|
+
KEY_MISSING = "key_missing"
|
|
22
|
+
KEY_EXPIRED = "key_expired"
|
|
23
|
+
NONCE_REUSED = "nonce_reused"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SecureChannelError(Exception):
|
|
27
|
+
kind: ErrorKind
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ConfigError(SecureChannelError):
|
|
32
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.CONFIG)
|
|
33
|
+
|
|
34
|
+
config_object: object
|
|
35
|
+
message: str
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
return f"{self.config_object}: {self.message}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ParamError(SecureChannelError):
|
|
43
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.PARAM)
|
|
44
|
+
|
|
45
|
+
param: str
|
|
46
|
+
message: str
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return f"{self.param}: {self.message}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class NetworkError(SecureChannelError):
|
|
54
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.NETWORK)
|
|
55
|
+
|
|
56
|
+
service_name: Literal["EPS", "RA", "RAS", "TKS"]
|
|
57
|
+
service: str
|
|
58
|
+
endpoint: Optional[str]
|
|
59
|
+
|
|
60
|
+
def _service(self) -> str:
|
|
61
|
+
if self.endpoint is None:
|
|
62
|
+
return f"{self.service_name} service {self.service}"
|
|
63
|
+
else:
|
|
64
|
+
return f"endpoint {self.endpoint} of {self.service_name} service {self.service}"
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str:
|
|
67
|
+
return f"Cannot access {self._service()}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ServiceError(NetworkError):
|
|
72
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.SERVICE)
|
|
73
|
+
|
|
74
|
+
message: str
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
return f"{self._service().capitalize()} returned error: {self.message}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class EncryptionError(SecureChannelError):
|
|
82
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.ENCRYPTION)
|
|
83
|
+
|
|
84
|
+
message: str
|
|
85
|
+
|
|
86
|
+
def __str__(self) -> str:
|
|
87
|
+
return self.message
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class DecryptionError(SecureChannelError):
|
|
92
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.DECRYPTION)
|
|
93
|
+
|
|
94
|
+
message: str
|
|
95
|
+
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
return self.message
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class SignatureError(SecureChannelError):
|
|
102
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.SIGNATURE)
|
|
103
|
+
|
|
104
|
+
message: str
|
|
105
|
+
|
|
106
|
+
def __str__(self) -> str:
|
|
107
|
+
return self.message
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class KeyMissingError(SecureChannelError):
|
|
112
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.KEY_MISSING)
|
|
113
|
+
|
|
114
|
+
def __str__(self) -> str:
|
|
115
|
+
return "Can not get key, Please check your config !"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class KeyExpiredError(SecureChannelError):
|
|
120
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.KEY_EXPIRED)
|
|
121
|
+
|
|
122
|
+
def __str__(self) -> str:
|
|
123
|
+
return "Key has expired"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class NonceReusedError(SecureChannelError):
|
|
128
|
+
kind: ErrorKind = field(init=False, repr=False, default=ErrorKind.NONCE_REUSED)
|
|
129
|
+
|
|
130
|
+
def __str__(self) -> str:
|
|
131
|
+
return "Nonce is reused"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"DEBUG",
|
|
3
|
+
"INFO",
|
|
4
|
+
"WARNING",
|
|
5
|
+
"ERROR",
|
|
6
|
+
"CRITICAL",
|
|
7
|
+
"FORMAT",
|
|
8
|
+
"LogConfig",
|
|
9
|
+
"debug",
|
|
10
|
+
"info",
|
|
11
|
+
"warning",
|
|
12
|
+
"error",
|
|
13
|
+
"critical",
|
|
14
|
+
"init_log_config",
|
|
15
|
+
]
|
|
16
|
+
from .config import DEBUG, INFO, WARNING, ERROR, CRITICAL, FORMAT, LogConfig
|
|
17
|
+
from .logger import init_log_config, info, debug, warning, error, critical
|
|
18
|
+
|
|
19
|
+
_initialized = False
|
|
20
|
+
if not _initialized:
|
|
21
|
+
config = LogConfig()
|
|
22
|
+
init_log_config(config)
|
|
23
|
+
_initialized = True
|