otdf-python 0.4.2__py3-none-any.whl → 0.4.3__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/cli.py CHANGED
@@ -112,33 +112,14 @@ def load_client_credentials(creds_file_path: str) -> tuple[str, str]:
112
112
  ) from e
113
113
 
114
114
 
115
- def build_sdk(args) -> SDK:
116
- """Build SDK instance from CLI arguments."""
117
- builder = SDKBuilder()
118
-
119
- if args.platform_url:
120
- builder.set_platform_endpoint(args.platform_url)
121
-
122
- # Auto-detect HTTP URLs and enable plaintext mode
123
- if args.platform_url.startswith("http://") and (
124
- not hasattr(args, "plaintext") or not args.plaintext
125
- ):
126
- logger.debug(
127
- f"Auto-detected HTTP URL {args.platform_url}, enabling plaintext mode"
128
- )
129
- builder.use_insecure_plaintext_connection(True)
130
-
131
- if args.oidc_endpoint:
132
- builder.set_issuer_endpoint(args.oidc_endpoint)
133
-
115
+ def _configure_auth(builder: SDKBuilder, args) -> None:
116
+ """Configure authentication on the SDK builder."""
134
117
  if args.client_id and args.client_secret:
135
118
  builder.client_secret(args.client_id, args.client_secret)
136
119
  elif hasattr(args, "with_client_creds_file") and args.with_client_creds_file:
137
- # Load credentials from file
138
120
  client_id, client_secret = load_client_credentials(args.with_client_creds_file)
139
121
  builder.client_secret(client_id, client_secret)
140
122
  elif hasattr(args, "auth") and args.auth:
141
- # Parse combined auth string (clientId:clientSecret) - legacy support
142
123
  auth_parts = args.auth.split(":")
143
124
  if len(auth_parts) != 2:
144
125
  raise CLIError(
@@ -152,12 +133,49 @@ def build_sdk(args) -> SDK:
152
133
  "Authentication required: provide --with-client-creds-file OR --client-id and --client-secret",
153
134
  )
154
135
 
136
+
137
+ def _configure_kas_allowlist(builder: SDKBuilder, args) -> None:
138
+ """Configure KAS allowlist on the SDK builder."""
139
+ if hasattr(args, "ignore_kas_allowlist") and args.ignore_kas_allowlist:
140
+ logger.warning(
141
+ "KAS allowlist validation is disabled. This may leak credentials "
142
+ "to malicious servers if decrypting untrusted TDF files."
143
+ )
144
+ builder.with_ignore_kas_allowlist(True)
145
+ elif hasattr(args, "kas_allowlist") and args.kas_allowlist:
146
+ kas_urls = [url.strip() for url in args.kas_allowlist.split(",") if url.strip()]
147
+ logger.debug(f"Using KAS allowlist: {kas_urls}")
148
+ builder.with_kas_allowlist(kas_urls)
149
+
150
+
151
+ def build_sdk(args) -> SDK:
152
+ """Build SDK instance from CLI arguments."""
153
+ builder = SDKBuilder()
154
+
155
+ if args.platform_url:
156
+ builder.set_platform_endpoint(args.platform_url)
157
+ # Auto-detect HTTP URLs and enable plaintext mode
158
+ if args.platform_url.startswith("http://") and (
159
+ not hasattr(args, "plaintext") or not args.plaintext
160
+ ):
161
+ logger.debug(
162
+ f"Auto-detected HTTP URL {args.platform_url}, enabling plaintext mode"
163
+ )
164
+ builder.use_insecure_plaintext_connection(True)
165
+
166
+ if args.oidc_endpoint:
167
+ builder.set_issuer_endpoint(args.oidc_endpoint)
168
+
169
+ _configure_auth(builder, args)
170
+
155
171
  if hasattr(args, "plaintext") and args.plaintext:
156
172
  builder.use_insecure_plaintext_connection(True)
157
173
 
158
174
  if args.insecure:
159
175
  builder.use_insecure_skip_verify(True)
160
176
 
177
+ _configure_kas_allowlist(builder, args)
178
+
161
179
  return builder.build()
162
180
 
163
181
 
@@ -476,6 +494,17 @@ Where creds.json contains:
476
494
  security_group.add_argument(
477
495
  "--insecure", action="store_true", help="Skip TLS verification"
478
496
  )
