otdf-python 0.4.0__py3-none-any.whl → 0.4.2__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 (96) hide show
  1. otdf_python/__init__.py +1 -2
  2. otdf_python/__main__.py +1 -2
  3. otdf_python/address_normalizer.py +8 -10
  4. otdf_python/aesgcm.py +8 -0
  5. otdf_python/assertion_config.py +21 -0
  6. otdf_python/asym_crypto.py +18 -22
  7. otdf_python/auth_headers.py +7 -6
  8. otdf_python/autoconfigure_utils.py +21 -7
  9. otdf_python/cli.py +5 -5
  10. otdf_python/collection_store.py +13 -1
  11. otdf_python/collection_store_impl.py +5 -0
  12. otdf_python/config.py +13 -0
  13. otdf_python/connect_client.py +1 -0
  14. otdf_python/constants.py +2 -0
  15. otdf_python/crypto_utils.py +4 -0
  16. otdf_python/dpop.py +3 -5
  17. otdf_python/ecc_constants.py +12 -14
  18. otdf_python/ecc_mode.py +7 -2
  19. otdf_python/ecdh.py +24 -31
  20. otdf_python/eckeypair.py +5 -0
  21. otdf_python/header.py +5 -0
  22. otdf_python/invalid_zip_exception.py +6 -2
  23. otdf_python/kas_client.py +66 -55
  24. otdf_python/kas_connect_rpc_client.py +75 -38
  25. otdf_python/kas_info.py +4 -3
  26. otdf_python/kas_key_cache.py +10 -9
  27. otdf_python/key_type.py +4 -0
  28. otdf_python/key_type_constants.py +4 -11
  29. otdf_python/manifest.py +24 -0
  30. otdf_python/nanotdf.py +30 -28
  31. otdf_python/nanotdf_ecdsa_struct.py +5 -11
  32. otdf_python/nanotdf_type.py +13 -1
  33. otdf_python/policy_binding_serializer.py +6 -4
  34. otdf_python/policy_info.py +6 -0
  35. otdf_python/policy_object.py +8 -0
  36. otdf_python/policy_stub.py +2 -0
  37. otdf_python/resource_locator.py +22 -13
  38. otdf_python/sdk.py +51 -73
  39. otdf_python/sdk_builder.py +60 -47
  40. otdf_python/sdk_exceptions.py +11 -1
  41. otdf_python/symmetric_and_payload_config.py +6 -0
  42. otdf_python/tdf.py +47 -10
  43. otdf_python/tdf_reader.py +10 -13
  44. otdf_python/tdf_writer.py +5 -0
  45. otdf_python/token_source.py +4 -3
  46. otdf_python/version.py +5 -0
  47. otdf_python/zip_reader.py +10 -2
  48. otdf_python/zip_writer.py +11 -0
  49. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/METADATA +3 -2
  50. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/RECORD +81 -72
  51. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/WHEEL +1 -1
  52. otdf_python_proto/__init__.py +2 -6
  53. otdf_python_proto/authorization/__init__.py +10 -0
  54. otdf_python_proto/authorization/authorization_connect.py +250 -0
  55. otdf_python_proto/authorization/v2/authorization_connect.py +315 -0
  56. otdf_python_proto/entityresolution/__init__.py +10 -0
  57. otdf_python_proto/entityresolution/entity_resolution_connect.py +185 -0
  58. otdf_python_proto/entityresolution/v2/entity_resolution_connect.py +185 -0
  59. otdf_python_proto/kas/__init__.py +2 -2
  60. otdf_python_proto/kas/kas_connect.py +259 -0
  61. otdf_python_proto/policy/actions/__init__.py +11 -0
  62. otdf_python_proto/policy/actions/actions_connect.py +380 -0
  63. otdf_python_proto/policy/attributes/__init__.py +11 -0
  64. otdf_python_proto/policy/attributes/attributes_connect.py +1310 -0
  65. otdf_python_proto/policy/kasregistry/__init__.py +11 -0
  66. otdf_python_proto/policy/kasregistry/key_access_server_registry_connect.py +912 -0
  67. otdf_python_proto/policy/keymanagement/__init__.py +11 -0
  68. otdf_python_proto/policy/keymanagement/key_management_connect.py +380 -0
  69. otdf_python_proto/policy/namespaces/__init__.py +11 -0
  70. otdf_python_proto/policy/namespaces/namespaces_connect.py +648 -0
  71. otdf_python_proto/policy/registeredresources/__init__.py +11 -0
  72. otdf_python_proto/policy/registeredresources/registered_resources_connect.py +770 -0
  73. otdf_python_proto/policy/resourcemapping/__init__.py +11 -0
  74. otdf_python_proto/policy/resourcemapping/resource_mapping_connect.py +790 -0
  75. otdf_python_proto/policy/subjectmapping/__init__.py +11 -0
  76. otdf_python_proto/policy/subjectmapping/subject_mapping_connect.py +851 -0
  77. otdf_python_proto/policy/unsafe/__init__.py +11 -0
  78. otdf_python_proto/policy/unsafe/unsafe_connect.py +705 -0
  79. otdf_python_proto/wellknownconfiguration/__init__.py +10 -0
  80. otdf_python_proto/wellknownconfiguration/wellknown_configuration_connect.py +124 -0
  81. otdf_python_proto/authorization/authorization_pb2_connect.py +0 -191
  82. otdf_python_proto/authorization/v2/authorization_pb2_connect.py +0 -233
  83. otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +0 -149
  84. otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +0 -149
  85. otdf_python_proto/kas/kas_pb2_connect.py +0 -192
  86. otdf_python_proto/policy/actions/actions_pb2_connect.py +0 -275
  87. otdf_python_proto/policy/attributes/attributes_pb2_connect.py +0 -863
  88. otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +0 -611
  89. otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +0 -275
  90. otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +0 -443
  91. otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +0 -527
  92. otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +0 -527
  93. otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +0 -569
  94. otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +0 -485
  95. otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +0 -107
  96. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,19 @@
