otdf-python 0.3.0__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/asym_crypto.py +135 -22
- otdf_python/auth_headers.py +13 -1
- otdf_python/cli.py +20 -21
- otdf_python/ecc_constants.py +176 -0
- otdf_python/ecc_mode.py +60 -9
- otdf_python/ecdh.py +317 -0
- otdf_python/header.py +40 -2
- otdf_python/kas_client.py +172 -66
- otdf_python/kas_connect_rpc_client.py +7 -1
- otdf_python/nanotdf.py +445 -135
- otdf_python/policy_info.py +5 -28
- otdf_python/resource_locator.py +149 -21
- otdf_python/sdk.py +15 -107
- otdf_python/sdk_builder.py +5 -37
- otdf_python/tdf.py +4 -3
- {otdf_python-0.3.0.dist-info → otdf_python-0.3.5.dist-info}/METADATA +6 -84
- {otdf_python-0.3.0.dist-info → otdf_python-0.3.5.dist-info}/RECORD +19 -19
- otdf_python/asym_decryption.py +0 -53
- otdf_python/asym_encryption.py +0 -75
- {otdf_python-0.3.0.dist-info → otdf_python-0.3.5.dist-info}/WHEEL +0 -0
- {otdf_python-0.3.0.dist-info → otdf_python-0.3.5.dist-info}/licenses/LICENSE +0 -0
otdf_python/policy_info.py
CHANGED
|
@@ -2,14 +2,10 @@ class PolicyInfo:
|
|
|
2
2
|
def __init__(
|
|
3
3
|
self,
|
|
4
4
|
policy_type: int = 0,
|
|
5
|
-
has_ecdsa_binding: bool = False,
|
|
6
5
|
body: bytes | None = None,
|
|
7
|
-
binding: bytes | None = None,
|
|
8
6
|
):
|
|
9
7
|
self.policy_type = policy_type
|
|
10
|
-
self.has_ecdsa_binding = has_ecdsa_binding
|
|
11
8
|
self.body = body
|
|
12
|
-
self.binding = binding
|
|
13
9
|
|
|
14
10
|
def set_embedded_plain_text_policy(self, body: bytes):
|
|
15
11
|
self.body = body
|
|
@@ -19,21 +15,13 @@ class PolicyInfo:
|
|
|
19
15
|
self.body = body
|
|
20
16
|
self.policy_type = 2 # Placeholder for EMBEDDED_POLICY_ENCRYPTED
|
|
21
17
|
|
|
22
|
-
def set_policy_binding(self, binding: bytes):
|
|
23
|
-
self.binding = binding
|
|
24
|
-
|
|
25
18
|
def get_body(self) -> bytes | None:
|
|
26
19
|
return self.body
|
|
27
20
|
|
|
28
|
-
def get_binding(self) -> bytes | None:
|
|
29
|
-
return self.binding
|
|
30
|
-
|
|
31
21
|
def get_total_size(self) -> int:
|
|
32
22
|
size = 1 # policy_type
|
|
33
23
|
size += 2 # body_len
|
|
34
24
|
size += len(self.body) if self.body else 0
|
|
35
|
-
size += 1 # binding_len
|
|
36
|
-
size += len(self.binding) if self.binding else 0
|
|
37
25
|
return size
|
|
38
26
|
|
|
39
27
|
def write_into_buffer(self, buffer: bytearray, offset: int = 0) -> int:
|
|
@@ -46,33 +34,22 @@ class PolicyInfo:
|
|
|
46
34
|
if self.body:
|
|
47
35
|
buffer[offset : offset + body_len] = self.body
|
|
48
36
|
offset += body_len
|
|
49
|
-
binding_len = len(self.binding) if self.binding else 0
|
|
50
|
-
buffer[offset] = binding_len
|
|
51
|
-
offset += 1
|
|
52
|
-
if self.binding:
|
|
53
|
-
buffer[offset : offset + binding_len] = self.binding
|
|
54
|
-
offset += binding_len
|
|
55
37
|
return offset - start
|
|
56
38
|
|
|
57
39
|
@staticmethod
|
|
58
40
|
def from_bytes_with_size(buffer: bytes, ecc_mode):
|
|
59
|
-
#
|
|
41
|
+
# Parse policy_type (1 byte), body_len (2 bytes), body
|
|
42
|
+
# Note: binding is NOT part of PolicyInfo - it's read separately in Header
|
|
60
43
|
offset = 0
|
|
61
|
-
if len(buffer) <
|
|
44
|
+
if len(buffer) < 3:
|
|
62
45
|
raise ValueError("Buffer too short for PolicyInfo header")
|
|
63
46
|
policy_type = buffer[offset]
|
|
64
47
|
offset += 1
|
|
65
48
|
body_len = int.from_bytes(buffer[offset : offset + 2], "big")
|
|
66
49
|
offset += 2
|
|
67
|
-
if len(buffer) < offset + body_len
|
|
50
|
+
if len(buffer) < offset + body_len:
|
|
68
51
|
raise ValueError("Buffer too short for PolicyInfo body")
|
|
69
52
|
body = buffer[offset : offset + body_len]
|
|
70
53
|
offset += body_len
|
|
71
|
-
|
|
72
|
-
offset += 1
|
|
73
|
-
if len(buffer) < offset + binding_len:
|
|
74
|
-
raise ValueError("Buffer too short for PolicyInfo binding")
|
|
75
|
-
binding = buffer[offset : offset + binding_len]
|
|
76
|
-
offset += binding_len
|
|
77
|
-
pi = PolicyInfo(policy_type=policy_type, body=body, binding=binding)
|
|
54
|
+
pi = PolicyInfo(policy_type=policy_type, body=body)
|
|
78
55
|
return pi, offset
|
otdf_python/resource_locator.py
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
class ResourceLocator:
|
|
2
|
+
"""
|
|
3
|
+
NanoTDF Resource Locator per the spec:
|
|
4
|
+
https://github.com/opentdf/spec/blob/main/schema/nanotdf/README.md
|
|
5
|
+
|
|
6
|
+
Format:
|
|
7
|
+
- Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
|
|
8
|
+
- Protocol: 0x0=HTTP, 0x1=HTTPS, 0xF=Shared Resource Directory
|
|
9
|
+
- Identifier: 0x0=None, 0x1=2 bytes, 0x2=8 bytes, 0x3=32 bytes
|
|
10
|
+
- Byte 1: Body Length (1-255 bytes)
|
|
11
|
+
- Bytes 2-N: Body (URL path)
|
|
12
|
+
- Bytes N+1-M: Identifier (optional, 0/2/8/32 bytes)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Protocol enum values
|
|
16
|
+
PROTOCOL_HTTP = 0x0
|
|
17
|
+
PROTOCOL_HTTPS = 0x1
|
|
18
|
+
PROTOCOL_SHARED_RESOURCE_DIR = 0xF
|
|
19
|
+
|
|
20
|
+
# Identifier length enum values (in bits 4-7)
|
|
21
|
+
IDENTIFIER_NONE = 0x0
|
|
22
|
+
IDENTIFIER_2_BYTES = 0x1
|
|
23
|
+
IDENTIFIER_8_BYTES = 0x2
|
|
24
|
+
IDENTIFIER_32_BYTES = 0x3
|
|
25
|
+
|
|
2
26
|
def __init__(self, resource_url: str | None = None, identifier: str | None = None):
|
|
3
|
-
self.resource_url = resource_url
|
|
4
|
-
self.identifier = identifier
|
|
27
|
+
self.resource_url = resource_url or ""
|
|
28
|
+
self.identifier = identifier or ""
|
|
5
29
|
|
|
6
30
|
def get_resource_url(self):
|
|
7
31
|
return self.resource_url
|
|
@@ -9,13 +33,68 @@ class ResourceLocator:
|
|
|
9
33
|
def get_identifier(self):
|
|
10
34
|
return self.identifier
|
|
11
35
|
|
|
36
|
+
def _parse_url(self):
|
|
37
|
+
"""Parse URL to extract protocol and body (path)."""
|
|
38
|
+
url = self.resource_url
|
|
39
|
+
if url.startswith("https://"):
|
|
40
|
+
protocol = self.PROTOCOL_HTTPS
|
|
41
|
+
body = url[8:] # Remove "https://"
|
|
42
|
+
elif url.startswith("http://"):
|
|
43
|
+
protocol = self.PROTOCOL_HTTP
|
|
44
|
+
body = url[7:] # Remove "http://"
|
|
45
|
+
else:
|
|
46
|
+
# Default to HTTP
|
|
47
|
+
protocol = self.PROTOCOL_HTTP
|
|
48
|
+
body = url
|
|
49
|
+
return protocol, body.encode()
|
|
50
|
+
|
|
51
|
+
def _get_identifier_bytes(self):
|
|
52
|
+
"""Get identifier bytes and determine identifier length enum."""
|
|
53
|
+
if not self.identifier:
|
|
54
|
+
return self.IDENTIFIER_NONE, b""
|
|
55
|
+
|
|
56
|
+
id_bytes = self.identifier.encode()
|
|
57
|
+
id_len = len(id_bytes)
|
|
58
|
+
|
|
59
|
+
if id_len == 0:
|
|
60
|
+
return self.IDENTIFIER_NONE, b""
|
|
61
|
+
elif id_len <= 2:
|
|
62
|
+
# Pad to 2 bytes
|
|
63
|
+
return self.IDENTIFIER_2_BYTES, id_bytes.ljust(2, b"\x00")
|
|
64
|
+
elif id_len <= 8:
|
|
65
|
+
# Pad to 8 bytes
|
|
66
|
+
return self.IDENTIFIER_8_BYTES, id_bytes.ljust(8, b"\x00")
|
|
67
|
+
elif id_len <= 32:
|
|
68
|
+
# Pad to 32 bytes
|
|
69
|
+
return self.IDENTIFIER_32_BYTES, id_bytes.ljust(32, b"\x00")
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Identifier too long: {id_len} bytes (max 32)")
|
|
72
|
+
|
|
12
73
|
def to_bytes(self):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
74
|
+
"""
|
|
75
|
+
Convert to NanoTDF Resource Locator format per spec.
|
|
76
|
+
|
|
77
|
+
Format:
|
|
78
|
+
- Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
|
|
79
|
+
- Byte 1: Body Length
|
|
80
|
+
- Bytes 2-N: Body (URL path)
|
|
81
|
+
- Bytes N+1-M: Identifier (0/2/8/32 bytes)
|
|
82
|
+
"""
|
|
83
|
+
protocol, body_bytes = self._parse_url()
|
|
84
|
+
identifier_enum, identifier_bytes = self._get_identifier_bytes()
|
|
85
|
+
|
|
86
|
+
if len(body_bytes) > 255:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Resource Locator body too long: {len(body_bytes)} bytes (max 255)"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Byte 0: protocol in bits 0-3, identifier length in bits 4-7
|
|
92
|
+
protocol_and_id = (identifier_enum << 4) | protocol
|
|
93
|
+
|
|
94
|
+
# Byte 1: body length
|
|
95
|
+
body_len = len(body_bytes)
|
|
96
|
+
|
|
97
|
+
return bytes([protocol_and_id, body_len]) + body_bytes + identifier_bytes
|
|
19
98
|
|
|
20
99
|
def get_total_size(self) -> int:
|
|
21
100
|
return len(self.to_bytes())
|
|
@@ -26,19 +105,68 @@ class ResourceLocator:
|
|
|
26
105
|
return len(data)
|
|
27
106
|
|
|
28
107
|
@staticmethod
|
|
29
|
-
def from_bytes_with_size(buffer: bytes):
|
|
30
|
-
|
|
108
|
+
def from_bytes_with_size(buffer: bytes): # noqa: C901
|
|
109
|
+
"""
|
|
110
|
+
Parse NanoTDF Resource Locator from bytes per spec.
|
|
111
|
+
|
|
112
|
+
Format:
|
|
113
|
+
- Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
|
|
114
|
+
- Byte 1: Body Length
|
|
115
|
+
- Bytes 2-N: Body (URL path)
|
|
116
|
+
- Bytes N+1-M: Identifier (0/2/8/32 bytes)
|
|
117
|
+
"""
|
|
31
118
|
if len(buffer) < 2:
|
|
32
119
|
raise ValueError("Buffer too short for ResourceLocator")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
120
|
+
|
|
121
|
+
# Parse byte 0: protocol and identifier length
|
|
122
|
+
protocol_and_id = buffer[0]
|
|
123
|
+
protocol = protocol_and_id & 0x0F # Bits 0-3
|
|
124
|
+
identifier_enum = (protocol_and_id >> 4) & 0x0F # Bits 4-7
|
|
125
|
+
|
|
126
|
+
# Parse byte 1: body length
|
|
127
|
+
body_len = buffer[1]
|
|
128
|
+
|
|
129
|
+
if len(buffer) < 2 + body_len:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"Buffer too short for ResourceLocator body (need {2 + body_len}, have {len(buffer)})"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Parse body (URL path)
|
|
135
|
+
body_bytes = buffer[2 : 2 + body_len]
|
|
136
|
+
body = body_bytes.decode()
|
|
137
|
+
|
|
138
|
+
# Reconstruct full URL with protocol
|
|
139
|
+
if protocol == ResourceLocator.PROTOCOL_HTTPS:
|
|
140
|
+
resource_url = f"https://{body}"
|
|
141
|
+
elif protocol == ResourceLocator.PROTOCOL_HTTP:
|
|
142
|
+
resource_url = f"http://{body}"
|
|
143
|
+
else:
|
|
144
|
+
resource_url = body
|
|
145
|
+
|
|
146
|
+
# Parse identifier based on identifier_enum
|
|
147
|
+
offset = 2 + body_len
|
|
148
|
+
if identifier_enum == ResourceLocator.IDENTIFIER_NONE:
|
|
149
|
+
identifier_len = 0
|
|
150
|
+
elif identifier_enum == ResourceLocator.IDENTIFIER_2_BYTES:
|
|
151
|
+
identifier_len = 2
|
|
152
|
+
elif identifier_enum == ResourceLocator.IDENTIFIER_8_BYTES:
|
|
153
|
+
identifier_len = 8
|
|
154
|
+
elif identifier_enum == ResourceLocator.IDENTIFIER_32_BYTES:
|
|
155
|
+
identifier_len = 32
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError(f"Invalid identifier length enum: {identifier_enum}")
|
|
158
|
+
|
|
159
|
+
if len(buffer) < offset + identifier_len:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Buffer too short for ResourceLocator identifier (need {offset + identifier_len}, have {len(buffer)})"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if identifier_len > 0:
|
|
165
|
+
identifier_bytes = buffer[offset : offset + identifier_len]
|
|
166
|
+
# Remove padding
|
|
167
|
+
identifier = identifier_bytes.rstrip(b"\x00").decode()
|
|
168
|
+
else:
|
|
169
|
+
identifier = ""
|
|
170
|
+
|
|
171
|
+
size = 2 + body_len + identifier_len
|
|
44
172
|
return ResourceLocator(resource_url, identifier), size
|
otdf_python/sdk.py
CHANGED
|
@@ -6,42 +6,12 @@ from contextlib import AbstractContextManager
|
|
|
6
6
|
from io import BytesIO
|
|
7
7
|
from typing import Any, BinaryIO
|
|
8
8
|
|
|
9
|
-
from otdf_python.config import NanoTDFConfig, TDFConfig
|
|
9
|
+
from otdf_python.config import KASInfo, NanoTDFConfig, TDFConfig
|
|
10
10
|
from otdf_python.nanotdf import NanoTDF
|
|
11
11
|
from otdf_python.sdk_exceptions import SDKException
|
|
12
12
|
from otdf_python.tdf import TDF, TDFReader, TDFReaderConfig
|
|
13
13
|
|
|
14
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
15
|
class KAS(AbstractContextManager):
|
|
46
16
|
"""
|
|
47
17
|
KAS (Key Access Service) interface to define methods related to key access and management.
|
|
@@ -71,7 +41,6 @@ class KAS(AbstractContextManager):
|
|
|
71
41
|
token_source=None,
|
|
72
42
|
sdk_ssl_verify=True,
|
|
73
43
|
use_plaintext=False,
|
|
74
|
-
auth_headers: dict | None = None,
|
|
75
44
|
):
|
|
76
45
|
"""
|
|
77
46
|
Initialize the KAS client
|
|
@@ -81,7 +50,6 @@ class KAS(AbstractContextManager):
|
|
|
81
50
|
token_source: Function that returns an authentication token
|
|
82
51
|
sdk_ssl_verify: Whether to verify SSL certificates
|
|
83
52
|
use_plaintext: Whether to use plaintext HTTP connections instead of HTTPS
|
|
84
|
-
auth_headers: Dictionary of authentication headers to include in requests
|
|
85
53
|
"""
|
|
86
54
|
from .kas_client import KASClient
|
|
87
55
|
|
|
@@ -94,7 +62,6 @@ class KAS(AbstractContextManager):
|
|
|
94
62
|
# Store the parameters for potential use
|
|
95
63
|
self._sdk_ssl_verify = sdk_ssl_verify
|
|
96
64
|
self._use_plaintext = use_plaintext
|
|
97
|
-
self._auth_headers = auth_headers
|
|
98
65
|
|
|
99
66
|
def get_ec_public_key(self, kas_info: Any, curve: Any) -> Any:
|
|
100
67
|
"""
|
|
@@ -152,7 +119,7 @@ class KAS(AbstractContextManager):
|
|
|
152
119
|
Unwrapped key as bytes
|
|
153
120
|
"""
|
|
154
121
|
if mock and wrapped_key and kas_private_key:
|
|
155
|
-
from .
|
|
122
|
+
from .asym_crypto import AsymDecryption
|
|
156
123
|
|
|
157
124
|
asym = AsymDecryption(private_key_pem=kas_private_key)
|
|
158
125
|
return asym.decrypt(wrapped_key)
|
|
@@ -179,12 +146,14 @@ class KAS(AbstractContextManager):
|
|
|
179
146
|
|
|
180
147
|
class SDK(AbstractContextManager):
|
|
181
148
|
def new_tdf_config(
|
|
182
|
-
self,
|
|
149
|
+
self,
|
|
150
|
+
attributes: list[str] | None = None,
|
|
151
|
+
kas_info_list: list[KASInfo] | None = None,
|
|
152
|
+
**kwargs,
|
|
183
153
|
) -> TDFConfig:
|
|
184
154
|
"""
|
|
185
155
|
Create a TDFConfig with default kas_info_list from the SDK's platform_url.
|
|
186
156
|
"""
|
|
187
|
-
from otdf_python.config import KASInfo
|
|
188
157
|
|
|
189
158
|
if self.platform_url is None:
|
|
190
159
|
raise SDKException("Cannot create TDFConfig: SDK platform_url is not set.")
|
|
@@ -232,10 +201,8 @@ class SDK(AbstractContextManager):
|
|
|
232
201
|
# Use existing port with the determined scheme
|
|
233
202
|
kas_url = f"{scheme}://{parsed_url.hostname}:{parsed_url.port}{parsed_url.path.rstrip('/')}/kas"
|
|
234
203
|
|
|
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
204
|
if kas_info_list is None:
|
|
205
|
+
kas_info = KASInfo(url=kas_url, default=True)
|
|
239
206
|
kas_info_list = [kas_info]
|
|
240
207
|
return TDFConfig(
|
|
241
208
|
kas_info_list=kas_info_list, attributes=attributes or [], **kwargs
|
|
@@ -251,30 +218,6 @@ class SDK(AbstractContextManager):
|
|
|
251
218
|
The Services interface provides access to various platform service clients and KAS.
|
|
252
219
|
"""
|
|
253
220
|
|
|
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
221
|
def kas(self) -> KAS:
|
|
279
222
|
"""
|
|
280
223
|
Returns the KAS client for key access operations.
|
|
@@ -292,9 +235,6 @@ class SDK(AbstractContextManager):
|
|
|
292
235
|
def __init__(
|
|
293
236
|
self,
|
|
294
237
|
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
238
|
platform_url: str | None = None,
|
|
299
239
|
ssl_verify: bool = True,
|
|
300
240
|
use_plaintext: bool = False,
|
|
@@ -304,17 +244,11 @@ class SDK(AbstractContextManager):
|
|
|
304
244
|
|
|
305
245
|
Args:
|
|
306
246
|
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
247
|
platform_url: Optional platform base URL
|
|
311
248
|
ssl_verify: Whether to verify SSL certificates (default: True)
|
|
312
249
|
use_plaintext: Whether to use HTTP instead of HTTPS (default: False)
|
|
313
250
|
"""
|
|
314
251
|
self.services = services
|
|
315
|
-
self.trust_manager = trust_manager
|
|
316
|
-
self.auth_interceptor = auth_interceptor
|
|
317
|
-
self.platform_services_client = platform_services_client
|
|
318
252
|
self.platform_url = platform_url
|
|
319
253
|
self.ssl_verify = ssl_verify
|
|
320
254
|
self._use_plaintext = use_plaintext
|
|
@@ -332,27 +266,17 @@ class SDK(AbstractContextManager):
|
|
|
332
266
|
"""Returns the services interface"""
|
|
333
267
|
return self.services
|
|
334
268
|
|
|
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
269
|
def get_platform_url(self) -> str | None:
|
|
348
270
|
"""Returns the platform URL if set"""
|
|
349
271
|
return self.platform_url
|
|
350
272
|
|
|
351
|
-
def
|
|
352
|
-
self,
|
|
273
|
+
def load_tdf(
|
|
274
|
+
self,
|
|
275
|
+
tdf_data: bytes | BinaryIO | BytesIO,
|
|
276
|
+
config: TDFReaderConfig | None = None,
|
|
353
277
|
) -> TDFReader:
|
|
354
278
|
"""
|
|
355
|
-
Loads a TDF from the provided data according to the config.
|
|
279
|
+
Loads a TDF from the provided data, optionally according to the config.
|
|
356
280
|
|
|
357
281
|
Args:
|
|
358
282
|
tdf_data: The TDF data as bytes, file object, or BytesIO
|
|
@@ -365,26 +289,10 @@ class SDK(AbstractContextManager):
|
|
|
365
289
|
SDKException: If there's an error loading the TDF
|
|
366
290
|
"""
|
|
367
291
|
tdf = TDF(self.services)
|
|
368
|
-
|
|
292
|
+
if config is None:
|
|
293
|
+
config = TDFReaderConfig()
|
|
369
294
|
|
|
370
|
-
|
|
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)
|
|
295
|
+
return tdf.load_tdf(tdf_data, config)
|
|
388
296
|
|
|
389
297
|
def create_tdf(
|
|
390
298
|
self,
|
otdf_python/sdk_builder.py
CHANGED
|
@@ -4,10 +4,9 @@ Provides methods to configure and build SDK instances.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
import os
|
|
8
7
|
import ssl
|
|
9
8
|
from dataclasses import dataclass
|
|
10
|
-
from
|
|
9
|
+
from pathlib import Path
|
|
11
10
|
|
|
12
11
|
import httpx
|
|
13
12
|
|
|
@@ -77,9 +76,10 @@ class SDKBuilder:
|
|
|
77
76
|
self.cert_paths = []
|
|
78
77
|
|
|
79
78
|
# Find all .pem and .crt files in the directory
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
certs_path = Path(certs_dir_path)
|
|
80
|
+
for cert_file in certs_path.iterdir():
|
|
81
|
+
if cert_file.suffix in (".pem", ".crt"):
|
|
82
|
+
self.cert_paths.append(str(cert_file))
|
|
83
83
|
|
|
84
84
|
# Create SSL context with these certificates
|
|
85
85
|
if self.cert_paths:
|
|
@@ -343,32 +343,6 @@ class SDKBuilder:
|
|
|
343
343
|
except Exception as e:
|
|
344
344
|
raise AutoConfigureException(f"Error during token acquisition: {e!s}")
|
|
345
345
|
|
|
346
|
-
def _create_auth_interceptor(self) -> Any:
|
|
347
|
-
"""
|
|
348
|
-
Creates an authentication interceptor for API requests (httpx).
|
|
349
|
-
Returns:
|
|
350
|
-
Any: An auth interceptor object
|
|
351
|
-
Raises:
|
|
352
|
-
AutoConfigureException: If auth configuration fails
|
|
353
|
-
"""
|
|
354
|
-
# For now, this is just a placeholder returning a dict with auth headers
|
|
355
|
-
# In a real implementation, this would create a proper interceptor object
|
|
356
|
-
# that injects auth headers into httpx requests
|
|
357
|
-
|
|
358
|
-
token = None
|
|
359
|
-
|
|
360
|
-
if self.auth_token:
|
|
361
|
-
# Use provided token
|
|
362
|
-
token = self.auth_token
|
|
363
|
-
elif self.oauth_config:
|
|
364
|
-
# Get token from OAuth
|
|
365
|
-
token = self._get_token_from_client_credentials()
|
|
366
|
-
|
|
367
|
-
if token:
|
|
368
|
-
return {"Authorization": f"Bearer {token}"}
|
|
369
|
-
|
|
370
|
-
return None
|
|
371
|
-
|
|
372
346
|
def _create_services(self) -> SDK.Services:
|
|
373
347
|
"""
|
|
374
348
|
Creates service client instances.
|
|
@@ -382,13 +356,11 @@ class SDKBuilder:
|
|
|
382
356
|
# connecting to the platform endpoints
|
|
383
357
|
|
|
384
358
|
ssl_verify = not self.insecure_skip_verify
|
|
385
|
-
auth_interceptor = self._create_auth_interceptor()
|
|
386
359
|
|
|
387
360
|
class ServicesImpl(SDK.Services):
|
|
388
361
|
def __init__(self, builder_instance):
|
|
389
362
|
self.closed = False
|
|
390
363
|
self._ssl_verify = ssl_verify
|
|
391
|
-
self._auth_headers = auth_interceptor if auth_interceptor else {}
|
|
392
364
|
self._builder = builder_instance
|
|
393
365
|
|
|
394
366
|
def kas(self) -> KAS:
|
|
@@ -432,16 +404,12 @@ class SDKBuilder:
|
|
|
432
404
|
if not self.platform_endpoint:
|
|
433
405
|
raise AutoConfigureException("Platform endpoint is not set")
|
|
434
406
|
|
|
435
|
-
# Create the auth interceptor
|
|
436
|
-
auth_interceptor = self._create_auth_interceptor()
|
|
437
|
-
|
|
438
407
|
# Create services
|
|
439
408
|
services = self._create_services()
|
|
440
409
|
|
|
441
410
|
# Return the SDK instance, platform_url is set for new_tdf_config
|
|
442
411
|
return SDK(
|
|
443
412
|
services=services,
|
|
444
|
-
auth_interceptor=auth_interceptor,
|
|
445
413
|
platform_url=self.platform_endpoint,
|
|
446
414
|
ssl_verify=not self.insecure_skip_verify,
|
|
447
415
|
use_plaintext=getattr(self, "use_plaintext", False),
|
otdf_python/tdf.py
CHANGED
|
@@ -87,7 +87,7 @@ class TDF:
|
|
|
87
87
|
import hashlib
|
|
88
88
|
import hmac
|
|
89
89
|
|
|
90
|
-
from
|
|
90
|
+
from .asym_crypto import AsymEncryption
|
|
91
91
|
|
|
92
92
|
key_access_objs = []
|
|
93
93
|
for kas in kas_infos:
|
|
@@ -188,7 +188,7 @@ class TDF:
|
|
|
188
188
|
"""
|
|
189
189
|
Unwraps the key locally using a provided private key (used for testing)
|
|
190
190
|
"""
|
|
191
|
-
from
|
|
191
|
+
from .asym_crypto import AsymDecryption
|
|
192
192
|
|
|
193
193
|
key = None
|
|
194
194
|
for ka in key_access_objs:
|
|
@@ -430,7 +430,8 @@ class TDF:
|
|
|
430
430
|
import zipfile
|
|
431
431
|
|
|
432
432
|
from otdf_python.aesgcm import AesGcm
|
|
433
|
-
|
|
433
|
+
|
|
434
|
+
from .asym_crypto import AsymDecryption
|
|
434
435
|
|
|
435
436
|
with zipfile.ZipFile(io.BytesIO(tdf_bytes), "r") as z:
|
|
436
437
|
manifest_json = z.read("0.manifest.json").decode()
|