497
+ security_group.add_argument(
498
+ "--kas-allowlist",
499
+ help="Comma-separated list of trusted KAS URLs. "
500
+ "By default, only the platform URL's KAS endpoint is trusted.",
501
+ )
502
+ security_group.add_argument(
503
+ "--ignore-kas-allowlist",
504
+ action="store_true",
505
+ help="WARNING: Disable KAS allowlist validation. This is insecure and "
506
+ "should only be used for testing. May leak credentials to malicious servers.",
507
+ )
479
508
 
480
509
  # Subcommands
481
510
  subparsers = parser.add_subparsers(dest="command", help="Available commands")
@@ -0,0 +1,182 @@
1
+ """KAS Allowlist: Validates KAS URLs against a list of trusted hosts.
2
+
3
+ This module provides protection against SSRF attacks where malicious TDF files
4
+ could contain attacker-controlled KAS URLs to steal OIDC credentials.
5
+ """
6
+
7
+ import logging
8
+ from urllib.parse import urlparse
9
+
10
+
11
+ class KASAllowlist:
12
+ """Validates KAS URLs against an allowlist of trusted hosts.
13
+
14
+ This class prevents credential theft by ensuring the SDK only sends
15
+ authentication tokens to trusted KAS endpoints.
16
+
17
+ Example:
18
+ allowlist = KASAllowlist(["https://kas.example.com"])
19
+ allowlist.is_allowed("https://kas.example.com/kas") # True
20
+ allowlist.is_allowed("https://evil.com/kas") # False
21
+
22
+ """
23
+
24
+ def __init__(self, allowed_urls: list[str] | None = None, allow_all: bool = False):
25
+ """Initialize the KAS allowlist.
26
+
27
+ Args:
28
+ allowed_urls: List of trusted KAS URLs. Each URL is normalized to
29
+ its origin (scheme://host:port) for comparison.
30
+ allow_all: If True, all URLs are allowed. Use only for testing.
31
+ A warning is logged when this is enabled.
32
+
33
+ """
34
+ self._allowed_origins: set[str] = set()
35
+ self._allow_all = allow_all
36
+
37
+ if allow_all:
38
+ logging.warning(
39
+ "KAS allowlist is disabled (allow_all=True). "
40
+ "This is insecure and should only be used for testing."
41
+ )
42
+
43
+ if allowed_urls:
44
+ for url in allowed_urls:
45
+ self.add(url)
46
+
47
+ def add(self, url: str) -> None:
48
+ """Add a URL to the allowlist.
49
+
50
+ The URL is normalized to its origin (scheme://host:port) before storage.
51
+ Paths and query strings are stripped.
52
+
53
+ Args:
54
+ url: The KAS URL to allow. Can include path components which
55
+ will be stripped for origin comparison.
56
+
57
+ """
58
+ origin = self._get_origin(url)
59
+ self._allowed_origins.add(origin)
60
+ logging.debug(f"Added KAS origin to allowlist: {origin}")
61
+
62
+ def is_allowed(self, url: str) -> bool:
63
+ """Check if a URL is allowed by the allowlist.
64
+
65
+ Args:
66
+ url: The KAS URL to check.
67
+
68
+ Returns:
69
+ True if the URL's origin is in the allowlist or allow_all is True.
70
+ False otherwise.
71
+
72
+ """
73
+ if self._allow_all:
74
+ logging.debug(f"KAS URL allowed (allow_all=True): {url}")
75
+ return True
76
+
77
+ if not self._allowed_origins:
78
+ logging.debug(f"KAS URL rejected (empty allowlist): {url}")
79
+ return False
80
+
81
+ origin = self._get_origin(url)
82
+ allowed = origin in self._allowed_origins
83
+ if allowed:
84
+ logging.debug(f"KAS URL allowed: {url} (origin: {origin})")
85
+ else:
86
+ logging.debug(
87
+ f"KAS URL rejected: {url} (origin: {origin}, "
88
+ f"allowed: {self._allowed_origins})"
89
+ )
90
+ return allowed
91
+
92
+ def validate(self, url: str) -> None:
93
+ """Validate a URL against the allowlist, raising an exception if not allowed.
94
+
95
+ Args:
96
+ url: The KAS URL to validate.
97
+
98
+ Raises:
99
+ SDK.KasAllowlistException: If the URL is not in the allowlist.
100
+
101
+ """
102
+ if not self.is_allowed(url):
103
+ # Import here to avoid circular imports
104
+ from .sdk import SDK
105
+
106
+ raise SDK.KasAllowlistException(url, self._allowed_origins)
107
+
108
+ @property
109
+ def allowed_origins(self) -> set[str]:
110
+ """Return the set of allowed origins (read-only copy)."""
111
+ return self._allowed_origins.copy()
112
+
113
+ @property
114
+ def allow_all(self) -> bool:
115
+ """Return whether all URLs are allowed."""
116
+ return self._allow_all
117
+
118
+ @staticmethod
119
+ def _get_origin(url: str) -> str:
120
+ """Extract the origin (scheme://host:port) from a URL.
121
+
122
+ This normalizes URLs for comparison by stripping paths and query strings.
123
+ Default ports (80 for http, 443 for https) are included explicitly.
124
+
125
+ Args:
126
+ url: The URL to extract the origin from.
127
+
128
+ Returns:
129
+ Normalized origin string in format scheme://host:port
130
+
131
+ """
132
+ # Add scheme if missing
133
+ if "://" not in url:
134
+ url = "https://" + url
135
+
136
+ try:
137
+ parsed = urlparse(url)
138
+ except Exception as e:
139
+ logging.warning(f"Failed to parse URL {url}: {e}")
140
+ # Return the URL as-is if parsing fails
141
+ return url.lower()
142
+
143
+ scheme = (parsed.scheme or "https").lower()
144
+ hostname = (parsed.hostname or "").lower()
145
+
146
+ if not hostname:
147
+ # URL might be malformed, return as-is
148
+ logging.warning(f"Could not extract hostname from URL: {url}")
149
+ return url.lower()
150
+
151
+ # Determine port (use explicit port or default for scheme)
152
+ if parsed.port:
153
+ port = parsed.port
154
+ elif scheme == "http":
155
+ port = 80
156
+ else:
157
+ port = 443
158
+
159
+ return f"{scheme}://{hostname}:{port}"
160
+
161
+ @classmethod
162
+ def from_platform_url(cls, platform_url: str) -> "KASAllowlist":
163
+ """Create an allowlist from a platform URL.
164
+
165
+ This is the default behavior: auto-allow the platform's KAS endpoint.
166
+
167
+ Args:
168
+ platform_url: The OpenTDF platform URL. The KAS endpoint is
169
+ assumed to be at {platform_url}/kas.
170
+
171
+ Returns:
172
+ KASAllowlist configured to allow the platform's KAS endpoint.
173
+
174
+ """
175
+ allowlist = cls()
176
+ # Add the platform URL itself (KAS might be at root or /kas)
177
+ allowlist.add(platform_url)
178
+ # Also construct the /kas endpoint explicitly
179
+ kas_url = platform_url.rstrip("/") + "/kas"
180
+ allowlist.add(kas_url)
181
+ logging.info(f"Created KAS allowlist from platform URL: {platform_url}")
182
+ return allowlist
otdf_python/kas_client.py CHANGED
@@ -38,13 +38,26 @@ class KASClient:
38
38
  cache=None,