1
- """
2
- KASKeyCache: In-memory cache for KAS (Key Access Service) public keys and info.
3
- """
1
+ """KASKeyCache: In-memory cache for KAS (Key Access Service) public keys and info."""
4
2
 
5
3
  import threading
6
4
  from typing import Any
7
5
 
8
6
 
9
7
  class KASKeyCache:
8
+ """In-memory cache for KAS public keys and information."""
9
+
10
10
  def __init__(self):
11
+ """Initialize KAS key cache."""
11
12
  self._cache = {}
12
13
  self._lock = threading.Lock()
13
14
 
14
15
  def get(self, url: str, algorithm: str | None = None) -> Any | None:
15
- """
16
- Gets a KASInfo object from the cache based on URL and algorithm.
16
+ """Get a KASInfo object from cache based on URL and algorithm.
17
17
 
18
18
  Args:
19
19
  url: The URL of the KAS
@@ -21,17 +21,18 @@ class KASKeyCache:
21
21
 
22
22
  Returns:
23
23
  The cached KASInfo object, or None if not found
24
+
24
25
  """
25
26
  cache_key = self._make_key(url, algorithm)
26
27
  with self._lock:
27
28
  return self._cache.get(cache_key)
28
29
 
29
30
  def store(self, kas_info) -> None:
30
- """
31
- Stores a KASInfo object in the cache.
31
+ """Store a KASInfo object in cache.
32
32
 
33
33
  Args:
34
34
  kas_info: The KASInfo object to store
35
+
35
36
  """
36
37
  cache_key = self._make_key(kas_info.url, getattr(kas_info, "algorithm", None))
37
38
  with self._lock:
@@ -43,10 +44,10 @@ class KASKeyCache:
43
44
  self._cache[key] = value
44
45
 
45
46
  def clear(self):
46
- """Clears the cache"""
47
+ """Clear the cache."""
47
48
  with self._lock:
48
49
  self._cache.clear()
49
50
 
50
51
  def _make_key(self, url: str, algorithm: str | None = None) -> str:
51
- """Creates a cache key from URL and algorithm"""
52
+ """Create a cache key from URL and algorithm."""
52
53
  return f"{url}:{algorithm or ''}"
