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.
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 +198 -0
  7. otdf_python/auth_headers.py +33 -0
  8. otdf_python/autoconfigure_utils.py +113 -0
  9. otdf_python/cli.py +569 -0
  10. otdf_python/collection_store.py +41 -0
  11. otdf_python/collection_store_impl.py +22 -0
  12. otdf_python/config.py +69 -0
  13. otdf_python/connect_client.py +0 -0
  14. otdf_python/constants.py +1 -0
  15. otdf_python/crypto_utils.py +78 -0
  16. otdf_python/dpop.py +81 -0
  17. otdf_python/ecc_constants.py +176 -0
  18. otdf_python/ecc_mode.py +83 -0
  19. otdf_python/ecdh.py +317 -0
  20. otdf_python/eckeypair.py +75 -0
  21. otdf_python/header.py +181 -0
  22. otdf_python/invalid_zip_exception.py +8 -0
  23. otdf_python/kas_client.py +709 -0
  24. otdf_python/kas_connect_rpc_client.py +213 -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 +863 -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 +55 -0
  35. otdf_python/policy_object.py +22 -0
  36. otdf_python/policy_stub.py +2 -0
  37. otdf_python/resource_locator.py +172 -0
  38. otdf_python/sdk.py +436 -0
  39. otdf_python/sdk_builder.py +416 -0
  40. otdf_python/sdk_exceptions.py +16 -0
  41. otdf_python/symmetric_and_payload_config.py +30 -0
  42. otdf_python/tdf.py +480 -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.5.dist-info/METADATA +153 -0
  50. otdf_python-0.3.5.dist-info/RECORD +137 -0
  51. {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info}/WHEEL +1 -2
  52. {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.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.10.dist-info/METADATA +0 -149
  143. otdf_python-0.1.10.dist-info/RECORD +0 -10
  144. 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