39
39
  use_plaintext=False,
40
40
  verify_ssl=True,
41
+ kas_allowlist=None,
41
42
  ):
42
- """Initialize KAS client."""
43
+ """Initialize KAS client.
44
+
45
+ Args:
46
+ kas_url: Default KAS URL
47
+ token_source: Function that returns an authentication token
48
+ cache: Optional KASKeyCache for caching public keys
49
+ use_plaintext: Whether to use HTTP instead of HTTPS
50
+ verify_ssl: Whether to verify SSL certificates
51
+ kas_allowlist: Optional KASAllowlist for URL validation. If provided,
52
+ only URLs in the allowlist will be contacted.
53
+
54
+ """
43
55
  self.kas_url = kas_url
44
56
  self.token_source = token_source
45
57
  self.cache = cache or KASKeyCache()
46
58
  self.use_plaintext = use_plaintext
47
59
  self.verify_ssl = verify_ssl
60
+ self.kas_allowlist = kas_allowlist
48
61
  self.decryptor = None
49
62
  self.client_public_key = None
50
63
 
@@ -86,15 +99,26 @@ class KASClient:
86
99
  def _normalize_kas_url(self, url: str) -> str:
87
100
  """Normalize KAS URLs based on client security settings.
88
101
 
102
+ This method also validates the URL against the KAS allowlist if one
103
+ is configured. This prevents SSRF attacks where malicious TDF files
104
+ could contain attacker-controlled KAS URLs to steal OIDC credentials.
105
+
89
106
  Args:
90
107
  url: The KAS URL to normalize
91
108
 
92
109
  Returns:
93
110
  Normalized URL with appropriate protocol and port
94
111
 
112
+ Raises:
113
+ KASAllowlistException: If the URL is not in the allowlist
114
+
95
115
  """