otdf_python/key_type.py CHANGED
@@ -1,7 +1,11 @@
1
+ """Key type constants for RSA and EC encryption."""
2
+
1
3
  from enum import Enum
2
4
 
3
5
 
4
6
  class KeyType(Enum):
7
+ """Key type enumeration for encryption algorithms."""
8
+
5
9
  RSA2048Key = "rsa:2048"
6
10
  EC256Key = "ec:secp256r1"
7
11
  EC384Key = "ec:secp384r1"
@@ -1,5 +1,4 @@
1
- """
2
- Constants for session key types used in the KAS client.
1
+ """Constants for session key types used in the KAS client.
3
2
  This matches the Java SDK's KeyType enum pattern.
4
3
  """
5
4
 
@@ -7,9 +6,7 @@ from enum import Enum, auto
7
6
 
8
7
 
9
8
  class KeyType(Enum):
10
- """
11
- Enum for key types used in the KAS client.
12
- """
9
+ """Enum for key types used in the KAS client."""
13
10
 
14
11
  RSA2048 = auto()
15
12
  EC_P256 = auto()
@@ -18,16 +15,12 @@ class KeyType(Enum):
18
15
 
19
16
  @property
20
17
  def is_ec(self):
21
- """
22
- Returns True if this key type is an EC key, False otherwise.
23
- """
18
+ """Returns True if this key type is an EC key, False otherwise."""
24
19
  return self in [KeyType.EC_P256, KeyType.EC_P384, KeyType.EC_P521]
25
20
 
26
21
  @property
27
22
  def curve_name(self):
28
- """
29
- Returns the curve name for EC keys.
30
- """
23
+ """Returns the curve name for EC keys."""
31
24
  if self == KeyType.EC_P256:
32
25
  return "P-256"
33
26
  elif self == KeyType.EC_P384:
otdf_python/manifest.py CHANGED
@@ -1,3 +1,5 @@
1
+ """TDF manifest representation and serialization."""
2
+
1
3
  import json
2
4
  from dataclasses import asdict, dataclass, field
3
5
  from typing import Any
@@ -5,6 +7,8 @@ from typing import Any
5
7
 
6
8
  @dataclass
7
9
  class ManifestSegment:
10
+ """Encrypted segment information in TDF manifest."""
11
+
8
12
  hash: str
9
13
  segmentSize: int
10
14
  encryptedSegmentSize: int
@@ -12,12 +16,16 @@ class ManifestSegment:
12
16
 
13
17
  @dataclass
14
18
  class ManifestRootSignature:
19
+ """Root signature for manifest integrity."""
20
+
15
21
  alg: str
16
22
  sig: str
17
23
 
18
24
 
19
25
  @dataclass
20
26
  class ManifestIntegrityInformation:
27
+ """Manifest integrity information with signatures and hashes."""
28
+
21
29
  rootSignature: ManifestRootSignature
22
30
  segmentHashAlg: str
23
31
  segmentSizeDefault: int
@@ -27,12 +35,16 @@ class ManifestIntegrityInformation:
27
35
 
28
36
  @dataclass
29
37
  class ManifestPolicyBinding:
38
+ """Policy binding with algorithm and hash."""
39
+
30
40
  alg: str
31
41
  hash: str
32
42
 
33
43
 
34
44
  @dataclass
35
45
  class ManifestKeyAccess:
46
+ """Key access information in manifest."""
47
+
36
48
  type: str
37
49
  url: str
38
50
  protocol: str
@@ -47,6 +59,8 @@ class ManifestKeyAccess:
47
59
 
48
60
  @dataclass
49
61
  class ManifestMethod:
62
+ """Encryption method information in manifest."""
63
+
50
64
  algorithm: str
51
65
  iv: str
52
66
  isStreamable: bool | None = None
@@ -54,6 +68,8 @@ class ManifestMethod:
54
68
 
55
69
  @dataclass
56
70
  class ManifestEncryptionInformation:
71
+ """Encryption information in TDF manifest."""
72
+
57
73
  type: str
58
74
  policy: str
59
75
  keyAccess: list[ManifestKeyAccess]
@@ -63,6 +79,8 @@ class ManifestEncryptionInformation:
63
79
 
64
80
  @dataclass
65
81
  class ManifestPayload:
82
+ """Payload information in TDF manifest."""
83
+
66
84
  type: str
67
85
  url: str
68
86
  protocol: str
@@ -72,12 +90,16 @@ class ManifestPayload:
72
90
 
73
91
  @dataclass
74
92
  class ManifestBinding:
93
+ """Assertion binding information."""
94
+
75
95
  method: str
76
96
  signature: str
77
97
 
78
98
 
79
99
  @dataclass
80
100
  class ManifestAssertion:
101
+ """TDF assertion in manifest."""
102
+
81
103
  id: str
82
104
  type: str
83
105
  scope: str
@@ -88,6 +110,8 @@ class ManifestAssertion:
88
110
 
89
111
  @dataclass
90
112
  class Manifest:
113
+ """TDF manifest with encryption and payload information."""
114
+
91
115
  schemaVersion: str | None = None
92
116
  encryptionInformation: ManifestEncryptionInformation | None = None
93
117
  payload: ManifestPayload | None = None
otdf_python/nanotdf.py CHANGED
@@ -1,3 +1,5 @@
1
+ """NanoTDF reader and writer implementation."""
2
+
1
3
  import contextlib
2
4
  import hashlib
3
5
  import json
@@ -24,22 +26,24 @@ from .asym_crypto import AsymDecryption
24
26
 
25
27
 
26
28
  class NanoTDFException(SDKException):
27
- pass
29
+ """Base exception for NanoTDF operations."""
28
30
 
29
31
 
30
32
  class NanoTDFMaxSizeLimit(NanoTDFException):
31
- pass
33
+ """Exception for NanoTDF size limit exceeded."""
32
34
 
33
35
 
34
36
  class UnsupportedNanoTDFFeature(NanoTDFException):
35
- pass
37
+ """Exception for unsupported NanoTDF features."""
36
38
 
37
39
 
38
40
  class InvalidNanoTDFConfig(NanoTDFException):
39
- pass
41
+ """Exception for invalid NanoTDF configuration."""
40
42
 
41
43
 
42
44
  class NanoTDF:
45
+ """NanoTDF reader and writer for compact TDF format."""
46
+
43
47
  MAGIC_NUMBER_AND_VERSION = MAGIC_NUMBER_AND_VERSION
44
48
  K_MAX_TDF_SIZE = (16 * 1024 * 1024) - 3 - 32
45
49
  K_NANOTDF_GMAC_LENGTH = 8
@@ -48,6 +52,7 @@ class NanoTDF:
48
52
  K_EMPTY_IV = bytes([0x0] * 12)
49
53
 
50
54
  def __init__(self, services=None, collection_store: CollectionStore | None = None):
55
+ """Initialize NanoTDF reader/writer."""
51
56
  self.services = services
52
57
  self.collection_store = collection_store or NoOpCollectionStore()
53
58
 
@@ -59,7 +64,7 @@ class NanoTDF:
59
64
  return PolicyObject(uuid=policy_uuid, body=body)
60
65
 
61
66
  def _serialize_policy_object(self, obj):
62
- """Custom NanoTDF serializer to convert to compatible JSON format."""
67
+ """Serialize policy object to compatible JSON format."""
63
68
  from otdf_python.policy_object import AttributeObject, PolicyBody
64
69
 
65
70
  if isinstance(obj, PolicyBody):
@@ -82,8 +87,7 @@ class NanoTDF:
82
87
  return obj.__dict__
83
88
 
84
89
  def _prepare_payload(self, payload: bytes | BytesIO) -> bytes:
85
- """
86
- Convert BytesIO to bytes and validate payload size.
90
+ """Convert BytesIO to bytes and validate payload size.
87
91
 
88
92
  Args:
89
93
  payload: The payload data as bytes or BytesIO
@@ -93,6 +97,7 @@ class NanoTDF:
93
97
 
94
98
  Raises:
95
99
  NanoTDFMaxSizeLimit: If the payload exceeds the maximum size
100
+
96
101
  """
