otdf-python 0.1.10__py3-none-any.whl → 0.3.5__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.
- otdf_python/__init__.py +25 -0
- otdf_python/__main__.py +12 -0
- otdf_python/address_normalizer.py +84 -0
- otdf_python/aesgcm.py +55 -0
- otdf_python/assertion_config.py +84 -0
- otdf_python/asym_crypto.py +198 -0
- otdf_python/auth_headers.py +33 -0
- otdf_python/autoconfigure_utils.py +113 -0
- otdf_python/cli.py +569 -0
- otdf_python/collection_store.py +41 -0
- otdf_python/collection_store_impl.py +22 -0
- otdf_python/config.py +69 -0
- otdf_python/connect_client.py +0 -0
- otdf_python/constants.py +1 -0
- otdf_python/crypto_utils.py +78 -0
- otdf_python/dpop.py +81 -0
- otdf_python/ecc_constants.py +176 -0
- otdf_python/ecc_mode.py +83 -0
- otdf_python/ecdh.py +317 -0
- otdf_python/eckeypair.py +75 -0
- otdf_python/header.py +181 -0
- otdf_python/invalid_zip_exception.py +8 -0
- otdf_python/kas_client.py +709 -0
- otdf_python/kas_connect_rpc_client.py +213 -0
- otdf_python/kas_info.py +25 -0
- otdf_python/kas_key_cache.py +52 -0
- otdf_python/key_type.py +31 -0
- otdf_python/key_type_constants.py +43 -0
- otdf_python/manifest.py +215 -0
- otdf_python/nanotdf.py +863 -0
- otdf_python/nanotdf_ecdsa_struct.py +132 -0
- otdf_python/nanotdf_type.py +43 -0
- otdf_python/policy_binding_serializer.py +39 -0
- otdf_python/policy_info.py +55 -0
- otdf_python/policy_object.py +22 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +172 -0
- otdf_python/sdk.py +436 -0
- otdf_python/sdk_builder.py +416 -0
- otdf_python/sdk_exceptions.py +16 -0
- otdf_python/symmetric_and_payload_config.py +30 -0
- otdf_python/tdf.py +480 -0
- otdf_python/tdf_reader.py +153 -0
- otdf_python/tdf_writer.py +23 -0
- otdf_python/token_source.py +34 -0
- otdf_python/version.py +57 -0
- otdf_python/zip_reader.py +47 -0
- otdf_python/zip_writer.py +70 -0
- otdf_python-0.3.5.dist-info/METADATA +153 -0
- otdf_python-0.3.5.dist-info/RECORD +137 -0
- {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info}/WHEEL +1 -2
- {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info/licenses}/LICENSE +1 -1
- otdf_python_proto/__init__.py +37 -0
- otdf_python_proto/authorization/__init__.py +1 -0
- otdf_python_proto/authorization/authorization_pb2.py +80 -0
- otdf_python_proto/authorization/authorization_pb2.pyi +161 -0
- otdf_python_proto/authorization/authorization_pb2_connect.py +191 -0
- otdf_python_proto/authorization/v2/authorization_pb2.py +105 -0
- otdf_python_proto/authorization/v2/authorization_pb2.pyi +134 -0
- otdf_python_proto/authorization/v2/authorization_pb2_connect.py +233 -0
- otdf_python_proto/common/__init__.py +1 -0
- otdf_python_proto/common/common_pb2.py +52 -0
- otdf_python_proto/common/common_pb2.pyi +61 -0
- otdf_python_proto/entity/__init__.py +1 -0
- otdf_python_proto/entity/entity_pb2.py +47 -0
- otdf_python_proto/entity/entity_pb2.pyi +50 -0
- otdf_python_proto/entityresolution/__init__.py +1 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.py +57 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.py +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/kas/__init__.py +9 -0
- otdf_python_proto/kas/kas_pb2.py +103 -0
- otdf_python_proto/kas/kas_pb2.pyi +170 -0
- otdf_python_proto/kas/kas_pb2_connect.py +192 -0
- otdf_python_proto/legacy_grpc/__init__.py +1 -0
- otdf_python_proto/legacy_grpc/authorization/authorization_pb2_grpc.py +163 -0
- otdf_python_proto/legacy_grpc/authorization/v2/authorization_pb2_grpc.py +206 -0
- otdf_python_proto/legacy_grpc/common/common_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entity/entity_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entityresolution/entity_resolution_pb2_grpc.py +122 -0
- otdf_python_proto/legacy_grpc/entityresolution/v2/entity_resolution_pb2_grpc.py +120 -0
- otdf_python_proto/legacy_grpc/kas/kas_pb2_grpc.py +172 -0
- otdf_python_proto/legacy_grpc/logger/audit/test_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/actions/actions_pb2_grpc.py +249 -0
- otdf_python_proto/legacy_grpc/policy/attributes/attributes_pb2_grpc.py +873 -0
- otdf_python_proto/legacy_grpc/policy/kasregistry/key_access_server_registry_pb2_grpc.py +602 -0
- otdf_python_proto/legacy_grpc/policy/keymanagement/key_management_pb2_grpc.py +251 -0
- otdf_python_proto/legacy_grpc/policy/namespaces/namespaces_pb2_grpc.py +427 -0
- otdf_python_proto/legacy_grpc/policy/objects_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/registeredresources/registered_resources_pb2_grpc.py +524 -0
- otdf_python_proto/legacy_grpc/policy/resourcemapping/resource_mapping_pb2_grpc.py +516 -0
- otdf_python_proto/legacy_grpc/policy/selectors_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/subjectmapping/subject_mapping_pb2_grpc.py +551 -0
- otdf_python_proto/legacy_grpc/policy/unsafe/unsafe_pb2_grpc.py +485 -0
- otdf_python_proto/legacy_grpc/wellknownconfiguration/wellknown_configuration_pb2_grpc.py +77 -0
- otdf_python_proto/logger/__init__.py +1 -0
- otdf_python_proto/logger/audit/test_pb2.py +43 -0
- otdf_python_proto/logger/audit/test_pb2.pyi +45 -0
- otdf_python_proto/policy/__init__.py +1 -0
- otdf_python_proto/policy/actions/actions_pb2.py +75 -0
- otdf_python_proto/policy/actions/actions_pb2.pyi +87 -0
- otdf_python_proto/policy/actions/actions_pb2_connect.py +275 -0
- otdf_python_proto/policy/attributes/attributes_pb2.py +234 -0
- otdf_python_proto/policy/attributes/attributes_pb2.pyi +328 -0
- otdf_python_proto/policy/attributes/attributes_pb2_connect.py +863 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.py +266 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.pyi +450 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +611 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.py +79 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.pyi +87 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +275 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.py +117 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +147 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +443 -0
- otdf_python_proto/policy/objects_pb2.py +150 -0
- otdf_python_proto/policy/objects_pb2.pyi +464 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.py +139 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.pyi +196 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +527 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.py +139 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.pyi +194 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +527 -0
- otdf_python_proto/policy/selectors_pb2.py +57 -0
- otdf_python_proto/policy/selectors_pb2.pyi +90 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.py +127 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.pyi +189 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +569 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.py +113 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +145 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +485 -0
- otdf_python_proto/wellknownconfiguration/__init__.py +1 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +51 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +32 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +107 -0
- otdf_python/_gotdf_python.cpython-312-darwin.so +0 -0
- otdf_python/build.py +0 -190
- otdf_python/go.py +0 -1478
- otdf_python/gotdf_python.py +0 -383
- otdf_python-0.1.10.dist-info/METADATA +0 -149
- otdf_python-0.1.10.dist-info/RECORD +0 -10
- otdf_python-0.1.10.dist-info/top_level.txt +0 -1
otdf_python/sdk.py
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python port of the main SDK class for OpenTDF platform interaction.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from contextlib import AbstractContextManager
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from typing import Any, BinaryIO
|
|
8
|
+
|
|
9
|
+
from otdf_python.config import KASInfo, NanoTDFConfig, TDFConfig
|
|
10
|
+
from otdf_python.nanotdf import NanoTDF
|
|
11
|
+
from otdf_python.sdk_exceptions import SDKException
|
|
12
|
+
from otdf_python.tdf import TDF, TDFReader, TDFReaderConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class KAS(AbstractContextManager):
|
|
16
|
+
"""
|
|
17
|
+
KAS (Key Access Service) interface to define methods related to key access and management.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def get_public_key(self, kas_info: Any) -> Any:
|
|
21
|
+
"""
|
|
22
|
+
Retrieves the public key from the KAS for RSA operations.
|
|
23
|
+
If the public key is cached, returns the cached value.
|
|
24
|
+
Otherwise, makes a request to the KAS.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
kas_info: KASInfo object containing the URL and algorithm
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Updated KASInfo object with KID and PublicKey populated
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
SDKException: If there's an error retrieving the public key
|
|
34
|
+
"""
|
|
35
|
+
# Delegate to the underlying KAS client which handles authentication properly
|
|
36
|
+
return self._kas_client.get_public_key(kas_info)
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
platform_url=None,
|
|
41
|
+
token_source=None,
|
|
42
|
+
sdk_ssl_verify=True,
|
|
43
|
+
use_plaintext=False,
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the KAS client
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
platform_url: URL of the platform
|
|
50
|
+
token_source: Function that returns an authentication token
|
|
51
|
+
sdk_ssl_verify: Whether to verify SSL certificates
|
|
52
|
+
use_plaintext: Whether to use plaintext HTTP connections instead of HTTPS
|
|
53
|
+
"""
|
|
54
|
+
from .kas_client import KASClient
|
|
55
|
+
|
|
56
|
+
self._kas_client = KASClient(
|
|
57
|
+
kas_url=platform_url,
|
|
58
|
+
token_source=token_source,
|
|
59
|
+
verify_ssl=sdk_ssl_verify,
|
|
60
|
+
use_plaintext=use_plaintext,
|
|
61
|
+
)
|
|
62
|
+
# Store the parameters for potential use
|
|
63
|
+
self._sdk_ssl_verify = sdk_ssl_verify
|
|
64
|
+
self._use_plaintext = use_plaintext
|
|
65
|
+
|
|
66
|
+
def get_ec_public_key(self, kas_info: Any, curve: Any) -> Any:
|
|
67
|
+
"""
|
|
68
|
+
Retrieves the EC public key from the KAS.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
kas_info: KASInfo object containing the URL
|
|
72
|
+
curve: The EC curve to use
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Updated KASInfo object with KID and PublicKey populated
|
|
76
|
+
"""
|
|
77
|
+
# Set algorithm to "ec:<curve>"
|
|
78
|
+
from copy import copy
|
|
79
|
+
|
|
80
|
+
kas_info_copy = copy(kas_info)
|
|
81
|
+
kas_info_copy.algorithm = f"ec:{curve}"
|
|
82
|
+
return self.get_public_key(kas_info_copy)
|
|
83
|
+
|
|
84
|
+
def unwrap(self, key_access: Any, policy: str, session_key_type: Any) -> bytes:
|
|
85
|
+
"""
|
|
86
|
+
Unwraps the key using the KAS.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
key_access: KeyAccess object containing the wrapped key
|
|
90
|
+
policy: Policy JSON string
|
|
91
|
+
session_key_type: Type of session key (RSA, EC)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Unwrapped key as bytes
|
|
95
|
+
"""
|
|
96
|
+
return self._kas_client.unwrap(key_access, policy, session_key_type)
|
|
97
|
+
|
|
98
|
+
def unwrap_nanotdf(
|
|
99
|
+
self,
|
|
100
|
+
curve: Any,
|
|
101
|
+
header: str,
|
|
102
|
+
kas_url: str,
|
|
103
|
+
wrapped_key: bytes | None = None,
|
|
104
|
+
kas_private_key: str | None = None,
|
|
105
|
+
mock: bool = False,
|
|
106
|
+
) -> bytes:
|
|
107
|
+
"""
|
|
108
|
+
Unwraps the NanoTDF key using the KAS. If mock=True, performs local unwrap using the private key (for tests).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
curve: EC curve used
|
|
112
|
+
header: NanoTDF header
|
|
113
|
+
kas_url: URL of the KAS
|
|
114
|
+
wrapped_key: Optional wrapped key bytes (for mock mode)
|
|
115
|
+
kas_private_key: Optional KAS private key (for mock mode)
|
|
116
|
+
mock: If True, unwrap locally using provided private key
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Unwrapped key as bytes
|
|
120
|
+
"""
|
|
121
|
+
if mock and wrapped_key and kas_private_key:
|
|
122
|
+
from .asym_crypto import AsymDecryption
|
|
123
|
+
|
|
124
|
+
asym = AsymDecryption(private_key_pem=kas_private_key)
|
|
125
|
+
return asym.decrypt(wrapped_key)
|
|
126
|
+
|
|
127
|
+
# This would be implemented using nanotdf-specific logic
|
|
128
|
+
raise NotImplementedError("KAS unwrap_nanotdf not implemented.")
|
|
129
|
+
|
|
130
|
+
def get_key_cache(self) -> Any:
|
|
131
|
+
"""
|
|
132
|
+
Returns the KAS key cache.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The KAS key cache object
|
|
136
|
+
"""
|
|
137
|
+
return self._kas_client.get_key_cache()
|
|
138
|
+
|
|
139
|
+
def close(self):
|
|
140
|
+
"""Closes resources associated with the KAS interface"""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
144
|
+
self.close()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class SDK(AbstractContextManager):
|
|
148
|
+
def new_tdf_config(
|
|
149
|
+
self,
|
|
150
|
+
attributes: list[str] | None = None,
|
|
151
|
+
kas_info_list: list[KASInfo] | None = None,
|
|
152
|
+
**kwargs,
|
|
153
|
+
) -> TDFConfig:
|
|
154
|
+
"""
|
|
155
|
+
Create a TDFConfig with default kas_info_list from the SDK's platform_url.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
if self.platform_url is None:
|
|
159
|
+
raise SDKException("Cannot create TDFConfig: SDK platform_url is not set.")
|
|
160
|
+
|
|
161
|
+
# Get use_plaintext setting - allow override via kwargs, fall back to SDK setting
|
|
162
|
+
use_plaintext = kwargs.pop(
|
|
163
|
+
"use_plaintext", getattr(self, "_use_plaintext", False)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Construct proper KAS URL by appending /kas to platform URL, like Java SDK
|
|
167
|
+
# Include explicit port for HTTPS to match otdfctl behavior
|
|
168
|
+
from urllib.parse import urlparse
|
|
169
|
+
|
|
170
|
+
parsed_url = urlparse(self.platform_url)
|
|
171
|
+
|
|
172
|
+
# Determine scheme and default port based on use_plaintext setting
|
|
173
|
+
if use_plaintext:
|
|
174
|
+
target_scheme = "http"
|
|
175
|
+
default_port = 80
|
|
176
|
+
else:
|
|
177
|
+
target_scheme = "https"
|
|
178
|
+
default_port = 443
|
|
179
|
+
|
|
180
|
+
# Use the original scheme if it exists, otherwise apply target_scheme
|
|
181
|
+
# This preserves the platform URL's scheme when it's already appropriate
|
|
182
|
+
original_scheme = parsed_url.scheme
|
|
183
|
+
if original_scheme in ("http", "https"):
|
|
184
|
+
# If platform URL already has a scheme, check if it's compatible with use_plaintext
|
|
185
|
+
if use_plaintext and original_scheme == "http":
|
|
186
|
+
scheme = "http"
|
|
187
|
+
elif not use_plaintext and original_scheme == "https":
|
|
188
|
+
scheme = "https"
|
|
189
|
+
else:
|
|
190
|
+
# Scheme conflicts with use_plaintext setting, apply target_scheme
|
|
191
|
+
scheme = target_scheme
|
|
192
|
+
else:
|
|
193
|
+
# No scheme or unknown scheme, apply target_scheme
|
|
194
|
+
scheme = target_scheme
|
|
195
|
+
|
|
196
|
+
# Handle URL construction with proper scheme and port
|
|
197
|
+
if parsed_url.port is None:
|
|
198
|
+
# Add explicit port if not present
|
|
199
|
+
kas_url = f"{scheme}://{parsed_url.hostname}:{default_port}{parsed_url.path.rstrip('/')}/kas"
|
|
200
|
+
else:
|
|
201
|
+
# Use existing port with the determined scheme
|
|
202
|
+
kas_url = f"{scheme}://{parsed_url.hostname}:{parsed_url.port}{parsed_url.path.rstrip('/')}/kas"
|
|
203
|
+
|
|
204
|
+
if kas_info_list is None:
|
|
205
|
+
kas_info = KASInfo(url=kas_url, default=True)
|
|
206
|
+
kas_info_list = [kas_info]
|
|
207
|
+
return TDFConfig(
|
|
208
|
+
kas_info_list=kas_info_list, attributes=attributes or [], **kwargs
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
Main SDK class for interacting with the OpenTDF platform.
|
|
213
|
+
Provides various services for TDF/NanoTDF operations and platform API calls.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
class Services(AbstractContextManager):
|
|
217
|
+
"""
|
|
218
|
+
The Services interface provides access to various platform service clients and KAS.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def kas(self) -> KAS:
|
|
222
|
+
"""
|
|
223
|
+
Returns the KAS client for key access operations.
|
|
224
|
+
This should be implemented to return an instance of KAS.
|
|
225
|
+
"""
|
|
226
|
+
raise NotImplementedError
|
|
227
|
+
|
|
228
|
+
def close(self):
|
|
229
|
+
"""Closes resources associated with the services"""
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
233
|
+
self.close()
|
|
234
|
+
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
services: "SDK.Services",
|
|
238
|
+
platform_url: str | None = None,
|
|
239
|
+
ssl_verify: bool = True,
|
|
240
|
+
use_plaintext: bool = False,
|
|
241
|
+
):
|
|
242
|
+
"""
|
|
243
|
+
Initializes a new SDK instance.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
services: The services interface implementation
|
|
247
|
+
platform_url: Optional platform base URL
|
|
248
|
+
ssl_verify: Whether to verify SSL certificates (default: True)
|
|
249
|
+
use_plaintext: Whether to use HTTP instead of HTTPS (default: False)
|
|
250
|
+
"""
|
|
251
|
+
self.services = services
|
|
252
|
+
self.platform_url = platform_url
|
|
253
|
+
self.ssl_verify = ssl_verify
|
|
254
|
+
self._use_plaintext = use_plaintext
|
|
255
|
+
|
|
256
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
257
|
+
"""Clean up resources when exiting context manager"""
|
|
258
|
+
self.close()
|
|
259
|
+
|
|
260
|
+
def close(self):
|
|
261
|
+
"""Close the SDK and release resources"""
|
|
262
|
+
if hasattr(self.services, "close"):
|
|
263
|
+
self.services.close()
|
|
264
|
+
|
|
265
|
+
def get_services(self) -> "SDK.Services":
|
|
266
|
+
"""Returns the services interface"""
|
|
267
|
+
return self.services
|
|
268
|
+
|
|
269
|
+
def get_platform_url(self) -> str | None:
|
|
270
|
+
"""Returns the platform URL if set"""
|
|
271
|
+
return self.platform_url
|
|
272
|
+
|
|
273
|
+
def load_tdf(
|
|
274
|
+
self,
|
|
275
|
+
tdf_data: bytes | BinaryIO | BytesIO,
|
|
276
|
+
config: TDFReaderConfig | None = None,
|
|
277
|
+
) -> TDFReader:
|
|
278
|
+
"""
|
|
279
|
+
Loads a TDF from the provided data, optionally according to the config.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
tdf_data: The TDF data as bytes, file object, or BytesIO
|
|
283
|
+
config: TDFReaderConfig dataclass
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
TDFReader: Contains payload and manifest
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
SDKException: If there's an error loading the TDF
|
|
290
|
+
"""
|
|
291
|
+
tdf = TDF(self.services)
|
|
292
|
+
if config is None:
|
|
293
|
+
config = TDFReaderConfig()
|
|
294
|
+
|
|
295
|
+
return tdf.load_tdf(tdf_data, config)
|
|
296
|
+
|
|
297
|
+
def create_tdf(
|
|
298
|
+
self,
|
|
299
|
+
payload: bytes | BinaryIO | BytesIO,
|
|
300
|
+
config,
|
|
301
|
+
output_stream: BinaryIO | None = None,
|
|
302
|
+
):
|
|
303
|
+
"""
|
|
304
|
+
Creates a TDF with the provided payload.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
payload: The payload data as bytes, file object, or BytesIO
|
|
308
|
+
config: TDFConfig dataclass from config.py
|
|
309
|
+
output_stream: The output stream to write the TDF to
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Manifest, size, output_stream
|
|
313
|
+
|
|
314
|
+
Raises:
|
|
315
|
+
SDKException: If there's an error creating the TDF
|
|
316
|
+
"""
|
|
317
|
+
tdf = TDF(self.services)
|
|
318
|
+
return tdf.create_tdf(payload, config, output_stream)
|
|
319
|
+
|
|
320
|
+
def create_nano_tdf(
|
|
321
|
+
self, payload: bytes | BytesIO, output_stream: BinaryIO, config: "NanoTDFConfig"
|
|
322
|
+
) -> int:
|
|
323
|
+
"""
|
|
324
|
+
Creates a NanoTDF with the provided payload.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
payload: The payload data as bytes or BytesIO
|
|
328
|
+
output_stream: The output stream to write the NanoTDF to
|
|
329
|
+
config: NanoTDFConfig for the NanoTDF creation
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
int: The size of the created NanoTDF
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
SDKException: If there's an error creating the NanoTDF
|
|
336
|
+
"""
|
|
337
|
+
nano_tdf = NanoTDF(self.services)
|
|
338
|
+
return nano_tdf.create_nano_tdf(payload, output_stream, config)
|
|
339
|
+
|
|
340
|
+
def read_nano_tdf(
|
|
341
|
+
self,
|
|
342
|
+
nano_tdf_data: bytes | BytesIO,
|
|
343
|
+
output_stream: BinaryIO,
|
|
344
|
+
config: NanoTDFConfig,
|
|
345
|
+
) -> None:
|
|
346
|
+
"""
|
|
347
|
+
Reads a NanoTDF and writes the payload to the output stream.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
nano_tdf_data: The NanoTDF data as bytes or BytesIO
|
|
351
|
+
output_stream: The output stream to write the payload to
|
|
352
|
+
config: NanoTDFConfig configuration for the NanoTDF reader
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
SDKException: If there's an error reading the NanoTDF
|
|
356
|
+
"""
|
|
357
|
+
nano_tdf = NanoTDF(self.services)
|
|
358
|
+
nano_tdf.read_nano_tdf(nano_tdf_data, output_stream, config)
|
|
359
|
+
|
|
360
|
+
@staticmethod
|
|
361
|
+
def is_tdf(data: bytes | BinaryIO) -> bool:
|
|
362
|
+
"""
|
|
363
|
+
Checks if the provided data is a TDF.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
data: The data to check
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
bool: True if the data is a TDF, False otherwise
|
|
370
|
+
"""
|
|
371
|
+
import zipfile
|
|
372
|
+
from io import BytesIO
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
file_like = BytesIO(data) if isinstance(data, bytes | bytearray) else data
|
|
376
|
+
with zipfile.ZipFile(file_like) as zf:
|
|
377
|
+
names = set(zf.namelist())
|
|
378
|
+
return {"0.manifest.json", "0.payload"}.issubset(names) and len(
|
|
379
|
+
names
|
|
380
|
+
) == 2
|
|
381
|
+
except Exception:
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
# Exception classes - SDK-specific exceptions that can occur during operations
|
|
385
|
+
class SplitKeyException(SDKException):
|
|
386
|
+
"""Thrown when the SDK encounters an error related to split key operations"""
|
|
387
|
+
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
class DataSizeNotSupported(SDKException):
|
|
391
|
+
"""Thrown when the user attempts to create a TDF with a size larger than the maximum size"""
|
|
392
|
+
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
class KasInfoMissing(SDKException):
|
|
396
|
+
"""Thrown during TDF creation when no KAS information is present"""
|
|
397
|
+
|
|
398
|
+
pass
|
|
399
|
+
|
|
400
|
+
class KasPublicKeyMissing(SDKException):
|
|
401
|
+
"""Thrown during encryption when the SDK cannot retrieve the public key for a KAS"""
|
|
402
|
+
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
class TamperException(SDKException):
|
|
406
|
+
"""Base class for exceptions related to signature mismatches"""
|
|
407
|
+
|
|
408
|
+
def __init__(self, error_message: str):
|
|
409
|
+
super().__init__(f"[tamper detected] {error_message}")
|
|
410
|
+
|
|
411
|
+
class RootSignatureValidationException(TamperException):
|
|
412
|
+
"""Thrown when the root signature validation fails"""
|
|
413
|
+
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
class SegmentSignatureMismatch(TamperException):
|
|
417
|
+
"""Thrown when a segment signature does not match the expected value"""
|
|
418
|
+
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
class KasBadRequestException(SDKException):
|
|
422
|
+
"""Thrown when the KAS returns a bad request response"""
|
|
423
|
+
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
class KasAllowlistException(SDKException):
|
|
427
|
+
"""Thrown when the KAS allowlist check fails"""
|
|
428
|
+
|
|
429
|
+
pass
|
|
430
|
+
|
|
431
|
+
class AssertionException(SDKException):
|
|
432
|
+
"""Thrown when an assertion validation fails"""
|
|
433
|
+
|
|
434
|
+
def __init__(self, error_message: str, assertion_id: str):
|
|
435
|
+
super().__init__(error_message)
|
|
436
|
+
self.assertion_id = assertion_id
|