96
116
  from urllib.parse import urlparse
97
117
 
118
+ # Validate against allowlist BEFORE making any requests
119
+ if self.kas_allowlist is not None:
120
+ self.kas_allowlist.validate(url)
121
+
98
122
  try:
99
123
  # Parse the URL
100
124
  parsed = urlparse(url)
@@ -154,7 +178,7 @@ class KASClient:
154
178
  # Reconstruct URL preserving the path (especially /kas prefix)
155
179
  try:
156
180
  # Create URL preserving the path component for proper endpoint routing
157
- path = parsed.path if parsed.path else ""
181
+ path = parsed.path or ""
158
182
  normalized_url = f"{scheme}://{parsed.hostname}:{port}{path}"
159
183
  logging.debug(f"normalized url [{parsed.geturl()}] to [{normalized_url}]")
160
184
  return normalized_url
otdf_python/nanotdf.py CHANGED
@@ -70,8 +70,8 @@ class NanoTDF:
70
70
  if isinstance(obj, PolicyBody):
71
71
  # Convert data_attributes to dataAttributes and use null instead of empty array
72
72
  result = {
73
- "dataAttributes": obj.data_attributes if obj.data_attributes else None,
74
- "dissem": obj.dissem if obj.dissem else None,
73
+ "dataAttributes": obj.data_attributes or None,
74
+ "dissem": obj.dissem or None,
75
75
  }
76
76
  return result
77
77
  elif isinstance(obj, AttributeObject):
@@ -115,14 +115,12 @@ class NanoTDF:
115
115
  tuple: (policy_body, policy_type)
116
116
 
117
117
  """
118
- attributes = config.attributes if config.attributes else []
118
+ attributes = config.attributes or []
119
119
  policy_object = self._create_policy_object(attributes)
120
120
  policy_json = json.dumps(
121
121
  policy_object, default=self._serialize_policy_object
122
122
  ).encode("utf-8")
123
- policy_type = (
124
- config.policy_type if config.policy_type else "EMBEDDED_POLICY_PLAIN_TEXT"
125
- )
123
+ policy_type = config.policy_type or "EMBEDDED_POLICY_PLAIN_TEXT"
126
124
 
127
125
  if policy_type == "EMBEDDED_POLICY_PLAIN_TEXT":
128
126
  policy_body = policy_json
otdf_python/sdk.py CHANGED
@@ -37,6 +37,7 @@ class KAS(AbstractContextManager):
37
37
  token_source=None,
38
38
  sdk_ssl_verify=True,
39
39
  use_plaintext=False,
40
+ kas_allowlist=None,
40
41
  ):
41
42
  """Initialize the KAS client.
42
43
 
@@ -45,6 +46,7 @@ class KAS(AbstractContextManager):
45
46
  token_source: Function that returns an authentication token
46
47
  sdk_ssl_verify: Whether to verify SSL certificates
47
48
  use_plaintext: Whether to use plaintext HTTP connections instead of HTTPS
49
+ kas_allowlist: Optional KASAllowlist for URL validation
48
50
 