97
102
  if isinstance(payload, BytesIO):
98
103
  payload = payload.getvalue()
@@ -101,14 +106,14 @@ class NanoTDF:
101
106
  return payload
102
107
 
103
108
  def _prepare_policy_data(self, config: NanoTDFConfig) -> tuple[bytes, str]:
104
- """
105
- Prepare policy data from configuration.
109
+ """Prepare policy data from configuration.
106
110
 
107
111
  Args:
108
112
  config: NanoTDFConfig configuration
109
113
 
110
114
  Returns:
111
115
  tuple: (policy_body, policy_type)
116
+
112
117
  """
113
118
  attributes = config.attributes if config.attributes else []
114
119
  policy_object = self._create_policy_object(attributes)
@@ -150,8 +155,7 @@ class NanoTDF:
150
155
  config: NanoTDFConfig,
151
156
  ephemeral_public_key: bytes | None = None,
152
157
  ) -> bytes:
153
- """
154
- Create the NanoTDF header.
158
+ """Create the NanoTDF header.
155
159
 
156
160
  Args:
157
161
  policy_body: The policy body bytes
@@ -161,6 +165,7 @@ class NanoTDF:
161
165
 
162
166
  Returns:
163
167
  bytes: The header bytes
168
+
164
169
  """
165
170
  from otdf_python.header import Header # Local import to avoid circular import
