otdf-python 0.3.4__py3-none-any.whl → 0.4.0__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.
@@ -2,14 +2,10 @@ class PolicyInfo:
2
2
  def __init__(
3
3
  self,
4
4
  policy_type: int = 0,
5
- has_ecdsa_binding: bool = False,
6
5
  body: bytes | None = None,
7
- binding: bytes | None = None,
8
6
  ):
9
7
  self.policy_type = policy_type
10
- self.has_ecdsa_binding = has_ecdsa_binding
11
8
  self.body = body
12
- self.binding = binding
13
9
 
14
10
  def set_embedded_plain_text_policy(self, body: bytes):
15
11
  self.body = body
@@ -19,21 +15,13 @@ class PolicyInfo:
19
15
  self.body = body
20
16
  self.policy_type = 2 # Placeholder for EMBEDDED_POLICY_ENCRYPTED
21
17
 
22
- def set_policy_binding(self, binding: bytes):
23
- self.binding = binding
24
-
25
18
  def get_body(self) -> bytes | None:
26
19
  return self.body
27
20
 
28
- def get_binding(self) -> bytes | None:
29
- return self.binding
30
-
31
21
  def get_total_size(self) -> int:
32
22
  size = 1 # policy_type
33
23
  size += 2 # body_len
34
24
  size += len(self.body) if self.body else 0
35
- size += 1 # binding_len
36
- size += len(self.binding) if self.binding else 0
37
25
  return size
38
26
 
39
27
  def write_into_buffer(self, buffer: bytearray, offset: int = 0) -> int:
@@ -46,33 +34,22 @@ class PolicyInfo:
46
34
  if self.body:
47
35
  buffer[offset : offset + body_len] = self.body
48
36
  offset += body_len
49
- binding_len = len(self.binding) if self.binding else 0
50
- buffer[offset] = binding_len
51
- offset += 1
52
- if self.binding:
53
- buffer[offset : offset + binding_len] = self.binding
54
- offset += binding_len
55
37
  return offset - start
56
38
 
57
39
  @staticmethod
58
40
  def from_bytes_with_size(buffer: bytes, ecc_mode):
59
- # Based on Java implementation: parse policy_type (1 byte), body_len (2 bytes), body, binding_len (1 byte), binding
41
+ # Parse policy_type (1 byte), body_len (2 bytes), body
42
+ # Note: binding is NOT part of PolicyInfo - it's read separately in Header
60
43
  offset = 0
61
- if len(buffer) < 4:
44
+ if len(buffer) < 3:
62
45
  raise ValueError("Buffer too short for PolicyInfo header")
63
46
  policy_type = buffer[offset]
64
47
  offset += 1
65
48
  body_len = int.from_bytes(buffer[offset : offset + 2], "big")
66
49
  offset += 2
67
- if len(buffer) < offset + body_len + 1:
50
+ if len(buffer) < offset + body_len:
68
51
  raise ValueError("Buffer too short for PolicyInfo body")
69
52
  body = buffer[offset : offset + body_len]
70
53
  offset += body_len
71
- binding_len = buffer[offset]
72
- offset += 1
73
- if len(buffer) < offset + binding_len:
74
- raise ValueError("Buffer too short for PolicyInfo binding")
75
- binding = buffer[offset : offset + binding_len]
76
- offset += binding_len
77
- pi = PolicyInfo(policy_type=policy_type, body=body, binding=binding)
54
+ pi = PolicyInfo(policy_type=policy_type, body=body)
78
55
  return pi, offset
@@ -1,7 +1,31 @@
1
1
  class ResourceLocator:
2
+ """
3
+ NanoTDF Resource Locator per the spec:
4
+ https://github.com/opentdf/spec/blob/main/schema/nanotdf/README.md
5
+
6
+ Format:
7
+ - Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
8
+ - Protocol: 0x0=HTTP, 0x1=HTTPS, 0xF=Shared Resource Directory
9
+ - Identifier: 0x0=None, 0x1=2 bytes, 0x2=8 bytes, 0x3=32 bytes
10
+ - Byte 1: Body Length (1-255 bytes)
11
+ - Bytes 2-N: Body (URL path)
12
+ - Bytes N+1-M: Identifier (optional, 0/2/8/32 bytes)
13
+ """
14
+
15
+ # Protocol enum values
16
+ PROTOCOL_HTTP = 0x0
17
+ PROTOCOL_HTTPS = 0x1
18
+ PROTOCOL_SHARED_RESOURCE_DIR = 0xF
19
+
20
+ # Identifier length enum values (in bits 4-7)
21
+ IDENTIFIER_NONE = 0x0
22
+ IDENTIFIER_2_BYTES = 0x1
23
+ IDENTIFIER_8_BYTES = 0x2
24
+ IDENTIFIER_32_BYTES = 0x3
25
+
2
26
  def __init__(self, resource_url: str | None = None, identifier: str | None = None):
3
- self.resource_url = resource_url
4
- self.identifier = identifier
27
+ self.resource_url = resource_url or ""
28
+ self.identifier = identifier or ""
5
29
 
6
30
  def get_resource_url(self):
7
31
  return self.resource_url
@@ -9,13 +33,68 @@ class ResourceLocator:
9
33
  def get_identifier(self):
10
34
  return self.identifier
11
35
 
36
+ def _parse_url(self):
37
+ """Parse URL to extract protocol and body (path)."""
38
+ url = self.resource_url
39
+ if url.startswith("https://"):
40
+ protocol = self.PROTOCOL_HTTPS
41
+ body = url[8:] # Remove "https://"
42
+ elif url.startswith("http://"):
43
+ protocol = self.PROTOCOL_HTTP
44
+ body = url[7:] # Remove "http://"
45
+ else:
46
+ # Default to HTTP
47
+ protocol = self.PROTOCOL_HTTP
48
+ body = url
49
+ return protocol, body.encode()
50
+
51
+ def _get_identifier_bytes(self):
52
+ """Get identifier bytes and determine identifier length enum."""
53
+ if not self.identifier:
54
+ return self.IDENTIFIER_NONE, b""
55
+
56
+ id_bytes = self.identifier.encode()
57
+ id_len = len(id_bytes)
58
+
59
+ if id_len == 0:
60
+ return self.IDENTIFIER_NONE, b""
61
+ elif id_len <= 2:
62
+ # Pad to 2 bytes
63
+ return self.IDENTIFIER_2_BYTES, id_bytes.ljust(2, b"\x00")
64
+ elif id_len <= 8:
65
+ # Pad to 8 bytes
66
+ return self.IDENTIFIER_8_BYTES, id_bytes.ljust(8, b"\x00")
67
+ elif id_len <= 32:
68
+ # Pad to 32 bytes
69
+ return self.IDENTIFIER_32_BYTES, id_bytes.ljust(32, b"\x00")
70
+ else:
71
+ raise ValueError(f"Identifier too long: {id_len} bytes (max 32)")
72
+
12
73
  def to_bytes(self):