49
51
  """
50
52
  from .kas_client import KASClient
@@ -54,6 +56,7 @@ class KAS(AbstractContextManager):
54
56
  token_source=token_source,
55
57
  verify_ssl=sdk_ssl_verify,
56
58
  use_plaintext=use_plaintext,
59
+ kas_allowlist=kas_allowlist,
57
60
  )
58
61
  # Store the parameters for potential use
59
62
  self._sdk_ssl_verify = sdk_ssl_verify
@@ -405,6 +408,33 @@ class SDK(AbstractContextManager):
405
408
  class KasAllowlistException(SDKException):
406
409
  """Throw when KAS allowlist check fails."""
407
410
 
411
+ def __init__(
412
+ self,
413
+ url: str,
414
+ allowed_origins: set[str] | None = None,
415
+ message: str | None = None,
416
+ ):
417
+ """Initialize exception.
418
+
419
+ Args:
420
+ url: The KAS URL that was rejected
421
+ allowed_origins: Set of allowed origin URLs
422
+ message: Optional custom message (auto-generated if not provided)
423
+
424
+ """
425
+ self.url = url
426
+ self.allowed_origins = allowed_origins or set()
427
+ if message is None:
428
+ origins_str = (
429
+ ", ".join(sorted(self.allowed_origins))
430
+ if self.allowed_origins
431
+ else "none"
432
+ )
433
+ message = (
434
+ f"KAS URL not in allowlist: {url}. Allowed origins: {origins_str}"
435
+ )
436
+ super().__init__(message)
437
+
408
438
  class AssertionException(SDKException):
409
439
  """Throw when an assertion validation fails."""
410
440
 
@@ -10,6 +10,7 @@ from pathlib import Path
10
10
 
11
11
  import httpx
12
12
 
13
+ from otdf_python.kas_allowlist import KASAllowlist
13
14
  from otdf_python.sdk import KAS, SDK
14
15
  from otdf_python.sdk_exceptions import AutoConfigureException
15
16
 
@@ -47,6 +48,8 @@ class SDKBuilder:
47
48
  self.ssl_context: ssl.SSLContext | None = None
48
49
  self.auth_token: str | None = None
49
50
  self.cert_paths: list[str] = []
51
+ self._kas_allowlist_urls: list[str] | None = None
52
+ self._ignore_kas_allowlist: bool = False
50
53
 
51
54
  @staticmethod
52
55
  def new_builder() -> "SDKBuilder":
@@ -201,6 +204,54 @@ class SDKBuilder:
201
204
  self.auth_token = token
202
205
  return self
203
206
 
207
+ def with_kas_allowlist(self, urls: list[str]) -> "SDKBuilder":
208
+ """Set the KAS allowlist to restrict which KAS servers the SDK will contact.
209
+
210
+ This provides protection against SSRF attacks where malicious TDF files
211
+ could contain attacker-controlled KAS URLs to steal OIDC credentials.
212
+
213
+ By default (if no allowlist is set), only the platform's KAS endpoint
214
+ is allowed.
215
+
216
+ Args:
217
+ urls: List of trusted KAS URLs. Each URL is normalized to its
218
+ origin (scheme://host:port) for comparison.
219
+
220
+ Returns:
221
+ self: The builder instance for chaining
222
+
223
+ Example:
224
+ builder.with_kas_allowlist([
225
+ "https://kas.example.com",
226
+ "https://kas2.example.com:8443"
227
+ ])
228
+
229
+ """
230
+ self._kas_allowlist_urls = urls
231
+ return self
232
+
233
+ def with_ignore_kas_allowlist(self, ignore: bool = True) -> "SDKBuilder":
234
+ """Configure whether to skip KAS allowlist validation.
235
+
236
+ WARNING: This is insecure and should only be used for testing or
237
+ development. When enabled, the SDK will contact any KAS URL found
238
+ in TDF files, potentially leaking credentials to malicious servers.
239
+
240
+ Args:
241
+ ignore: Whether to ignore the KAS allowlist (default: True)
242
+
243
+ Returns:
244
+ self: The builder instance for chaining
245
+
246
+ """
247
+ self._ignore_kas_allowlist = ignore
248
+ if ignore:
249
+ logger.warning(
250
+ "KAS allowlist validation is disabled. This is insecure and "
251
+ "should only be used for testing."
252
+ )
253
+ return self
254
+
204
255
  def _discover_token_endpoint_from_platform(self) -> None:
205
256
  """Discover token endpoint using OpenTDF platform configuration.
206
257
 
@@ -356,6 +407,34 @@ class SDKBuilder:
356
407
  f"Error during token acquisition: {e!s}"
357
408
  ) from e