166
171
 
@@ -228,8 +233,7 @@ class NanoTDF:
228
233
  return self.MAGIC_NUMBER_AND_VERSION + header_bytes
229
234
 
230
235
  def _is_ec_key(self, key_pem: str) -> bool:
231
- """
232
- Detect if a PEM key is an EC key (vs RSA).
236
+ """Detect if a PEM key is an EC key (vs RSA).
233
237
 
234
238
  Args:
235
239
  key_pem: PEM-formatted key string
@@ -239,6 +243,7 @@ class NanoTDF:
239
243
 
240
244
  Raises:
241
245
  SDKException: If key cannot be parsed
246
+
242
247
  """
243
248
  try:
244
249
  # Try to load as public key first
@@ -260,13 +265,12 @@ class NanoTDF:
260
265
  else:
261
266
  raise SDKException("Invalid PEM format - no BEGIN header found")
262
267
  except Exception as e:
263
- raise SDKException(f"Failed to detect key type: {e}")
268
+ raise SDKException(f"Failed to detect key type: {e}") from e
264
269
 
265
270
  def _derive_key_with_ecdh( # noqa: C901
266
271
  self, config: NanoTDFConfig
267
272
  ) -> tuple[bytes, bytes | None, bytes | None]:
268
- """
269
- Derive encryption key using ECDH if KAS public key is provided or can be fetched.
273
+ """Derive encryption key using ECDH if KAS public key is provided or can be fetched.
270
274
 
271
275
  This implements the NanoTDF spec's ECDH + HKDF key derivation:
272
276
  1. Generate ephemeral keypair
@@ -283,6 +287,7 @@ class NanoTDF:
283
287
  - derived_key: 32-byte AES-256 key for encrypting the payload
284
288
  - ephemeral_public_key_compressed: Compressed ephemeral public key to store in header (None for RSA)
285
289
  - kas_public_key: KAS public key PEM string (or None if not available)
290
+
286
291
  """
287
292
  import logging
288
293
 
@@ -384,8 +389,7 @@ class NanoTDF:
384
389
  return derived_key, ephemeral_public_key_compressed, kas_public_key
385
390
 
386
391
  def _encrypt_payload(self, payload: bytes, key: bytes) -> tuple[bytes, bytes]:
387
- """
388
- Encrypt the payload using AES-GCM.
392
+ """Encrypt the payload using AES-GCM.
389
393
 
390
394
  Args:
391
395
  payload: The payload to encrypt
@@ -393,6 +397,7 @@ class NanoTDF:
393
397
 
394
398
  Returns:
395
399
  tuple: (iv, ciphertext)