13
- # Based on Java implementation: [url_len][url_bytes][id_len][id_bytes], each len is 1 byte
14
- url_bytes = (self.resource_url or "").encode()
15
- id_bytes = (self.identifier or "").encode()
16
- if len(url_bytes) > 255 or len(id_bytes) > 255:
17
- raise ValueError("ResourceLocator fields too long for 1-byte length prefix")
18
- return bytes([len(url_bytes)]) + url_bytes + bytes([len(id_bytes)]) + id_bytes
74
+ """
75
+ Convert to NanoTDF Resource Locator format per spec.
76
+
77
+ Format:
78
+ - Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
79
+ - Byte 1: Body Length
80
+ - Bytes 2-N: Body (URL path)
81
+ - Bytes N+1-M: Identifier (0/2/8/32 bytes)
82
+ """
83
+ protocol, body_bytes = self._parse_url()
84
+ identifier_enum, identifier_bytes = self._get_identifier_bytes()
85
+
86
+ if len(body_bytes) > 255:
87
+ raise ValueError(
88
+ f"Resource Locator body too long: {len(body_bytes)} bytes (max 255)"
89
+ )
90
+
91
+ # Byte 0: protocol in bits 0-3, identifier length in bits 4-7
92
+ protocol_and_id = (identifier_enum << 4) | protocol
93
+
94
+ # Byte 1: body length
95
+ body_len = len(body_bytes)
96
+
97
+ return bytes([protocol_and_id, body_len]) + body_bytes + identifier_bytes
19
98
 
20
99
  def get_total_size(self) -> int:
21
100
  return len(self.to_bytes())
@@ -26,19 +105,68 @@ class ResourceLocator:
26
105
  return len(data)
27
106
 
28
107
  @staticmethod
29
- def from_bytes_with_size(buffer: bytes):
30
- # Based on Java implementation: [url_len][url_bytes][id_len][id_bytes]
108
+ def from_bytes_with_size(buffer: bytes): # noqa: C901
109
+ """
110
+ Parse NanoTDF Resource Locator from bytes per spec.
111
+
112
+ Format:
113
+ - Byte 0: Protocol Enum (bits 0-3) + Identifier Length (bits 4-7)
114
+ - Byte 1: Body Length
115
+ - Bytes 2-N: Body (URL path)
116
+ - Bytes N+1-M: Identifier (0/2/8/32 bytes)
117
+ """
31
118
  if len(buffer) < 2:
32
119
  raise ValueError("Buffer too short for ResourceLocator")
33
- url_len = buffer[0]
34
- if len(buffer) < 1 + url_len + 1:
35
- raise ValueError("Buffer too short for ResourceLocator url")
36
- url_bytes = buffer[1 : 1 + url_len]
37
- id_len = buffer[1 + url_len]
38
- if len(buffer) < 1 + url_len + 1 + id_len:
39
- raise ValueError("Buffer too short for ResourceLocator id")
40
- id_bytes = buffer[1 + url_len + 1 : 1 + url_len + 1 + id_len]
41
- resource_url = url_bytes.decode()
42
- identifier = id_bytes.decode()
43
- size = 1 + url_len + 1 + id_len
120
+
121
+ # Parse byte 0: protocol and identifier length
122
+ protocol_and_id = buffer[0]
123
+ protocol = protocol_and_id & 0x0F # Bits 0-3
124
+ identifier_enum = (protocol_and_id >> 4) & 0x0F # Bits 4-7
125
+
126
+ # Parse byte 1: body length
127
+ body_len = buffer[1]
128
+
129
+ if len(buffer) < 2 + body_len:
130
+ raise ValueError(
131
+ f"Buffer too short for ResourceLocator body (need {2 + body_len}, have {len(buffer)})"
132
+ )
133
+
134
+ # Parse body (URL path)
135
+ body_bytes = buffer[2 : 2 + body_len]
136
+ body = body_bytes.decode()
137
+
138
+ # Reconstruct full URL with protocol
139
+ if protocol == ResourceLocator.PROTOCOL_HTTPS:
140
+ resource_url = f"https://{body}"
141
+ elif protocol == ResourceLocator.PROTOCOL_HTTP:
142
+ resource_url = f"http://{body}"
143
+ else:
144
+ resource_url = body
145
+
146
+ # Parse identifier based on identifier_enum
147
+ offset = 2 + body_len
148
+ if identifier_enum == ResourceLocator.IDENTIFIER_NONE:
149
+ identifier_len = 0
150
+ elif identifier_enum == ResourceLocator.IDENTIFIER_2_BYTES:
151
+ identifier_len = 2
152
+ elif identifier_enum == ResourceLocator.IDENTIFIER_8_BYTES:
153
+ identifier_len = 8
154
+ elif identifier_enum == ResourceLocator.IDENTIFIER_32_BYTES:
155
+ identifier_len = 32
156
+ else:
157
+ raise ValueError(f"Invalid identifier length enum: {identifier_enum}")
158
+
159
+ if len(buffer) < offset + identifier_len:
160
+ raise ValueError(
161
+ f"Buffer too short for ResourceLocator identifier (need {offset + identifier_len}, have {len(buffer)})"
162
+ )
163
+
164
+ if identifier_len > 0:
165
+ identifier_bytes = buffer[offset : offset + identifier_len]
166
+ # Remove padding
167
+ identifier = identifier_bytes.rstrip(b"\x00").decode()
168
+ else:
169
+ identifier = ""
170
+
171
+ size = 2 + body_len + identifier_len
44
172
  return ResourceLocator(resource_url, identifier), size