358
409
 
410
+ def _create_kas_allowlist(self) -> KASAllowlist | None:
411
+ """Create the KAS allowlist based on builder configuration.
412
+
413
+ Returns:
414
+ KASAllowlist configured based on builder settings, or None if
415
+ allowlist validation is disabled.
416
+
417
+ """
418
+ # If ignoring allowlist, return an allow-all instance
419
+ if self._ignore_kas_allowlist:
420
+ return KASAllowlist(allow_all=True)
421
+
422
+ # If explicit allowlist provided, use it
423
+ if self._kas_allowlist_urls:
424
+ allowlist = KASAllowlist(self._kas_allowlist_urls)
425
+ # Also add the platform URL for convenience
426
+ if self.platform_endpoint:
427
+ allowlist.add(self.platform_endpoint)
428
+ allowlist.add(self.platform_endpoint.rstrip("/") + "/kas")
429
+ return allowlist
430
+
431
+ # Default: create allowlist from platform URL only
432
+ if self.platform_endpoint:
433
+ return KASAllowlist.from_platform_url(self.platform_endpoint)
434
+
435
+ # No platform endpoint set yet - return None and let SDK handle it
436
+ return None
437
+
359
438
  def _create_services(self) -> SDK.Services:
360
439
  """Create service client instances.
361
440
 
@@ -371,11 +450,15 @@ class SDKBuilder:
371
450
 
372
451
  ssl_verify = not self.insecure_skip_verify
373
452
 
453
+ # Create the KAS allowlist
454
+ kas_allowlist = self._create_kas_allowlist()
455
+
374
456
  class ServicesImpl(SDK.Services):
375
- def __init__(self, builder_instance):
457
+ def __init__(self, builder_instance, allowlist: KASAllowlist | None):
376
458
  self.closed = False
377
459
  self._ssl_verify = ssl_verify
378
460
  self._builder = builder_instance
461
+ self._kas_allowlist = allowlist
379
462
 
380
463
  def kas(self) -> KAS:
381
464
  """Return the KAS interface with SSL verification settings."""
@@ -394,6 +477,7 @@ class SDKBuilder:
394
477
  token_source=token_source,
395
478
  sdk_ssl_verify=self._ssl_verify,
396
479
  use_plaintext=self._builder.use_plaintext,
480
+ kas_allowlist=self._kas_allowlist,
397
481
  )
398
482
  return kas_impl
399
483
 
@@ -403,7 +487,7 @@ class SDKBuilder:
403
487
  def __exit__(self, exc_type, exc_val, exc_tb):
404
488
  self.close()
405
489
 
406
- return ServicesImpl(self)
490
+ return ServicesImpl(self, kas_allowlist)
407
491
 
408
492
  def build(self) -> SDK:
409
493
  """Build and return an SDK instance with configured properties.
otdf_python/tdf.py CHANGED
@@ -183,8 +183,8 @@ class TDF:
183
183
  if isinstance(obj, PolicyBody):
184
184
  # Convert data_attributes to dataAttributes and use null instead of empty array
185
185
  result = {
186
- "dataAttributes": obj.data_attributes if obj.data_attributes else None,
187
- "dissem": obj.dissem if obj.dissem else None,
186
+ "dataAttributes": obj.data_attributes or None,
187
+ "dissem": obj.dissem or None,
188
188
  }
189
189
  return result
190
190
  elif isinstance(obj, AttributeObject):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otdf-python
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Unofficial OpenTDF SDK for Python
5
5
  Project-URL: Homepage, https://github.com/b-long/opentdf-python-sdk
6
6
  Project-URL: Repository, https://github.com/b-long/opentdf-python-sdk
@@ -6,7 +6,7 @@ otdf_python/assertion_config.py,sha256=rw0SIB3xG-nAeb5r_liuxLphU4tcj-zlq8rVvXncX
6
6
  otdf_python/asym_crypto.py,sha256=EYkMNhZJP5khH0IvICTOG2bMg_TMvd6wXDu5zW0jpj4,7234