400
+
396
401
  """
397
402
  iv = secrets.token_bytes(self.K_NANOTDF_IV_SIZE)
398
403
  iv_padded = self.K_EMPTY_IV[: self.K_IV_PADDING] + iv
@@ -403,8 +408,7 @@ class NanoTDF:
403
408
  def create_nano_tdf(
404
409
  self, payload: bytes | BytesIO, output_stream: BinaryIO, config: NanoTDFConfig
405
410
  ) -> int:
406
- """
407
- Stream-based NanoTDF creation - writes encrypted payload to an output stream.
411
+ """Stream-based NanoTDF creation - writes encrypted payload to an output stream.
408
412
 
409
413
  For convenience method that returns bytes, use create_nanotdf() instead.
410
414
  Supports ECDH key derivation if KAS info with public key is provided in config.
@@ -422,8 +426,8 @@ class NanoTDF:
422
426
  UnsupportedNanoTDFFeature: If an unsupported feature is requested
423
427
  InvalidNanoTDFConfig: If the configuration is invalid
424
428
  SDKException: For other errors
425
- """
426
429
 
430
+ """
427
431
  # Process payload and validate size
428
432
  payload = self._prepare_payload(payload)
429
433
 
@@ -557,8 +561,7 @@ class NanoTDF:
557
561
  output_stream: BinaryIO,
558
562
  config: NanoTDFConfig,
559
563
  ) -> None:
560
- """
561
- Stream-based NanoTDF decryption - writes decrypted payload to an output stream.
564
+ """Stream-based NanoTDF decryption - writes decrypted payload to an output stream.
562
565
 
563
566
  For convenience method that returns bytes, use read_nanotdf() instead.
564
567
  Supports ECDH key derivation and KAS key unwrapping.
@@ -571,6 +574,7 @@ class NanoTDF:
571
574
  Raises:
572
575
  InvalidNanoTDFConfig: If the NanoTDF format is invalid or config is missing required info
573
576
  SDKException: For other errors
577
+
574
578
  """
575
579
  # Convert to bytes if BytesIO
576
580
  if isinstance(nano_tdf_data, BytesIO):
@@ -582,7 +586,7 @@ class NanoTDF:
582
586
  header_len = Header.peek_length(nano_tdf_data)
583
587
  header_obj = Header.from_bytes(nano_tdf_data[:header_len])
584
588
  except Exception as e:
585
- raise InvalidNanoTDFConfig(f"Failed to parse NanoTDF header: {e}")
589
+ raise InvalidNanoTDFConfig(f"Failed to parse NanoTDF header: {e}") from e
586
590
 
587
591
  # Read payload section per NanoTDF spec:
588
592
  # [3 bytes: length] [3 bytes: IV] [variable: ciphertext] [tag]
@@ -768,8 +772,7 @@ class NanoTDF:
768
772
  return key, config
769
773
 
770
774
  def create_nanotdf(self, data: bytes, config: dict | NanoTDFConfig) -> bytes:
771
- """
772
- Convenience method - creates a NanoTDF and returns the encrypted bytes.
775
+ """Create a NanoTDF and return the encrypted bytes.
773
776
 
774
777
  For stream-based version, use create_nano_tdf() instead.
775
778
  """
@@ -846,8 +849,7 @@ class NanoTDF:
846
849
  def read_nanotdf(
847
850
  self, nanotdf_bytes: bytes, config: dict | NanoTDFConfig | None = None
848
851
  ) -> bytes:
849
- """
850
- Convenience method - decrypts a NanoTDF and returns the plaintext bytes.
852
+ """Decrypt a NanoTDF and return the plaintext bytes.
851
853
 
852
854
  For stream-based version, use read_nano_tdf() instead.
853
855
  """
@@ -1,6 +1,4 @@
1
- """
2
- NanoTDF ECDSA Signature Structure.
3
- """
1
+ """NanoTDF ECDSA Signature Structure."""
4
2
 
5
3
  from dataclasses import dataclass, field
6
4
 
@@ -8,13 +6,10 @@ from dataclasses import dataclass, field
8
6
  class IncorrectNanoTDFECDSASignatureSize(Exception):
9
7
  """Exception raised when the signature size is incorrect."""
10
8
 
11
- pass
12
-
13
9
 
14
10
  @dataclass
15
11
  class NanoTDFECDSAStruct:
16
- """
17
- Class to handle ECDSA signature structure for NanoTDF.
12
+ """Class to handle ECDSA signature structure for NanoTDF.
18
13
 
19
14
  This structure represents an ECDSA signature as required by the NanoTDF format.
