otdf-python 0.1.9__py3-none-any.whl → 0.3.0__py3-none-any.whl

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