7
7
  otdf_python/auth_headers.py,sha256=uOLflFunBCw59nwk23rdiFQWOFrS19HugQXuQPGv3xE,986
8
8
  otdf_python/autoconfigure_utils.py,sha256=W5aJ0tC7HWfGb_1Mva_oxgduUzSpPoyDeaq0rPwgPAs,3689
9
- otdf_python/cli.py,sha256=HKIzuIXIW2sBwW3jy9sGq8WVFlxlKrdKac9MjJig88Y,19940
9
+ otdf_python/cli.py,sha256=icooiGgRh8H1IlP-_iO7paLB60IXUSaSacODABeqrS4,21165
10
10
  otdf_python/collection_store.py,sha256=sYL6VMFDBfHfCCLk14iybeC_qoUlpJFB0wOMt1bdwpY,1429
11
11
  otdf_python/collection_store_impl.py,sha256=3RqO3rvDCosajKpuls5DiO2_SWYsNQul9_9L7n-lQ68,758
12
12
  otdf_python/config.py,sha256=l1Ykg1gFUrFZTnd6bwMI6oi_clR5uCZ_Y1qH7QKtW90,2523
@@ -20,14 +20,15 @@ otdf_python/ecdh.py,sha256=fwxE80qFSIkfJeUz3GNhEndRKkZrBN06FE1gnvwUHHI,10201
20
20
  otdf_python/eckeypair.py,sha256=qcPKv0OS1lYxRICj9dhAW_eMz32anFBtpI8EJfXxpX0,2470
21
21
  otdf_python/header.py,sha256=peG14kE_KAUCW4fY82sqcWF5zTAVAnJfFXCHtC8Z0iQ,7189
22
22
  otdf_python/invalid_zip_exception.py,sha256=M_bAiXEjJdxPfA178YH-uHGRwMrNBKzjQzlQ54aDP2w,292
23
- otdf_python/kas_client.py,sha256=6q61WHUPAaDdLET0EkFbIpGMy9hHFCJoA3gs4xbd9FE,26542
23
+ otdf_python/kas_allowlist.py,sha256=0yX3J9J1od_ew0sp3pX6kfav39pTz7aBBpMnVkE5T7s,5884
24
+ otdf_python/kas_client.py,sha256=N5mQubRUrnJCtSxTiiyjf7DEfsg2wVZ02xufuZBH_b0,27532
24
25
  otdf_python/kas_connect_rpc_client.py,sha256=cB3cBomCClmu-InUpw8R35cLyJQ8X0PoEsFvYSH0RUM,8950
25
26
  otdf_python/kas_info.py,sha256=V-5om8k4RKbhE0h1CS1Rxb18TYcHKvq_hEPP6ah8K_o,738
26
27
  otdf_python/kas_key_cache.py,sha256=6hfzRAg9o_IfRErWSe-_gGTG9kRyYENMizMY1Shkmfk,1548
27
28
  otdf_python/key_type.py,sha256=2gQlXOj35J3ISCcWjU3sGYUxmlZR47BMq6Xr2yoKA8k,928
28
29
  otdf_python/key_type_constants.py,sha256=MV2Dsea3A6nnnYztoD0N1QxhrbQXZfaXaqCr2rI6sqo,954
29
30
  otdf_python/manifest.py,sha256=aglGw9EdtZZIxmwqy82sV5wum_mKkjzew4brSgxmJjc,7047
30
- otdf_python/nanotdf.py,sha256=lJgO7Go6qhdvQ8TTvglMBkxn0fg2VTDkPNgcfOhJabY,34033
31
+ otdf_python/nanotdf.py,sha256=y1mLktlZ-mRCup2vzGY4ZbFeNEhWpCYLOZYjzTSPEtc,33921
31
32
  otdf_python/nanotdf_ecdsa_struct.py,sha256=jTQKFAicTfMfN9CxJZYQcnEYGmtfAQoDOhz8ta-pGAQ,4066
32
33
  otdf_python/nanotdf_type.py,sha256=3MQzT6lJ3WJKMICFyyYZXX2_cFYcZ5G4m1uif-l9Nxo,1112
33
34
  otdf_python/policy_binding_serializer.py,sha256=oOcGBYOISPTzHRtk8JszwLTraY_F2OoevOf0a53jGHA,1271
