otdf-python 0.3.5__py3-none-any.whl → 0.4.1__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/__init__.py +1 -2
- otdf_python/__main__.py +1 -2
- otdf_python/address_normalizer.py +8 -10
- otdf_python/aesgcm.py +8 -0
- otdf_python/assertion_config.py +21 -0
- otdf_python/asym_crypto.py +18 -22
- otdf_python/auth_headers.py +7 -6
- otdf_python/autoconfigure_utils.py +22 -6
- otdf_python/cli.py +5 -5
- otdf_python/collection_store.py +13 -0
- otdf_python/collection_store_impl.py +5 -0
- otdf_python/config.py +13 -0
- otdf_python/connect_client.py +1 -0
- otdf_python/constants.py +2 -0
- otdf_python/crypto_utils.py +4 -0
- otdf_python/dpop.py +3 -5
- otdf_python/ecc_constants.py +12 -14
- otdf_python/ecc_mode.py +7 -2
- otdf_python/ecdh.py +24 -25
- otdf_python/eckeypair.py +5 -0
- otdf_python/header.py +5 -0
- otdf_python/invalid_zip_exception.py +6 -2
- otdf_python/kas_client.py +48 -55
- otdf_python/kas_connect_rpc_client.py +16 -19
- otdf_python/kas_info.py +4 -3
- otdf_python/kas_key_cache.py +10 -9
- otdf_python/key_type.py +4 -0
- otdf_python/key_type_constants.py +4 -11
- otdf_python/manifest.py +24 -0
- otdf_python/nanotdf.py +34 -24
- otdf_python/nanotdf_ecdsa_struct.py +5 -9
- otdf_python/nanotdf_type.py +12 -0
- otdf_python/policy_binding_serializer.py +6 -4
- otdf_python/policy_info.py +6 -0
- otdf_python/policy_object.py +8 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +22 -13
- otdf_python/sdk.py +49 -57
- otdf_python/sdk_builder.py +58 -41
- otdf_python/sdk_exceptions.py +11 -1
- otdf_python/symmetric_and_payload_config.py +6 -0
- otdf_python/tdf.py +47 -10
- otdf_python/tdf_reader.py +10 -13
- otdf_python/tdf_writer.py +5 -0
- otdf_python/token_source.py +4 -3
- otdf_python/version.py +5 -0
- otdf_python/zip_reader.py +10 -2
- otdf_python/zip_writer.py +11 -0
- {otdf_python-0.3.5.dist-info → otdf_python-0.4.1.dist-info}/METADATA +19 -2
- {otdf_python-0.3.5.dist-info → otdf_python-0.4.1.dist-info}/RECORD +52 -52
- {otdf_python-0.3.5.dist-info → otdf_python-0.4.1.dist-info}/WHEEL +1 -1
- {otdf_python-0.3.5.dist-info → otdf_python-0.4.1.dist-info}/licenses/LICENSE +0 -0
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,32 @@ from .asym_crypto import AsymDecryption
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class NanoTDFException(SDKException):
|
|
29
|
+
"""Base exception for NanoTDF operations."""
|
|
30
|
+
|
|
27
31
|
pass
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
class NanoTDFMaxSizeLimit(NanoTDFException):
|
|
35
|
+
"""Exception for NanoTDF size limit exceeded."""
|
|
36
|
+
|
|
31
37
|
pass
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
class UnsupportedNanoTDFFeature(NanoTDFException):
|
|
41
|
+
"""Exception for unsupported NanoTDF features."""
|
|
42
|
+
|
|
35
43
|
pass
|
|
36
44
|
|
|
37
45
|
|
|
38
46
|
class InvalidNanoTDFConfig(NanoTDFException):
|
|
47
|
+
"""Exception for invalid NanoTDF configuration."""
|
|
48
|
+
|
|
39
49
|
pass
|
|
40
50
|
|
|
41
51
|
|
|
42
52
|
class NanoTDF:
|
|
53
|
+
"""NanoTDF reader and writer for compact TDF format."""
|
|
54
|
+
|
|
43
55
|
MAGIC_NUMBER_AND_VERSION = MAGIC_NUMBER_AND_VERSION
|
|
44
56
|
K_MAX_TDF_SIZE = (16 * 1024 * 1024) - 3 - 32
|
|
45
57
|
K_NANOTDF_GMAC_LENGTH = 8
|
|
@@ -48,6 +60,7 @@ class NanoTDF:
|
|
|
48
60
|
K_EMPTY_IV = bytes([0x0] * 12)
|
|
49
61
|
|
|
50
62
|
def __init__(self, services=None, collection_store: CollectionStore | None = None):
|
|
63
|
+
"""Initialize NanoTDF reader/writer."""
|
|
51
64
|
self.services = services
|
|
52
65
|
self.collection_store = collection_store or NoOpCollectionStore()
|
|
53
66
|
|
|
@@ -59,7 +72,7 @@ class NanoTDF:
|
|
|
59
72
|
return PolicyObject(uuid=policy_uuid, body=body)
|
|
60
73
|
|
|
61
74
|
def _serialize_policy_object(self, obj):
|
|
62
|
-
"""
|
|
75
|
+
"""Serialize policy object to compatible JSON format."""
|
|
63
76
|
from otdf_python.policy_object import AttributeObject, PolicyBody
|
|
64
77
|
|
|
65
78
|
if isinstance(obj, PolicyBody):
|
|
@@ -82,8 +95,7 @@ class NanoTDF:
|
|
|
82
95
|
return obj.__dict__
|
|
83
96
|
|
|
84
97
|
def _prepare_payload(self, payload: bytes | BytesIO) -> bytes:
|
|
85
|
-
"""
|
|
86
|
-
Convert BytesIO to bytes and validate payload size.
|
|
98
|
+
"""Convert BytesIO to bytes and validate payload size.
|
|
87
99
|
|
|
88
100
|
Args:
|
|
89
101
|
payload: The payload data as bytes or BytesIO
|
|
@@ -93,6 +105,7 @@ class NanoTDF:
|
|
|
93
105
|
|
|
94
106
|
Raises:
|
|
95
107
|
NanoTDFMaxSizeLimit: If the payload exceeds the maximum size
|
|
108
|
+
|
|
96
109
|
"""
|
|
97
110
|
if isinstance(payload, BytesIO):
|
|
98
111
|
payload = payload.getvalue()
|
|
@@ -101,14 +114,14 @@ class NanoTDF:
|
|
|
101
114
|
return payload
|
|
102
115
|
|
|
103
116
|
def _prepare_policy_data(self, config: NanoTDFConfig) -> tuple[bytes, str]:
|
|
104
|
-
"""
|
|
105
|
-
Prepare policy data from configuration.
|
|
117
|
+
"""Prepare policy data from configuration.
|
|
106
118
|
|
|
107
119
|
Args:
|
|
108
120
|
config: NanoTDFConfig configuration
|
|
109
121
|
|
|
110
122
|
Returns:
|
|
111
123
|
tuple: (policy_body, policy_type)
|
|
124
|
+
|
|
112
125
|
"""
|
|
113
126
|
attributes = config.attributes if config.attributes else []
|
|
114
127
|
policy_object = self._create_policy_object(attributes)
|
|
@@ -150,8 +163,7 @@ class NanoTDF:
|
|
|
150
163
|
config: NanoTDFConfig,
|
|
151
164
|
ephemeral_public_key: bytes | None = None,
|
|
152
165
|
) -> bytes:
|
|
153
|
-
"""
|
|
154
|
-
Create the NanoTDF header.
|
|
166
|
+
"""Create the NanoTDF header.
|
|
155
167
|
|
|
156
168
|
Args:
|
|
157
169
|
policy_body: The policy body bytes
|
|
@@ -161,6 +173,7 @@ class NanoTDF:
|
|
|
161
173
|
|
|
162
174
|
Returns:
|
|
163
175
|
bytes: The header bytes
|
|
176
|
+
|
|
164
177
|
"""
|
|
165
178
|
from otdf_python.header import Header # Local import to avoid circular import
|
|
166
179
|
|
|
@@ -228,8 +241,7 @@ class NanoTDF:
|
|
|
228
241
|
return self.MAGIC_NUMBER_AND_VERSION + header_bytes
|
|
229
242
|
|
|
230
243
|
def _is_ec_key(self, key_pem: str) -> bool:
|
|
231
|
-
"""
|
|
232
|
-
Detect if a PEM key is an EC key (vs RSA).
|
|
244
|
+
"""Detect if a PEM key is an EC key (vs RSA).
|
|
233
245
|
|
|
234
246
|
Args:
|
|
235
247
|
key_pem: PEM-formatted key string
|
|
@@ -239,6 +251,7 @@ class NanoTDF:
|
|
|
239
251
|
|
|
240
252
|
Raises:
|
|
241
253
|
SDKException: If key cannot be parsed
|
|
254
|
+
|
|
242
255
|
"""
|
|
243
256
|
try:
|
|
244
257
|
# Try to load as public key first
|
|
@@ -260,13 +273,12 @@ class NanoTDF:
|
|
|
260
273
|
else:
|
|
261
274
|
raise SDKException("Invalid PEM format - no BEGIN header found")
|
|
262
275
|
except Exception as e:
|
|
263
|
-
raise SDKException(f"Failed to detect key type: {e}")
|
|
276
|
+
raise SDKException(f"Failed to detect key type: {e}") from e
|
|
264
277
|
|
|
265
278
|
def _derive_key_with_ecdh( # noqa: C901
|
|
266
279
|
self, config: NanoTDFConfig
|
|
267
280
|
) -> tuple[bytes, bytes | None, bytes | None]:
|
|
268
|
-
"""
|
|
269
|
-
Derive encryption key using ECDH if KAS public key is provided or can be fetched.
|
|
281
|
+
"""Derive encryption key using ECDH if KAS public key is provided or can be fetched.
|
|
270
282
|
|
|
271
283
|
This implements the NanoTDF spec's ECDH + HKDF key derivation:
|
|
272
284
|
1. Generate ephemeral keypair
|
|
@@ -283,6 +295,7 @@ class NanoTDF:
|
|
|
283
295
|
- derived_key: 32-byte AES-256 key for encrypting the payload
|
|
284
296
|
- ephemeral_public_key_compressed: Compressed ephemeral public key to store in header (None for RSA)
|
|
285
297
|
- kas_public_key: KAS public key PEM string (or None if not available)
|
|
298
|
+
|
|
286
299
|
"""
|
|
287
300
|
import logging
|
|
288
301
|
|
|
@@ -384,8 +397,7 @@ class NanoTDF:
|
|
|
384
397
|
return derived_key, ephemeral_public_key_compressed, kas_public_key
|
|
385
398
|
|
|
386
399
|
def _encrypt_payload(self, payload: bytes, key: bytes) -> tuple[bytes, bytes]:
|
|
387
|
-
"""
|
|
388
|
-
Encrypt the payload using AES-GCM.
|
|
400
|
+
"""Encrypt the payload using AES-GCM.
|
|
389
401
|
|
|
390
402
|
Args:
|
|
391
403
|
payload: The payload to encrypt
|
|
@@ -393,6 +405,7 @@ class NanoTDF:
|
|
|
393
405
|
|
|
394
406
|
Returns:
|
|
395
407
|
tuple: (iv, ciphertext)
|
|
408
|
+
|
|
396
409
|
"""
|
|
397
410
|
iv = secrets.token_bytes(self.K_NANOTDF_IV_SIZE)
|
|
398
411
|
iv_padded = self.K_EMPTY_IV[: self.K_IV_PADDING] + iv
|
|
@@ -403,8 +416,7 @@ class NanoTDF:
|
|
|
403
416
|
def create_nano_tdf(
|
|
404
417
|
self, payload: bytes | BytesIO, output_stream: BinaryIO, config: NanoTDFConfig
|
|
405
418
|
) -> int:
|
|
406
|
-
"""
|
|
407
|
-
Stream-based NanoTDF creation - writes encrypted payload to an output stream.
|
|
419
|
+
"""Stream-based NanoTDF creation - writes encrypted payload to an output stream.
|
|
408
420
|
|
|
409
421
|
For convenience method that returns bytes, use create_nanotdf() instead.
|
|
410
422
|
Supports ECDH key derivation if KAS info with public key is provided in config.
|
|
@@ -422,8 +434,8 @@ class NanoTDF:
|
|
|
422
434
|
UnsupportedNanoTDFFeature: If an unsupported feature is requested
|
|
423
435
|
InvalidNanoTDFConfig: If the configuration is invalid
|
|
424
436
|
SDKException: For other errors
|
|
425
|
-
"""
|
|
426
437
|
|
|
438
|
+
"""
|
|
427
439
|
# Process payload and validate size
|
|
428
440
|
payload = self._prepare_payload(payload)
|
|
429
441
|
|
|
@@ -557,8 +569,7 @@ class NanoTDF:
|
|
|
557
569
|
output_stream: BinaryIO,
|
|
558
570
|
config: NanoTDFConfig,
|
|
559
571
|
) -> None:
|
|
560
|
-
"""
|
|
561
|
-
Stream-based NanoTDF decryption - writes decrypted payload to an output stream.
|
|
572
|
+
"""Stream-based NanoTDF decryption - writes decrypted payload to an output stream.
|
|
562
573
|
|
|
563
574
|
For convenience method that returns bytes, use read_nanotdf() instead.
|
|
564
575
|
Supports ECDH key derivation and KAS key unwrapping.
|
|
@@ -571,6 +582,7 @@ class NanoTDF:
|
|
|
571
582
|
Raises:
|
|
572
583
|
InvalidNanoTDFConfig: If the NanoTDF format is invalid or config is missing required info
|
|
573
584
|
SDKException: For other errors
|
|
585
|
+
|
|
574
586
|
"""
|
|
575
587
|
# Convert to bytes if BytesIO
|
|
576
588
|
if isinstance(nano_tdf_data, BytesIO):
|
|
@@ -582,7 +594,7 @@ class NanoTDF:
|
|
|
582
594
|
header_len = Header.peek_length(nano_tdf_data)
|
|
583
595
|
header_obj = Header.from_bytes(nano_tdf_data[:header_len])
|
|
584
596
|
except Exception as e:
|
|
585
|
-
raise InvalidNanoTDFConfig(f"Failed to parse NanoTDF header: {e}")
|
|
597
|
+
raise InvalidNanoTDFConfig(f"Failed to parse NanoTDF header: {e}") from e
|
|
586
598
|
|
|
587
599
|
# Read payload section per NanoTDF spec:
|
|
588
600
|
# [3 bytes: length] [3 bytes: IV] [variable: ciphertext] [tag]
|
|
@@ -768,8 +780,7 @@ class NanoTDF:
|
|
|
768
780
|
return key, config
|
|
769
781
|
|
|
770
782
|
def create_nanotdf(self, data: bytes, config: dict | NanoTDFConfig) -> bytes:
|
|
771
|
-
"""
|
|
772
|
-
Convenience method - creates a NanoTDF and returns the encrypted bytes.
|
|
783
|
+
"""Create a NanoTDF and return the encrypted bytes.
|
|
773
784
|
|
|
774
785
|
For stream-based version, use create_nano_tdf() instead.
|
|
775
786
|
"""
|
|
@@ -846,8 +857,7 @@ class NanoTDF:
|
|
|
846
857
|
def read_nanotdf(
|
|
847
858
|
self, nanotdf_bytes: bytes, config: dict | NanoTDFConfig | None = None
|
|
848
859
|
) -> bytes:
|
|
849
|
-
"""
|
|
850
|
-
Convenience method - decrypts a NanoTDF and returns the plaintext bytes.
|
|
860
|
+
"""Decrypt a NanoTDF and return the plaintext bytes.
|
|
851
861
|
|
|
852
862
|
For stream-based version, use read_nano_tdf() instead.
|
|
853
863
|
"""
|
|
@@ -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
|
|
|
@@ -13,8 +11,7 @@ class IncorrectNanoTDFECDSASignatureSize(Exception):
|
|
|
13
11
|
|
|
14
12
|
@dataclass
|
|
15
13
|
class NanoTDFECDSAStruct:
|
|
16
|
-
"""
|
|
17
|
-
Class to handle ECDSA signature structure for NanoTDF.
|
|
14
|
+
"""Class to handle ECDSA signature structure for NanoTDF.
|
|
18
15
|
|
|
19
16
|
This structure represents an ECDSA signature as required by the NanoTDF format.
|
|
20
17
|
It consists of r and s values along with their lengths.
|
|
@@ -29,8 +26,7 @@ class NanoTDFECDSAStruct:
|
|
|
29
26
|
def from_bytes(
|
|
30
27
|
cls, ecdsa_signature_value: bytes, key_size: int
|
|
31
28
|
) -> "NanoTDFECDSAStruct":
|
|
32
|
-
"""
|
|
33
|
-
Create a NanoTDFECDSAStruct from a byte array.
|
|
29
|
+
"""Create a NanoTDFECDSAStruct from a byte array.
|
|
34
30
|
|
|
35
31
|
Args:
|
|
36
32
|
ecdsa_signature_value: The signature value as bytes
|
|
@@ -41,6 +37,7 @@ class NanoTDFECDSAStruct:
|
|
|
41
37
|
|
|
42
38
|
Raises:
|
|
43
39
|
IncorrectNanoTDFECDSASignatureSize: If the signature buffer size is invalid
|
|
40
|
+
|
|
44
41
|
"""
|
|
45
42
|
if len(ecdsa_signature_value) != (2 * key_size) + 2:
|
|
46
43
|
raise IncorrectNanoTDFECDSASignatureSize(
|
|
@@ -72,8 +69,7 @@ class NanoTDFECDSAStruct:
|
|
|
72
69
|
return struct_obj
|
|
73
70
|
|
|
74
71
|
def as_bytes(self) -> bytes:
|
|
75
|
-
"""
|
|
76
|
-
Convert the signature structure to bytes.
|
|
72
|
+
"""Convert the signature structure to bytes.
|
|
77
73
|
Raises ValueError if r_value or s_value is None.
|
|
78
74
|
"""
|
|
79
75
|
if self.r_value is None or self.s_value is None:
|
otdf_python/nanotdf_type.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
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
11
|
SECP521R1 = "secp384r1"
|
|
@@ -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
|
-
|
|
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
|
"""
|
otdf_python/policy_info.py
CHANGED
|
@@ -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
|
|
otdf_python/policy_object.py
CHANGED
|
@@ -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
|
otdf_python/policy_stub.py
CHANGED
otdf_python/resource_locator.py
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
"""NanoTDF resource locator handling."""
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class ResourceLocator:
|
|
2
|
-
"""
|
|
3
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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)
|