20
15
  It consists of r and s values along with their lengths.
@@ -29,8 +24,7 @@ class NanoTDFECDSAStruct:
29
24
  def from_bytes(
30
25
  cls, ecdsa_signature_value: bytes, key_size: int
31
26
  ) -> "NanoTDFECDSAStruct":
32
- """
33
- Create a NanoTDFECDSAStruct from a byte array.
27
+ """Create a NanoTDFECDSAStruct from a byte array.
34
28
 
35
29
  Args:
36
30
  ecdsa_signature_value: The signature value as bytes
@@ -41,6 +35,7 @@ class NanoTDFECDSAStruct:
41
35
 
42
36
  Raises:
43
37
  IncorrectNanoTDFECDSASignatureSize: If the signature buffer size is invalid
38
+
44
39
  """
45
40
  if len(ecdsa_signature_value) != (2 * key_size) + 2:
46
41
  raise IncorrectNanoTDFECDSASignatureSize(
@@ -72,8 +67,7 @@ class NanoTDFECDSAStruct:
72
67
  return struct_obj
73
68
 
74
69
  def as_bytes(self) -> bytes:
75
- """
76
- Convert the signature structure to bytes.
70
+ """Convert the signature structure to bytes.
77
71
  Raises ValueError if r_value or s_value is None.
78
72
  """
79
73
  if self.r_value is None or self.s_value is None:
@@ -1,10 +1,14 @@
1
+ """NanoTDF type enumeration."""
2
+
1
3
  from enum import Enum
2
4
 
3
5
 
4
6
  class ECCurve(Enum):
7
+ """Elliptic curve enumeration for NanoTDF."""
8
+
5
9
  SECP256R1 = "secp256r1"
6
10
  SECP384R1 = "secp384r1"
7
- SECP521R1 = "secp384r1"
11
+ SECP521R1 = "secp521r1"
8
12
  SECP256K1 = "secp256k1"
9
13
 
10
14
  def __str__(self):
@@ -12,11 +16,15 @@ class ECCurve(Enum):
12
16
 
13
17
 
14
18
  class Protocol(Enum):
19
+ """Protocol enumeration for KAS communication."""
20
+
15
21
  HTTP = "HTTP"
16
22
  HTTPS = "HTTPS"
17
23
 
18
24
 
19
25
  class IdentifierType(Enum):
26
+ """Identifier type enumeration for NanoTDF."""
27
+
20
28
  NONE = 0
21
29
  TWO_BYTES = 2
22
30
  EIGHT_BYTES = 8
@@ -27,6 +35,8 @@ class IdentifierType(Enum):
27
35
 
28
36
 
29
37
  class PolicyType(Enum):
38
+ """Policy type enumeration for NanoTDF."""
39
+
30
40
  REMOTE_POLICY = 0
31
41
  EMBEDDED_POLICY_PLAIN_TEXT = 1
32
42
  EMBEDDED_POLICY_ENCRYPTED = 2
@@ -34,6 +44,8 @@ class PolicyType(Enum):
34
44
 
35
45
 
36
46
  class Cipher(Enum):
47
+ """Cipher enumeration for NanoTDF encryption."""
48
+
37
49
  AES_256_GCM_64_TAG = 0
38
50
  AES_256_GCM_96_TAG = 1
39
51
  AES_256_GCM_104_TAG = 2
@@ -1,21 +1,23 @@
1
+ """Policy binding serialization for HMAC calculation."""
2
+
1
3
  from typing import Any
2
4
 
3
5
 
4
6
  class PolicyBinding:
5
- """
6
- Represents a policy binding in the TDF manifest.
7
+ """Represents a policy binding in the TDF manifest.
8
+
7
9
  This is a placeholder implementation as the complete details of
8
10
  the PolicyBinding class aren't provided in the code snippets.
9
11
  """
10
12
 
11
13
  def __init__(self, **kwargs):
14
+ """Initialize policy binding from kwargs."""
12
15
  for key, value in kwargs.items():
13
16
  setattr(self, key, value)
14
17
 
15
18
 
16
19
  class PolicyBindingSerializer:
17
- """
18
- Handles serialization and deserialization of policy bindings.
20
+ """Handles serialization and deserialization of policy bindings.
19
21
  This class provides static methods to convert between JSON representations
20
22
  and PolicyBinding objects.
21
23
  """