otdf_python/sdk.py CHANGED
@@ -119,7 +119,7 @@ class KAS(AbstractContextManager):
119
119
  Unwrapped key as bytes
120
120
  """
121
121
  if mock and wrapped_key and kas_private_key:
122
- from .asym_decryption import AsymDecryption
122
+ from .asym_crypto import AsymDecryption
123
123
 
124
124
  asym = AsymDecryption(private_key_pem=kas_private_key)
125
125
  return asym.decrypt(wrapped_key)
otdf_python/tdf.py CHANGED
@@ -87,7 +87,7 @@ class TDF:
87
87
  import hashlib
88
88
  import hmac
89
89
 
90
- from otdf_python.asym_crypto import AsymEncryption
90
+ from .asym_crypto import AsymEncryption
91
91
 
92
92
  key_access_objs = []
93
93
  for kas in kas_infos:
@@ -188,7 +188,7 @@ class TDF:
188
188
  """
189
189
  Unwraps the key locally using a provided private key (used for testing)
190
190
  """
191
- from otdf_python.asym_decryption import AsymDecryption
191
+ from .asym_crypto import AsymDecryption
192
192
 
193
193
  key = None
194
194
  for ka in key_access_objs:
@@ -430,7 +430,8 @@ class TDF:
430
430
  import zipfile
431
431
 
432
432
  from otdf_python.aesgcm import AesGcm
433
- from otdf_python.asym_crypto import AsymDecryption
433
+
434
+ from .asym_crypto import AsymDecryption
434
435
 
435
436
  with zipfile.ZipFile(io.BytesIO(tdf_bytes), "r") as z:
436
437
  manifest_json = z.read("0.manifest.json").decode()
@@ -1,11 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otdf-python
3
- Version: 0.3.4
3
+ Version: 0.4.0
4
4
  Summary: Unofficial OpenTDF SDK for Python
5
+ Project-URL: Homepage, https://github.com/b-long/opentdf-python-sdk
6
+ Project-URL: Repository, https://github.com/b-long/opentdf-python-sdk
7
+ Project-URL: Issues, https://github.com/b-long/opentdf-python-sdk/issues
5
8
  Author-email: b-long <b-long@users.noreply.github.com>
6
9
  License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Security :: Cryptography
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
7
24
  Requires-Python: >=3.10
8
- Requires-Dist: connect-python[compiler]>=0.4.2
25
+ Requires-Dist: connect-python[compiler]<0.5,>=0.4.2
9
26
  Requires-Dist: cryptography>=45.0.4
10
27
  Requires-Dist: grpcio-status>=1.74.0
11
28
  Requires-Dist: grpcio-tools>=1.74.0
@@ -3,12 +3,10 @@ otdf_python/__main__.py,sha256=V9cmvhX9Ht2wpPOHGrw_onBogStJnuWTx9eyiezEDsQ,276
3
3
  otdf_python/address_normalizer.py,sha256=pv7RoG4Dwac6so7NK1zCsVZBNFCcQK3cj_4iW6FPnRw,2815
4
4
  otdf_python/aesgcm.py,sha256=KhMabIUYxxp1gUwdwNLiwD-SvmJPqW2PunbgeguMEh8,1666
5
5
  otdf_python/assertion_config.py,sha256=P2Pc1OxMk9Ln6bvp1ZI8j66Q7uoZoYB7oMB6WLG38Y8,1763
6
- otdf_python/asym_crypto.py,sha256=GzijIWYhWeazEwXYw-s8fRHVS7JLxDqHVcl7oRSOx_A,3008
7
- otdf_python/asym_decryption.py,sha256=eVfgfzFHMK4ni3g-u8fBe3sdF02eo_msZouo19UBlh4,1981
8
- otdf_python/asym_encryption.py,sha256=ex-S_PvBnWKSSvbocTjJ_p5VQjjeiThcc9bMRZqvifw,2932
6
+ otdf_python/asym_crypto.py,sha256=dwx3lUxUt3e8JA9z2lQrpsOBA3x36DN07bQbcVGZBvI,7251
9
7
  otdf_python/auth_headers.py,sha256=a0TQD36QKXO1G4swume2wx3Sk9zQsSWEAQJnST2DlH0,966
10
8
  otdf_python/autoconfigure_utils.py,sha256=NiNtbapBoIO-6kM59HRvX3Kj_Z0IRMrTkFlXjwuNfPc,3236
11
- otdf_python/cli.py,sha256=XotWLHHEhxwIB9vnwkR2leEV0Q_hhcdceSvcWXyAMrE,19460
9
+ otdf_python/cli.py,sha256=0wXan-QrKQllIlSCnXDsFAT1EQpH7gv7g6TZKIVGI8E,19883
12
10
  otdf_python/collection_store.py,sha256=MH1RxlevRVFj5lBS_6DN3zz5ZgOhBjD0P7AYBLBqS0o,1004
13
11
  otdf_python/collection_store_impl.py,sha256=g3YeSwMeXr1BNmwmFr75fv9ptPjieC36Bhct-Jf1trw,613
14
12
  otdf_python/config.py,sha256=uGYVByTWl7pWcKRER41ef-ay5VevIbOyUAEd6BIArVg,2185
@@ -16,30 +14,32 @@ otdf_python/connect_client.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
16
14
  otdf_python/constants.py,sha256=SFGjrzB1GK4CkSZgNlEUIAZMY37MelTxgc8ajXIAhoc,53
17
15
  otdf_python/crypto_utils.py,sha256=KsNss9EC-wHs1nCiKivDnxJSMv0uxGlWLDcCowadcV8,2837
18
16
  otdf_python/dpop.py,sha256=ssKXTGydJS--rBTCqX9Y8qy-c5Um4pIZPG3DZcj0rWU,2301
19
- otdf_python/ecc_mode.py,sha256=PUJJ6n0jk0IKKNVZ1_tWGPPujGR4fny4m5JBkUhPimo,1085
17
+ otdf_python/ecc_constants.py,sha256=uZkYeuhSnqCLjnrINOsaQQhdCKUTKLwL0t2Cswou4BA,5892
18
+ otdf_python/ecc_mode.py,sha256=8q-R_XR9WWmuzwI0gI6RQpsEzK8PD8Hi8uuDCvSsGXY,3077
19
+ otdf_python/ecdh.py,sha256=D8jHIZHYeQwCGCtjd3FJLXqIXnnYeEqSRLiWoiHZm1k,10233
20
20
  otdf_python/eckeypair.py,sha256=4uZfdJaqSxW605FhU58Gko06mixSNoDOGBgVpP5VlSQ,2324
21
- otdf_python/header.py,sha256=QGxqo5yfuJIFrEntLGoETYC8ihc9tszXc2IjtETCVmg,5379
21
+ otdf_python/header.py,sha256=0p259_GNO9aumUg0KOBZkiawKZTdFzoYVcyHaMCMVC8,7045
22
22
  otdf_python/invalid_zip_exception.py,sha256=YEU20hM5_yE-RWJncPZobcqj4a90RAuahB54VjAS2SU,213
23
- otdf_python/kas_client.py,sha256=vjOP3o7ip2cWqfE4FB1sHWqXWWY-aXPI-PnaKsPPEF4,22464
23
+ otdf_python/kas_client.py,sha256=NP8T6G5SWuciJbYnX0QNrGGlpMhqTYbcjED2tfiTr8U,25938
24
24
  otdf_python/kas_connect_rpc_client.py,sha256=LRhdFF6d8qz_BDFN4WMu0Kn-ZynvKMEWmuHYPj09A6Q,7862
25
25
  otdf_python/kas_info.py,sha256=STnyPo-Vu_rzVQRdQl8mUGPK8eZE2vY4oPkI-PqrZJM,687
26
26
  otdf_python/kas_key_cache.py,sha256=Ems2NyKC_3yh_xs8k4_n2gA3Jb-9nCeRRjIdz1LRC9I,1472
27
27
  otdf_python/key_type.py,sha256=cwhlh6DOFG5C8JlCxFqXYi0SJDlOLUkxceg7L8arFNk,816
28
28
  otdf_python/key_type_constants.py,sha256=ghridYdhFMLKDa_9Q5cplrl8DqdEgApd--20bJ-MBtA,1001
29
29
  otdf_python/manifest.py,sha256=Uv-Hvo9hOPLrSPTeKQpDvNeHzq7qSmJ5HYtkgGwVppE,6419
30
- otdf_python/nanotdf.py,sha256=zklLXXEbX5uHO11ebnI4pgo_bVp94I2deXKjN0tjExs,20214
30
+ otdf_python/nanotdf.py,sha256=3pQ5Lip_pExhdfj2wqm58RS1Tb7A9f7dcxGIzVXSDuQ,33828
31
31
  otdf_python/nanotdf_ecdsa_struct.py,sha256=nRScIJLeNcbxBBE9DlG15I7EXGTkH0ITib1ra-C1-uQ,4100
32
32
  otdf_python/nanotdf_type.py,sha256=40PyDd62iDwcfmS7rhUeQE2B0W6FYIeCpi5cDmMO7d4,819
33
33
  otdf_python/policy_binding_serializer.py,sha256=8d9MizhLTdy14ghdAvhXRBA8KzKt6Nf9kmmU9hoWhrQ,1169
34
- otdf_python/policy_info.py,sha256=n9hgdQrTRqPO7O1R90EUtIyoWNlaABAF7mRfbjIzaX8,2861
34
+ otdf_python/policy_info.py,sha256=HGAOkxyB1I0N8_nHpnFkJXYJaycYsetqvWFNTxwwPi0,1951
35
35
  otdf_python/policy_object.py,sha256=zzwHk6jhZwpiX2BWxTJ1kDl3cgFijQfQrKJP3OqI35Q,380
36
36
  otdf_python/policy_stub.py,sha256=BQn06Ye5MwCu9feJZFPmO_UTfhugtiQa539iBkSQwhQ,117
37
- otdf_python/resource_locator.py,sha256=zYN5yd9cGwN9SRQO2tUD1UXj2zkPl2apd-X4E1MrnDg,1879
38
- otdf_python/sdk.py,sha256=rihHwRwMzS1xu6eiJZ79Y2biDwPEZvqiQoFw97ma9-Q,14211
37
+ otdf_python/resource_locator.py,sha256=vrTWtkIh82Ba4KRS6k6Pm92lQG5KazIQ18xX4VoX86Y,6050
38
+ otdf_python/sdk.py,sha256=7KKsjlUXL8_rUFVKoHFW37jvzS9Pi8Cww86NPyp3Kjw,14207
39
39
  otdf_python/sdk_builder.py,sha256=jgswTxnAfKXDGqEOIQwEiE3S0Jh0w5HMtPxHwUFIu-U,14655
40
40
  otdf_python/sdk_exceptions.py,sha256=L_bYkkyF-hnEBFXfjT5C41i5lM-t8OJB5mU_1zYvfP8,479
41
41
  otdf_python/symmetric_and_payload_config.py,sha256=D_LArhk1gA5-tpUiaUZTTM90ZKdc3DCq1a6X8etCir8,992
42
- otdf_python/tdf.py,sha256=T4Wr7cc-QcQ607F4RfqWieTpQdaeOndGnLqx4Di9B_M,19648
42
+ otdf_python/tdf.py,sha256=WYfCVn7BAEBKTVEPT0SSYuCI0S6S_TgYB3C3FbHProU,19612
43
43
  otdf_python/tdf_reader.py,sha256=XQ3CuIXSOVRuBMYv2YIYlyaY-tHQA85HHHqNBoXNH9o,5267
44
44
  otdf_python/tdf_writer.py,sha256=DVEGEl8pyBm1JK2K-9BJKmT2TqvQXokPkthAQMwgCkk,645
45
45
  otdf_python/token_source.py,sha256=tfHWcIpPQ3FR45DUDiKz6q_vBaHH_HNifnnIpDVbMD8,923
@@ -131,7 +131,7 @@ otdf_python_proto/wellknownconfiguration/__init__.py,sha256=X3GeZJ1mG_N1MViryjkq
131
131
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py,sha256=g9xSm9TxX0IPMqiFCaridJvI2TrL8PrXVFPgu8tX9VM,3863
132
132
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi,sha256=Zw4vROvTgomnFqsalJrYda632ojXH0FVXSzTXxerybw,1490
133
133
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py,sha256=i9rGG2mgQZfk6xGCp1ywu4QqKWSiwpuLoNKGUwl43t8,5346
134
- otdf_python-0.3.4.dist-info/METADATA,sha256=3mt22hjKkKrXHv1GFyqQUO1JQTKdvDe4CsazN6x5pYY,4225
135
- otdf_python-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
136
- otdf_python-0.3.4.dist-info/licenses/LICENSE,sha256=DPrPHdI6tfZcqk9kzQ37vh1Ftk7LJYdMrUtwKl7L3Pw,1074
137
- otdf_python-0.3.4.dist-info/RECORD,,
134
+ otdf_python-0.4.0.dist-info/METADATA,sha256=Hpm2WiRbzRfr38wswo45o8X-p-R2Nlt8AAap09WxKiQ,5132
135
+ otdf_python-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
136
+ otdf_python-0.4.0.dist-info/licenses/LICENSE,sha256=DPrPHdI6tfZcqk9kzQ37vh1Ftk7LJYdMrUtwKl7L3Pw,1074
137
+ otdf_python-0.4.0.dist-info/RECORD,,
@@ -1,53 +0,0 @@
1
- import base64
2
-
3
- from cryptography.hazmat.backends import default_backend
4
- from cryptography.hazmat.primitives import hashes, serialization
5
- from cryptography.hazmat.primitives.asymmetric import padding
6
-
7
- from .sdk_exceptions import SDKException
8
-
9
-
10
- class AsymDecryption:
11
- """
12
- Class providing functionality for asymmetric decryption using an RSA private key.
13
- """
14
-
15
- CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
16
- PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----"
17
- PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----"
18
-
19
- def __init__(self, private_key_pem: str | None = None, private_key_obj=None):
20
- if private_key_obj is not None:
21
- self.private_key = private_key_obj
22
- elif private_key_pem is not None:
23
- try:
24
- private_key_pem = (
25
- private_key_pem.replace(self.PRIVATE_KEY_HEADER, "")
26
- .replace(self.PRIVATE_KEY_FOOTER, "")
27
- .replace("\n", "")
28
- .replace("\r", "")
29
- .replace(" ", "")
30
- )
31
- decoded = base64.b64decode(private_key_pem)
32
- self.private_key = serialization.load_der_private_key(
33
- decoded, password=None, backend=default_backend()
34
- )
35
- except Exception as e:
36
- raise SDKException(f"Failed to load private key: {e}")
37
- else:
38
- self.private_key = None
39
-
40
- def decrypt(self, data: bytes) -> bytes:
41
- if self.private_key is None:
42
- raise SDKException("Failed to decrypt, private key is empty")
43
- try:
44
- return self.private_key.decrypt(
45
- data,
46
- padding.OAEP(
47
- mgf=padding.MGF1(algorithm=hashes.SHA1()),
48
- algorithm=hashes.SHA1(),
49
- label=None,
50
- ),
51
- )
52
- except Exception as e:
53
- raise SDKException(f"Error performing decryption: {e}")
@@ -1,75 +0,0 @@
1
- import base64
2
- import re
3
-
4
- from cryptography.hazmat.backends import default_backend
5
- from cryptography.hazmat.primitives import hashes, serialization
6
- from cryptography.hazmat.primitives.asymmetric import padding
7
- from cryptography.x509 import load_pem_x509_certificate
8
-
9
- from .sdk_exceptions import SDKException
10
-
11
-
12
- class AsymEncryption:
13
- """
14
- Provides methods for asymmetric encryption and handling public keys in PEM format.
15
- """
16
-
17
- PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----"
18
- PUBLIC_KEY_FOOTER = "-----END PUBLIC KEY-----"
19
- CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
20
-
21
- def __init__(self, public_key_pem: str | None = None, public_key_obj=None):
22
- if public_key_obj is not None:
23
- self.public_key = public_key_obj
24
- elif public_key_pem is not None:
25
- try:
26
- if "BEGIN CERTIFICATE" in public_key_pem:
27
- cert = load_pem_x509_certificate(
28
- public_key_pem.encode(), default_backend()
29
- )
30
- self.public_key = cert.public_key()
31
- else:
32
- # Remove PEM headers/footers and whitespace
33
- pem_body = re.sub(r"-----BEGIN (.*)-----", "", public_key_pem)
34
- pem_body = re.sub(r"-----END (.*)-----", "", pem_body)
35
- pem_body = re.sub(r"\s", "", pem_body)
36
- decoded = base64.b64decode(pem_body)
37
- self.public_key = serialization.load_der_public_key(
38
- decoded, backend=default_backend()
39
- )
40
- except Exception as e:
41
- raise SDKException(f"Failed to load public key: {e}")
42
- else:
43
- self.public_key = None
44
-
45
- from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
46
-
47
- if self.public_key is not None and not isinstance(
48
- self.public_key, RSAPublicKey
49
- ):
50
- raise SDKException("Not an RSA PEM formatted public key")
51
-
52
- def encrypt(self, data: bytes) -> bytes:
53
- if self.public_key is None:
54
- raise SDKException("Failed to encrypt, public key is empty")
55
- try:
56
- return self.public_key.encrypt(
57
- data,
58
- padding.OAEP(
59
- mgf=padding.MGF1(algorithm=hashes.SHA1()),
60
- algorithm=hashes.SHA1(),
61
- label=None,
62
- ),
63
- )
64
- except Exception as e:
65
- raise SDKException(f"Error performing encryption: {e}")
66
-
67
- def public_key_in_pem_format(self) -> str:
68
- try:
69
- pem = self.public_key.public_bytes(
70
- encoding=serialization.Encoding.PEM,
71
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
72
- )
73
- return pem.decode()
74
- except Exception as e:
75
- raise SDKException(f"Error exporting public key to PEM: {e}")