otdf-python 0.1.10__py3-none-any.whl → 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otdf_python/__init__.py +25 -0
- otdf_python/__main__.py +12 -0
- otdf_python/address_normalizer.py +84 -0
- otdf_python/aesgcm.py +55 -0
- otdf_python/assertion_config.py +84 -0
- otdf_python/asym_crypto.py +198 -0
- otdf_python/auth_headers.py +33 -0
- otdf_python/autoconfigure_utils.py +113 -0
- otdf_python/cli.py +569 -0
- otdf_python/collection_store.py +41 -0
- otdf_python/collection_store_impl.py +22 -0
- otdf_python/config.py +69 -0
- otdf_python/connect_client.py +0 -0
- otdf_python/constants.py +1 -0
- otdf_python/crypto_utils.py +78 -0
- otdf_python/dpop.py +81 -0
- otdf_python/ecc_constants.py +176 -0
- otdf_python/ecc_mode.py +83 -0
- otdf_python/ecdh.py +317 -0
- otdf_python/eckeypair.py +75 -0
- otdf_python/header.py +181 -0
- otdf_python/invalid_zip_exception.py +8 -0
- otdf_python/kas_client.py +709 -0
- otdf_python/kas_connect_rpc_client.py +213 -0
- otdf_python/kas_info.py +25 -0
- otdf_python/kas_key_cache.py +52 -0
- otdf_python/key_type.py +31 -0
- otdf_python/key_type_constants.py +43 -0
- otdf_python/manifest.py +215 -0
- otdf_python/nanotdf.py +863 -0
- otdf_python/nanotdf_ecdsa_struct.py +132 -0
- otdf_python/nanotdf_type.py +43 -0
- otdf_python/policy_binding_serializer.py +39 -0
- otdf_python/policy_info.py +55 -0
- otdf_python/policy_object.py +22 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +172 -0
- otdf_python/sdk.py +436 -0
- otdf_python/sdk_builder.py +416 -0
- otdf_python/sdk_exceptions.py +16 -0
- otdf_python/symmetric_and_payload_config.py +30 -0
- otdf_python/tdf.py +480 -0
- otdf_python/tdf_reader.py +153 -0
- otdf_python/tdf_writer.py +23 -0
- otdf_python/token_source.py +34 -0
- otdf_python/version.py +57 -0
- otdf_python/zip_reader.py +47 -0
- otdf_python/zip_writer.py +70 -0
- otdf_python-0.3.5.dist-info/METADATA +153 -0
- otdf_python-0.3.5.dist-info/RECORD +137 -0
- {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info}/WHEEL +1 -2
- {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info/licenses}/LICENSE +1 -1
- otdf_python_proto/__init__.py +37 -0
- otdf_python_proto/authorization/__init__.py +1 -0
- otdf_python_proto/authorization/authorization_pb2.py +80 -0
- otdf_python_proto/authorization/authorization_pb2.pyi +161 -0
- otdf_python_proto/authorization/authorization_pb2_connect.py +191 -0
- otdf_python_proto/authorization/v2/authorization_pb2.py +105 -0
- otdf_python_proto/authorization/v2/authorization_pb2.pyi +134 -0
- otdf_python_proto/authorization/v2/authorization_pb2_connect.py +233 -0
- otdf_python_proto/common/__init__.py +1 -0
- otdf_python_proto/common/common_pb2.py +52 -0
- otdf_python_proto/common/common_pb2.pyi +61 -0
- otdf_python_proto/entity/__init__.py +1 -0
- otdf_python_proto/entity/entity_pb2.py +47 -0
- otdf_python_proto/entity/entity_pb2.pyi +50 -0
- otdf_python_proto/entityresolution/__init__.py +1 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.py +57 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.py +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/kas/__init__.py +9 -0
- otdf_python_proto/kas/kas_pb2.py +103 -0
- otdf_python_proto/kas/kas_pb2.pyi +170 -0
- otdf_python_proto/kas/kas_pb2_connect.py +192 -0
- otdf_python_proto/legacy_grpc/__init__.py +1 -0
- otdf_python_proto/legacy_grpc/authorization/authorization_pb2_grpc.py +163 -0
- otdf_python_proto/legacy_grpc/authorization/v2/authorization_pb2_grpc.py +206 -0
- otdf_python_proto/legacy_grpc/common/common_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entity/entity_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entityresolution/entity_resolution_pb2_grpc.py +122 -0
- otdf_python_proto/legacy_grpc/entityresolution/v2/entity_resolution_pb2_grpc.py +120 -0
- otdf_python_proto/legacy_grpc/kas/kas_pb2_grpc.py +172 -0
- otdf_python_proto/legacy_grpc/logger/audit/test_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/actions/actions_pb2_grpc.py +249 -0
- otdf_python_proto/legacy_grpc/policy/attributes/attributes_pb2_grpc.py +873 -0
- otdf_python_proto/legacy_grpc/policy/kasregistry/key_access_server_registry_pb2_grpc.py +602 -0
- otdf_python_proto/legacy_grpc/policy/keymanagement/key_management_pb2_grpc.py +251 -0
- otdf_python_proto/legacy_grpc/policy/namespaces/namespaces_pb2_grpc.py +427 -0
- otdf_python_proto/legacy_grpc/policy/objects_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/registeredresources/registered_resources_pb2_grpc.py +524 -0
- otdf_python_proto/legacy_grpc/policy/resourcemapping/resource_mapping_pb2_grpc.py +516 -0
- otdf_python_proto/legacy_grpc/policy/selectors_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/subjectmapping/subject_mapping_pb2_grpc.py +551 -0
- otdf_python_proto/legacy_grpc/policy/unsafe/unsafe_pb2_grpc.py +485 -0
- otdf_python_proto/legacy_grpc/wellknownconfiguration/wellknown_configuration_pb2_grpc.py +77 -0
- otdf_python_proto/logger/__init__.py +1 -0
- otdf_python_proto/logger/audit/test_pb2.py +43 -0
- otdf_python_proto/logger/audit/test_pb2.pyi +45 -0
- otdf_python_proto/policy/__init__.py +1 -0
- otdf_python_proto/policy/actions/actions_pb2.py +75 -0
- otdf_python_proto/policy/actions/actions_pb2.pyi +87 -0
- otdf_python_proto/policy/actions/actions_pb2_connect.py +275 -0
- otdf_python_proto/policy/attributes/attributes_pb2.py +234 -0
- otdf_python_proto/policy/attributes/attributes_pb2.pyi +328 -0
- otdf_python_proto/policy/attributes/attributes_pb2_connect.py +863 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.py +266 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.pyi +450 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +611 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.py +79 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.pyi +87 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +275 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.py +117 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +147 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +443 -0
- otdf_python_proto/policy/objects_pb2.py +150 -0
- otdf_python_proto/policy/objects_pb2.pyi +464 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.py +139 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.pyi +196 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +527 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.py +139 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.pyi +194 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +527 -0
- otdf_python_proto/policy/selectors_pb2.py +57 -0
- otdf_python_proto/policy/selectors_pb2.pyi +90 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.py +127 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.pyi +189 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +569 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.py +113 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +145 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +485 -0
- otdf_python_proto/wellknownconfiguration/__init__.py +1 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +51 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +32 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +107 -0
- otdf_python/_gotdf_python.cpython-312-darwin.so +0 -0
- otdf_python/build.py +0 -190
- otdf_python/go.py +0 -1478
- otdf_python/gotdf_python.py +0 -383
- otdf_python-0.1.10.dist-info/METADATA +0 -149
- otdf_python-0.1.10.dist-info/RECORD +0 -10
- otdf_python-0.1.10.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NanoTDF ECDSA Signature Structure.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IncorrectNanoTDFECDSASignatureSize(Exception):
|
|
9
|
+
"""Exception raised when the signature size is incorrect."""
|
|
10
|
+
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class NanoTDFECDSAStruct:
|
|
16
|
+
"""
|
|
17
|
+
Class to handle ECDSA signature structure for NanoTDF.
|
|
18
|
+
|
|
19
|
+
This structure represents an ECDSA signature as required by the NanoTDF format.
|
|
20
|
+
It consists of r and s values along with their lengths.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
r_length: bytearray = field(default_factory=lambda: bytearray(1))
|
|
24
|
+
r_value: bytearray = None
|
|
25
|
+
s_length: bytearray = field(default_factory=lambda: bytearray(1))
|
|
26
|
+
s_value: bytearray = None
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_bytes(
|
|
30
|
+
cls, ecdsa_signature_value: bytes, key_size: int
|
|
31
|
+
) -> "NanoTDFECDSAStruct":
|
|
32
|
+
"""
|
|
33
|
+
Create a NanoTDFECDSAStruct from a byte array.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
ecdsa_signature_value: The signature value as bytes
|
|
37
|
+
key_size: The size of the key in bytes
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A new NanoTDFECDSAStruct
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
IncorrectNanoTDFECDSASignatureSize: If the signature buffer size is invalid
|
|
44
|
+
"""
|
|
45
|
+
if len(ecdsa_signature_value) != (2 * key_size) + 2:
|
|
46
|
+
raise IncorrectNanoTDFECDSASignatureSize(
|
|
47
|
+
f"Invalid signature buffer size. Expected {(2 * key_size) + 2}, got {len(ecdsa_signature_value)}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
struct_obj = cls()
|
|
51
|
+
|
|
52
|
+
# Copy value of r_length to signature struct
|
|
53
|
+
index = 0
|
|
54
|
+
struct_obj.r_length[0] = ecdsa_signature_value[index]
|
|
55
|
+
|
|
56
|
+
# Copy the contents of r_value to signature struct
|
|
57
|
+
index += 1
|
|
58
|
+
r_len = struct_obj.r_length[0]
|
|
59
|
+
struct_obj.r_value = bytearray(key_size)
|
|
60
|
+
struct_obj.r_value[:r_len] = ecdsa_signature_value[index : index + r_len]
|
|
61
|
+
|
|
62
|
+
# Copy value of s_length to signature struct
|
|
63
|
+
index += key_size
|
|
64
|
+
struct_obj.s_length[0] = ecdsa_signature_value[index]
|
|
65
|
+
|
|
66
|
+
# Copy value of s_value
|
|
67
|
+
index += 1
|
|
68
|
+
s_len = struct_obj.s_length[0]
|
|
69
|
+
struct_obj.s_value = bytearray(key_size)
|
|
70
|
+
struct_obj.s_value[:s_len] = ecdsa_signature_value[index : index + s_len]
|
|
71
|
+
|
|
72
|
+
return struct_obj
|
|
73
|
+
|
|
74
|
+
def as_bytes(self) -> bytes:
|
|
75
|
+
"""
|
|
76
|
+
Convert the signature structure to bytes.
|
|
77
|
+
Raises ValueError if r_value or s_value is None.
|
|
78
|
+
"""
|
|
79
|
+
if self.r_value is None or self.s_value is None:
|
|
80
|
+
raise ValueError("r_value and s_value must not be None")
|
|
81
|
+
total_size = 1 + len(self.r_value) + 1 + len(self.s_value)
|
|
82
|
+
signature = bytearray(total_size)
|
|
83
|
+
|
|
84
|
+
# Copy value of r_length
|
|
85
|
+
index = 0
|
|
86
|
+
signature[index] = self.r_length[0]
|
|
87
|
+
|
|
88
|
+
# Copy the contents of r_value
|
|
89
|
+
index += 1
|
|
90
|
+
signature[index : index + len(self.r_value)] = self.r_value
|
|
91
|
+
|
|
92
|
+
# Copy value of s_length
|
|
93
|
+
index += len(self.r_value)
|
|
94
|
+
signature[index] = self.s_length[0]
|
|
95
|
+
|
|
96
|
+
# Copy value of s_value
|
|
97
|
+
index += 1
|
|
98
|
+
signature[index : index + len(self.s_value)] = self.s_value
|
|
99
|
+
|
|
100
|
+
return bytes(signature)
|
|
101
|
+
|
|
102
|
+
def get_s_value(self) -> bytearray:
|
|
103
|
+
"""Get the s value of the signature."""
|
|
104
|
+
return self.s_value
|
|
105
|
+
|
|
106
|
+
def set_s_value(self, s_value: bytearray) -> None:
|
|
107
|
+
"""Set the s value of the signature."""
|
|
108
|
+
self.s_value = s_value
|
|
109
|
+
|
|
110
|
+
def get_s_length(self) -> int:
|
|
111
|
+
"""Get the length of the s value."""
|
|
112
|
+
return self.s_length[0]
|
|
113
|
+
|
|
114
|
+
def set_s_length(self, s_length: int) -> None:
|
|
115
|
+
"""Set the length of the s value."""
|
|
116
|
+
self.s_length[0] = s_length
|
|
117
|
+
|
|
118
|
+
def get_r_value(self) -> bytearray:
|
|
119
|
+
"""Get the r value of the signature."""
|
|
120
|
+
return self.r_value
|
|
121
|
+
|
|
122
|
+
def set_r_value(self, r_value: bytearray) -> None:
|
|
123
|
+
"""Set the r value of the signature."""
|
|
124
|
+
self.r_value = r_value
|
|
125
|
+
|
|
126
|
+
def get_r_length(self) -> int:
|
|
127
|
+
"""Get the length of the r value."""
|
|
128
|
+
return self.r_length[0]
|
|
129
|
+
|
|
130
|
+
def set_r_length(self, r_length: int) -> None:
|
|
131
|
+
"""Set the length of the r value."""
|
|
132
|
+
self.r_length[0] = r_length
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ECCurve(Enum):
|
|
5
|
+
SECP256R1 = "secp256r1"
|
|
6
|
+
SECP384R1 = "secp384r1"
|
|
7
|
+
SECP521R1 = "secp384r1"
|
|
8
|
+
SECP256K1 = "secp256k1"
|
|
9
|
+
|
|
10
|
+
def __str__(self):
|
|
11
|
+
return self.value
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Protocol(Enum):
|
|
15
|
+
HTTP = "HTTP"
|
|
16
|
+
HTTPS = "HTTPS"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IdentifierType(Enum):
|
|
20
|
+
NONE = 0
|
|
21
|
+
TWO_BYTES = 2
|
|
22
|
+
EIGHT_BYTES = 8
|
|
23
|
+
THIRTY_TWO_BYTES = 32
|
|
24
|
+
|
|
25
|
+
def get_length(self):
|
|
26
|
+
return self.value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PolicyType(Enum):
|
|
30
|
+
REMOTE_POLICY = 0
|
|
31
|
+
EMBEDDED_POLICY_PLAIN_TEXT = 1
|
|
32
|
+
EMBEDDED_POLICY_ENCRYPTED = 2
|
|
33
|
+
EMBEDDED_POLICY_ENCRYPTED_POLICY_KEY_ACCESS = 3
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Cipher(Enum):
|
|
37
|
+
AES_256_GCM_64_TAG = 0
|
|
38
|
+
AES_256_GCM_96_TAG = 1
|
|
39
|
+
AES_256_GCM_104_TAG = 2
|
|
40
|
+
AES_256_GCM_112_TAG = 3
|
|
41
|
+
AES_256_GCM_120_TAG = 4
|
|
42
|
+
AES_256_GCM_128_TAG = 5
|
|
43
|
+
EAD_AES_256_HMAC_SHA_256 = 6
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PolicyBinding:
|
|
5
|
+
"""
|
|
6
|
+
Represents a policy binding in the TDF manifest.
|
|
7
|
+
This is a placeholder implementation as the complete details of
|
|
8
|
+
the PolicyBinding class aren't provided in the code snippets.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, **kwargs):
|
|
12
|
+
for key, value in kwargs.items():
|
|
13
|
+
setattr(self, key, value)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PolicyBindingSerializer:
|
|
17
|
+
"""
|
|
18
|
+
Handles serialization and deserialization of policy bindings.
|
|
19
|
+
This class provides static methods to convert between JSON representations
|
|
20
|
+
and PolicyBinding objects.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def deserialize(
|
|
25
|
+
json_data: Any, typeofT: type | None = None, context: Any = None
|
|
26
|
+
) -> Any:
|
|
27
|
+
if isinstance(json_data, dict):
|
|
28
|
+
return PolicyBinding(**json_data)
|
|
29
|
+
if isinstance(json_data, str):
|
|
30
|
+
return json_data
|
|
31
|
+
raise ValueError("Invalid type for PolicyBinding deserialization")
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def serialize(
|
|
35
|
+
src: Any, typeofSrc: type | None = None, context: Any = None
|
|
36
|
+
) -> dict | str:
|
|
37
|
+
if isinstance(src, PolicyBinding):
|
|
38
|
+
return vars(src)
|
|
39
|
+
return str(src)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class PolicyInfo:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
policy_type: int = 0,
|
|
5
|
+
body: bytes | None = None,
|
|
6
|
+
):
|
|
7
|
+
self.policy_type = policy_type
|
|
8
|
+
self.body = body
|
|
9
|
+
|
|
10
|
+
def set_embedded_plain_text_policy(self, body: bytes):
|
|
11
|
+
self.body = body
|
|
12
|
+
self.policy_type = 1 # Placeholder for EMBEDDED_POLICY_PLAIN_TEXT
|
|
13
|
+
|
|
14
|
+
def set_embedded_encrypted_text_policy(self, body: bytes):
|
|
15
|
+
self.body = body
|
|
16
|
+
self.policy_type = 2 # Placeholder for EMBEDDED_POLICY_ENCRYPTED
|
|
17
|
+
|
|
18
|
+
def get_body(self) -> bytes | None:
|
|
19
|
+
return self.body
|
|
20
|
+
|
|
21
|
+
def get_total_size(self) -> int:
|
|
22
|
+
size = 1 # policy_type
|
|
23
|
+
size += 2 # body_len
|
|
24
|
+
size += len(self.body) if self.body else 0
|
|
25
|
+
return size
|
|
26
|
+
|
|
27
|
+
def write_into_buffer(self, buffer: bytearray, offset: int = 0) -> int:
|
|
28
|
+
start = offset
|
|
29
|
+
buffer[offset] = self.policy_type
|
|
30
|
+
offset += 1
|
|
31
|
+
body_len = len(self.body) if self.body else 0
|
|
32
|
+
buffer[offset : offset + 2] = body_len.to_bytes(2, "big")
|
|
33
|
+
offset += 2
|
|
34
|
+
if self.body:
|
|
35
|
+
buffer[offset : offset + body_len] = self.body
|
|
36
|
+
offset += body_len
|
|
37
|
+
return offset - start
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def from_bytes_with_size(buffer: bytes, ecc_mode):
|
|
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
|
|
43
|
+
offset = 0
|
|
44
|
+
if len(buffer) < 3:
|
|
45
|
+
raise ValueError("Buffer too short for PolicyInfo header")
|
|
46
|
+
policy_type = buffer[offset]
|
|
47
|
+
offset += 1
|
|
48
|
+
body_len = int.from_bytes(buffer[offset : offset + 2], "big")
|
|
49
|
+
offset += 2
|
|
50
|
+
if len(buffer) < offset + body_len:
|
|
51
|
+
raise ValueError("Buffer too short for PolicyInfo body")
|
|
52
|
+
body = buffer[offset : offset + body_len]
|
|
53
|
+
offset += body_len
|
|
54
|
+
pi = PolicyInfo(policy_type=policy_type, body=body)
|
|
55
|
+
return pi, offset
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class AttributeObject:
|
|
6
|
+
attribute: str
|
|
7
|
+
display_name: str | None = None
|
|
8
|
+
is_default: bool = False
|
|
9
|
+
pub_key: str | None = None
|
|
10
|
+
kas_url: str | None = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class PolicyBody:
|
|
15
|
+
data_attributes: list[AttributeObject]
|
|
16
|
+
dissem: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class PolicyObject:
|
|
21
|
+
uuid: str
|
|
22
|
+
body: PolicyBody
|
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
|
|
26
|
+
def __init__(self, resource_url: str | None = None, identifier: str | None = None):
|
|
27
|
+
self.resource_url = resource_url or ""
|
|
28
|
+
self.identifier = identifier or ""
|
|
29
|
+
|
|
30
|
+
def get_resource_url(self):
|
|
31
|
+
return self.resource_url
|
|
32
|
+
|
|
33
|
+
def get_identifier(self):
|
|
34
|
+
return self.identifier
|
|
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
|
+
|
|
73
|
+
def to_bytes(self):
|
|
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
|
|
98
|
+
|
|
99
|
+
def get_total_size(self) -> int:
|
|
100
|
+
return len(self.to_bytes())
|
|
101
|
+
|
|
102
|
+
def write_into_buffer(self, buffer: bytearray, offset: int = 0) -> int:
|
|
103
|
+
data = self.to_bytes()
|
|
104
|
+
buffer[offset : offset + len(data)] = data
|
|
105
|
+
return len(data)
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
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
|
+
"""
|
|
118
|
+
if len(buffer) < 2:
|
|
119
|
+
raise ValueError("Buffer too short for ResourceLocator")
|
|
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
|
|
172
|
+
return ResourceLocator(resource_url, identifier), size
|