@@ -1,9 +1,15 @@
1
+ """Policy information handling for NanoTDF."""
2
+
3
+
1
4
  class PolicyInfo:
5
+ """Policy information."""
6
+
2
7
  def __init__(
3
8
  self,
4
9
  policy_type: int = 0,
5
10
  body: bytes | None = None,
6
11
  ):
12
+ """Initialize policy information."""
7
13
  self.policy_type = policy_type
8
14
  self.body = body
9
15
 
@@ -1,8 +1,12 @@
1
+ """Policy object dataclasses for OpenTDF."""
2
+
1
3
  from dataclasses import dataclass
2
4
 
3
5
 
4
6
  @dataclass
5
7
  class AttributeObject:
8
+ """An attribute object."""
9
+
6
10
  attribute: str
7
11
  display_name: str | None = None
8
12
  is_default: bool = False
@@ -12,11 +16,15 @@ class AttributeObject:
12
16
 
13
17
  @dataclass
14
18
  class PolicyBody:
19
+ """A policy body."""
20
+
15
21
  data_attributes: list[AttributeObject]
16
22
  dissem: list[str]
17
23
 
18
24
 
19
25
  @dataclass
20
26
  class PolicyObject:
27
+ """A policy object."""
28
+
21
29
  uuid: str
22
30
  body: PolicyBody
@@ -1,2 +1,4 @@
1
+ """Policy UUID constants for OpenTDF."""
2
+
1
3
  # TODO: Replace this with a proper Policy UUID values
2
4
  NULL_POLICY_UUID: str = "00000000-0000-0000-0000-000000000000"
@@ -1,15 +1,19 @@
1
+ """NanoTDF resource locator handling."""
2
+
3
+
1
4
  class ResourceLocator:
2
- """
3
- NanoTDF Resource Locator per the spec:
4
- https://github.com/opentdf/spec/blob/main/schema/nanotdf/README.md
5
+ """Represent NanoTDF Resource Locator per specification.
6
+
7
+ See https://github.com/opentdf/spec/blob/main/schema/nanotdf/README.md
5
8
 
6
9
  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)
10
+ - Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
11
+ - Protocol: 0x0=HTTP, 0x1=HTTPS, 0xF=Shared Resource Directory
12
+ - Identifier: 0x0=None, 0x1=2 bytes, 0x2=8 bytes, 0x3=32 bytes
13
+ - Byte 1: Body Length (1-255 bytes)
14
+ - Bytes 2-N: Body (URL path)
15
+ - Bytes N+1-M: Identifier (optional, 0/2/8/32 bytes)
16
+
13
17
  """
14
18
 
15
19
  # Protocol enum values
@@ -24,6 +28,13 @@ class ResourceLocator:
24
28
  IDENTIFIER_32_BYTES = 0x3
25
29
 
26
30
  def __init__(self, resource_url: str | None = None, identifier: str | None = None):
31
+ """Initialize resource locator.
32
+
33
+ Args:
34
+ resource_url: URL of the resource
35
+ identifier: Optional identifier for the resource
36
+
37
+ """
27
38
  self.resource_url = resource_url or ""
28
39
  self.identifier = identifier or ""
29
40
 
@@ -71,8 +82,7 @@ class ResourceLocator:
71
82
  raise ValueError(f"Identifier too long: {id_len} bytes (max 32)")
72
83
 
73
84
  def to_bytes(self):
74
- """
75
- Convert to NanoTDF Resource Locator format per spec.
85
+ """Convert to NanoTDF Resource Locator format per spec.
76
86
 
77
87
  Format:
78
88
  - Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
@@ -106,8 +116,7 @@ class ResourceLocator:
106
116
 
107
117
  @staticmethod
108
118
  def from_bytes_with_size(buffer: bytes): # noqa: C901
109
- """
110
- Parse NanoTDF Resource Locator from bytes per spec.
119
+ """Parse NanoTDF Resource Locator from bytes per spec.
111
120
 
112
121
  Format:
113
122
  - Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)