@@ -35,11 +36,11 @@ otdf_python/policy_info.py,sha256=aq74dZg9PhTZ6cMkZyFsu3D754C5YijFMiuoYEL-1bY,20
35
36
  otdf_python/policy_object.py,sha256=LikIsahPkKr-iYA0lhgQitCbh8CsmxUBYyBs6VYfmxY,512
36
37
  otdf_python/policy_stub.py,sha256=RfU_fICqsAOnTXOHpKhtKC0RJ3KoWhDxO0XecZWM548,159
37
38
  otdf_python/resource_locator.py,sha256=bjK935XcfNq-PyqidHNq8eIiPeZEStYdQvmQ9B9GY20,6290
38
- otdf_python/sdk.py,sha256=4aIdIDhx2dMPRMfp9U8gzpwiqSeBf7SrEewb2BjB6PM,14012
39
- otdf_python/sdk_builder.py,sha256=ltnFZD7ClVVwhGwu6ew4WNiNj0NB3n2Xn_MTEcbIMxo,14497
39
+ otdf_python/sdk.py,sha256=-3Zuyfvf8ZM6iqxFpFDRAxCSfe35r6-TIuOOGUbPNPM,15058
40
+ otdf_python/sdk_builder.py,sha256=xTPoBjB3eGwoWjfSgiPmiNnEZJ8g-LUQVZDmo7BM9cY,17649
40
41
  otdf_python/sdk_exceptions.py,sha256=2GElGyM5LKN8Uh_lAiT6Ho4oNRWYRQsMNOK5s2jiv28,687
41
42
  otdf_python/symmetric_and_payload_config.py,sha256=iPHeZSeY9BjsQ-wkvdm6-FIR7773EgGiaIvSG-ICOHw,1158
42
- otdf_python/tdf.py,sha256=PtVBY_vAPIGz1I4gUlHPLPxa1gCCsd2nlRm0SrbaoJA,20840
43
+ otdf_python/tdf.py,sha256=lqaSSGfd-nzlmjpYoXa6ZLihZIJA170dwodKt_iRko8,20799
43
44
  otdf_python/tdf_reader.py,sha256=WMetzX4CIKJ15f4J_zyFGtObQO6bQ33KC_ykIonH9ik,5228
44
45
  otdf_python/tdf_writer.py,sha256=FLm1P26J4p6WPyKsjOb7QLYJqDIMDsBONqBW_JuFxyw,798
45
46
  otdf_python/token_source.py,sha256=YHbP7deSSXo1CvzVGJX7DkOuBgqwfP_Ockm8CE-MN0o,1011
@@ -140,7 +141,7 @@ otdf_python_proto/wellknownconfiguration/__init__.py,sha256=xK8XUrwCL9elWuMTx6vV
140
141
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_connect.py,sha256=fVQfo4OsTK8e63ytmiGyMe1AxgirktNqI_O3rQ6o6Nc,6342
141
142
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py,sha256=g9xSm9TxX0IPMqiFCaridJvI2TrL8PrXVFPgu8tX9VM,3863
142
143
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi,sha256=Zw4vROvTgomnFqsalJrYda632ojXH0FVXSzTXxerybw,1490
143
- otdf_python-0.4.2.dist-info/METADATA,sha256=bbTfB1zZ1j7iv1lUmw01ZzHIGpfBhSlI78l8bN2UuXE,5175
144
- otdf_python-0.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
145
- otdf_python-0.4.2.dist-info/licenses/LICENSE,sha256=DPrPHdI6tfZcqk9kzQ37vh1Ftk7LJYdMrUtwKl7L3Pw,1074
146
- otdf_python-0.4.2.dist-info/RECORD,,
144
+ otdf_python-0.4.3.dist-info/METADATA,sha256=OCF50LC7pWodNWzwurJp5yWLBleo01O7g3p6QhRo5Ug,5175
145
+ otdf_python-0.4.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
146
+ otdf_python-0.4.3.dist-info/licenses/LICENSE,sha256=DPrPHdI6tfZcqk9kzQ37vh1Ftk7LJYdMrUtwKl7L3Pw,1074
147
+ otdf_python-0.4.3.dist-info/RECORD,,