otdf-python 0.9.0__py3-none-any.whl → 0.10.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.
Files changed (52) hide show
  1. {otdf_python-0.9.0.dist-info → otdf_python-0.10.0.dist-info}/METADATA +1 -1
  2. {otdf_python-0.9.0.dist-info → otdf_python-0.10.0.dist-info}/RECORD +3 -52
  3. {otdf_python-0.9.0.dist-info → otdf_python-0.10.0.dist-info}/WHEEL +1 -1
  4. otdf_python/__init__.py +0 -24
  5. otdf_python/__main__.py +0 -11
  6. otdf_python/address_normalizer.py +0 -82
  7. otdf_python/aesgcm.py +0 -63
  8. otdf_python/assertion_config.py +0 -105
  9. otdf_python/asym_crypto.py +0 -194
  10. otdf_python/auth_headers.py +0 -35
  11. otdf_python/autoconfigure_utils.py +0 -127
  12. otdf_python/cli.py +0 -598
  13. otdf_python/collection_store.py +0 -53
  14. otdf_python/collection_store_impl.py +0 -27
  15. otdf_python/config.py +0 -82
  16. otdf_python/connect_client.py +0 -1
  17. otdf_python/constants.py +0 -3
  18. otdf_python/crypto_utils.py +0 -82
  19. otdf_python/dpop.py +0 -79
  20. otdf_python/ecc_constants.py +0 -174
  21. otdf_python/ecc_mode.py +0 -88
  22. otdf_python/ecdh.py +0 -310
  23. otdf_python/eckeypair.py +0 -80
  24. otdf_python/header.py +0 -186
  25. otdf_python/invalid_zip_exception.py +0 -12
  26. otdf_python/kas_allowlist.py +0 -182
  27. otdf_python/kas_client.py +0 -748
  28. otdf_python/kas_connect_rpc_client.py +0 -251
  29. otdf_python/kas_info.py +0 -27
  30. otdf_python/kas_key_cache.py +0 -53
  31. otdf_python/key_type.py +0 -35
  32. otdf_python/key_type_constants.py +0 -37
  33. otdf_python/manifest.py +0 -239
  34. otdf_python/nanotdf.py +0 -863
  35. otdf_python/nanotdf_ecdsa_struct.py +0 -127
  36. otdf_python/nanotdf_type.py +0 -55
  37. otdf_python/policy_binding_serializer.py +0 -42
  38. otdf_python/policy_info.py +0 -61
  39. otdf_python/policy_object.py +0 -30
  40. otdf_python/policy_stub.py +0 -4
  41. otdf_python/resource_locator.py +0 -181
  42. otdf_python/sdk.py +0 -446
  43. otdf_python/sdk_builder.py +0 -513
  44. otdf_python/sdk_exceptions.py +0 -26
  45. otdf_python/symmetric_and_payload_config.py +0 -36
  46. otdf_python/tdf.py +0 -517
  47. otdf_python/tdf_reader.py +0 -151
  48. otdf_python/tdf_writer.py +0 -28
  49. otdf_python/token_source.py +0 -35
  50. otdf_python/version.py +0 -62
  51. otdf_python/zip_reader.py +0 -55
  52. otdf_python/zip_writer.py +0 -81
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otdf-python
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Unofficial OpenTDF SDK for Python
5
5
  Project-URL: Homepage, https://github.com/b-long/opentdf-python-sdk
6
6
  Project-URL: Repository, https://github.com/b-long/opentdf-python-sdk
@@ -1,52 +1,3 @@
1
- otdf_python/__init__.py,sha256=q93tKt2r5-_244iKPeJf4ja1ftlmOmdRQnXWAZsxISI,542
2
- otdf_python/__main__.py,sha256=VuImcvrzCHTYOwQ-yS9HQwKmF2O6lORf1psmOkdZIh0,275
3
- otdf_python/address_normalizer.py,sha256=7gigfxFIFnDILi8BGQipuLl-Cr71pwi5vE72hbpLdmI,2850
4
- otdf_python/aesgcm.py,sha256=jQgVSEeKM6MJuH2tvlClXIzvg11a5O_GEY9Sh56CrxE,1947
5
- otdf_python/assertion_config.py,sha256=rw0SIB3xG-nAeb5r_liuxLphU4tcj-zlq8rVvXncX-Y,2317
6
- otdf_python/asym_crypto.py,sha256=EYkMNhZJP5khH0IvICTOG2bMg_TMvd6wXDu5zW0jpj4,7234
7
- otdf_python/auth_headers.py,sha256=onS-fXsIZN4l33yQS9VspPvoiAnfM2B0nNAVL_YIGZ0,987
8
- otdf_python/autoconfigure_utils.py,sha256=W5aJ0tC7HWfGb_1Mva_oxgduUzSpPoyDeaq0rPwgPAs,3689
9
- otdf_python/cli.py,sha256=icooiGgRh8H1IlP-_iO7paLB60IXUSaSacODABeqrS4,21165
10
- otdf_python/collection_store.py,sha256=sYL6VMFDBfHfCCLk14iybeC_qoUlpJFB0wOMt1bdwpY,1429
11
- otdf_python/collection_store_impl.py,sha256=3RqO3rvDCosajKpuls5DiO2_SWYsNQul9_9L7n-lQ68,758
12
- otdf_python/config.py,sha256=l1Ykg1gFUrFZTnd6bwMI6oi_clR5uCZ_Y1qH7QKtW90,2523
13
- otdf_python/connect_client.py,sha256=TpHpcU2t19pSqWn30cqzfM48nDG590BfNTlDPgUu054,45
14
- otdf_python/constants.py,sha256=ArVgY9OF2fkVM7dg0-9N3xs0xmKy0DWDUlVcO0_n9PQ,102
15
- otdf_python/crypto_utils.py,sha256=x0cEltQDVW8bxJ9L555KQKJU4_dI5Uw2h7j9oQGNT38,2933
16
- otdf_python/dpop.py,sha256=-76xjKz9Alf529StB_jQWhr-gCAOBkpssaSYQw2EH1A,2295
17
- otdf_python/ecc_constants.py,sha256=rCVZCWZ9zhyq2sqnUGFadiZ1CwXXe5T9olDtFYCJCIs,5846
18
- otdf_python/ecc_mode.py,sha256=aEAISAZpXcS72gXRUlYgH8n8EJP7zIpOeSVPxUJdAG8,3163
19
- otdf_python/ecdh.py,sha256=fwxE80qFSIkfJeUz3GNhEndRKkZrBN06FE1gnvwUHHI,10201
20
- otdf_python/eckeypair.py,sha256=qcPKv0OS1lYxRICj9dhAW_eMz32anFBtpI8EJfXxpX0,2470
21
- otdf_python/header.py,sha256=peG14kE_KAUCW4fY82sqcWF5zTAVAnJfFXCHtC8Z0iQ,7189
22
- otdf_python/invalid_zip_exception.py,sha256=M_bAiXEjJdxPfA178YH-uHGRwMrNBKzjQzlQ54aDP2w,292
23
- otdf_python/kas_allowlist.py,sha256=0yX3J9J1od_ew0sp3pX6kfav39pTz7aBBpMnVkE5T7s,5884
24
- otdf_python/kas_client.py,sha256=kcLpdkFDN3LVmlmSuaEm6EKtPsbOK5RCsClPdm8bpAU,27536
25
- otdf_python/kas_connect_rpc_client.py,sha256=Y2w0SPh4QZxLLhneKky8N20jd9UQmShmAnLo_c9kOXk,9113
26
- otdf_python/kas_info.py,sha256=R_zfW8m2n2PT1-Enk6NG5K8YIjWvb1g-PQ6M0bZX-o8,739
27
- otdf_python/kas_key_cache.py,sha256=6hfzRAg9o_IfRErWSe-_gGTG9kRyYENMizMY1Shkmfk,1548
28
- otdf_python/key_type.py,sha256=2gQlXOj35J3ISCcWjU3sGYUxmlZR47BMq6Xr2yoKA8k,928
29
- otdf_python/key_type_constants.py,sha256=0aqG1N1Ay0Z8o5fsq7fAiJXQeD6k-fxp06If__BGcR4,955
30
- otdf_python/manifest.py,sha256=aglGw9EdtZZIxmwqy82sV5wum_mKkjzew4brSgxmJjc,7047
31
- otdf_python/nanotdf.py,sha256=y1mLktlZ-mRCup2vzGY4ZbFeNEhWpCYLOZYjzTSPEtc,33921
32
- otdf_python/nanotdf_ecdsa_struct.py,sha256=C-qQnroJGO9yXrBb-p-rYMSxf2gS8kF6SCUvO-njAXU,4067
33
- otdf_python/nanotdf_type.py,sha256=3MQzT6lJ3WJKMICFyyYZXX2_cFYcZ5G4m1uif-l9Nxo,1112
34
- otdf_python/policy_binding_serializer.py,sha256=hA-M5BhEa3Q0bpjbpdiaJ6mivwx9UGDdmB8HvFG7SY0,1272
35
- otdf_python/policy_info.py,sha256=aq74dZg9PhTZ6cMkZyFsu3D754C5YijFMiuoYEL-1bY,2076
36
- otdf_python/policy_object.py,sha256=LikIsahPkKr-iYA0lhgQitCbh8CsmxUBYyBs6VYfmxY,512
37
- otdf_python/policy_stub.py,sha256=RfU_fICqsAOnTXOHpKhtKC0RJ3KoWhDxO0XecZWM548,159
38
- otdf_python/resource_locator.py,sha256=bjK935XcfNq-PyqidHNq8eIiPeZEStYdQvmQ9B9GY20,6290
39
- otdf_python/sdk.py,sha256=6yQbaHS6EUqiEOEn2hI3Ox4OYlYQHDuxjqHjYo8LV5A,15060
40
- otdf_python/sdk_builder.py,sha256=N57HrXADKLSP3bGiltYV058DxgrJyMzhsKuqNuKBqqo,17659
41
- otdf_python/sdk_exceptions.py,sha256=2GElGyM5LKN8Uh_lAiT6Ho4oNRWYRQsMNOK5s2jiv28,687
42
- otdf_python/symmetric_and_payload_config.py,sha256=iPHeZSeY9BjsQ-wkvdm6-FIR7773EgGiaIvSG-ICOHw,1158
43
- otdf_python/tdf.py,sha256=lqaSSGfd-nzlmjpYoXa6ZLihZIJA170dwodKt_iRko8,20799
44
- otdf_python/tdf_reader.py,sha256=f-LCb_Z4TwaByT8_gu-cpJIdW8lk3741jCAd-fJOLJg,5229
45
- otdf_python/tdf_writer.py,sha256=FLm1P26J4p6WPyKsjOb7QLYJqDIMDsBONqBW_JuFxyw,798
46
- otdf_python/token_source.py,sha256=Ow4bZqPqVxbZx64M7pLxQarVlPfiz15ZvZr9H17_ZMY,1021
47
- otdf_python/version.py,sha256=uDKJKsSQoaEH-JlqAwiXDxceLRX9hkG4I3NVLEfDCHA,2025
48
- otdf_python/zip_reader.py,sha256=qrHv-ecs09tz99ZKdOMiWaciYf2XsQOUTiXy1JHjuEY,1705
49
- otdf_python/zip_writer.py,sha256=5-KChgEXCf4TKAAML-R8cqpAiap85ur1l2lJCCac6BE,2405
50
1
  otdf_python_proto/__init__.py,sha256=dK3iYz7kos_DmjPzmboF1QkojrA0jFNFgmSEljgReR4,3211
51
2
  otdf_python_proto/authorization/__init__.py,sha256=PQ8pNK86JNhOg7rcvbOQzAx5nm0hdjQqj-xIwDD4izY,237
52
3
  otdf_python_proto/authorization/authorization_connect.py,sha256=sD5FEABMXq47ICNsdiwnAw9FjLHTvJAoZm4W4Q1VsIM,12521
@@ -170,6 +121,6 @@ otdf_python_proto/wellknownconfiguration/__init__.py,sha256=xK8XUrwCL9elWuMTx6vV
170
121
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_connect.py,sha256=HShWKFfd_7p1aZzqZKJiT1lDma0IINTB4kfv_DcrBA0,6585
171
122
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py,sha256=KXjTE12hr8GV7apJLTzv2VDBv8jw-74jZUjQnIzzP8g,3633
172
123
  otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi,sha256=H-Pjnl-fvckhUilvIHIuuSbHTDovejk-e7HFQ8f8eOc,1431
173
- otdf_python-0.9.0.dist-info/METADATA,sha256=xMgKnS-QPDi9n1_gUCzGD-SNWmpRWntqqYM-cLMo7Rs,5037
174
- otdf_python-0.9.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
175
- otdf_python-0.9.0.dist-info/RECORD,,
124
+ otdf_python-0.10.0.dist-info/METADATA,sha256=m0riOnyyHG_L-PJJNk9_al1LS02RSJqQ9515TbkcpnU,5038
125
+ otdf_python-0.10.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
126
+ otdf_python-0.10.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
otdf_python/__init__.py DELETED
@@ -1,24 +0,0 @@
1
- """OpenTDF Python SDK.
2
-
3
- A Python implementation of the OpenTDF SDK for working with Trusted Data Format (TDF) files.
4
- Provides both programmatic APIs and command-line interface for encryption and decryption.
5
- """
6
-
7
- from .cli import main as cli_main
8
- from .config import KASInfo, NanoTDFConfig, TDFConfig
9
- from .sdk import SDK
10
- from .sdk_builder import SDKBuilder
11
-
12
- __all__ = [
13
- "SDK",
14
- "KASInfo",
15
- "NanoTDFConfig",
16
- "SDKBuilder",
17
- "TDFConfig",
18
- "cli_main",
19
- ]
20
-
21
-
22
- def main() -> None:
23
- """Entry point for the CLI."""
24
- cli_main()
otdf_python/__main__.py DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Main entry point for running otdf_python as a module.
3
-
4
- This allows the package to be run with `python -m otdf_python` and properly
5
- handles the CLI interface without import conflicts.
6
- """
7
-
8
- from .cli import main
9
-
10
- if __name__ == "__main__":
11
- main()
@@ -1,82 +0,0 @@
1
- """Address normalization utilities for OpenTDF."""
2
-
3
- import logging
4
- import re
5
- from urllib.parse import urlparse
6
-
7
- from .sdk_exceptions import SDKException
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- def normalize_address(url_string: str, use_plaintext: bool) -> str:
13
- """Normalize a URL address to ensure it has the correct scheme and port.
14
-
15
- Args:
16
- url_string: The URL string to normalize
17
- use_plaintext: If True, use http scheme, otherwise use https
18
-
19
- Returns:
20
- The normalized URL string
21
-
22
- Raises:
23
- SDKException: If there's an error parsing or creating the URL
24
-
25
- """
26
- scheme = "http" if use_plaintext else "https"
27
-
28
- # Check if we have a host:port format without scheme (with non-digit port)
29
- host_port_pattern = re.match(r"^([^/:]+):([^/]+)$", url_string)
30
- if host_port_pattern:
31
- host = host_port_pattern.group(1)
32
- port_str = host_port_pattern.group(2)
33
- try:
34
- port = int(port_str)
35
- except ValueError as err:
36
- raise SDKException(f"Invalid port in URL [{url_string}]") from err
37
-
38
- normalized_url = f"{scheme}://{host}:{port}"
39
- logger.debug(f"normalized url [{url_string}] to [{normalized_url}]")
40
- return normalized_url
41
-
42
- try:
43
- # Check if we just have a hostname without scheme and port
44
- if "://" not in url_string and "/" not in url_string and ":" not in url_string:
45
- port = 80 if use_plaintext else 443
46
- normalized_url = f"{scheme}://{url_string}:{port}"
47
- logger.debug(f"normalized url [{url_string}] to [{normalized_url}]")
48
- return normalized_url
49
-
50
- # Parse the URL
51
- parsed_url = urlparse(url_string)
52
-
53
- # If no scheme, add one
54
- if not parsed_url.scheme:
55
- url_string = f"{scheme}://{url_string}"
56
- parsed_url = urlparse(url_string)
57
-
58
- # Extract host and port
59
- host = parsed_url.netloc.split(":")[0] if parsed_url.netloc else parsed_url.path
60
-
61
- # If there's a port in the URL, try to extract it
62
- port = None
63
- if ":" in parsed_url.netloc:
64
- _, port_str = parsed_url.netloc.split(":", 1)
65
- try:
66
- port = int(port_str)
67
- except ValueError as err:
68
- raise SDKException(f"Invalid port in URL [{url_string}]") from err
69
-
70
- # If no port was found or extracted, use the default
71
- if port is None:
72
- port = 80 if use_plaintext else 443
73
-
74
- # Reconstruct the URL with the desired scheme
75
- normalized_url = f"{scheme}://{host}:{port}"
76
- logger.debug(f"normalized url [{url_string}] to [{normalized_url}]")
77
- return normalized_url
78
-
79
- except Exception as e:
80
- if isinstance(e, SDKException):
81
- raise e
82
- raise SDKException(f"Error normalizing URL [{url_string}]: {e}") from e
otdf_python/aesgcm.py DELETED
@@ -1,63 +0,0 @@
1
- """AES-GCM encryption and decryption functionality."""
2
-
3
- import os
4
-
5
- from cryptography.hazmat.primitives.ciphers.aead import AESGCM
6
-
7
-
8
- class AesGcm:
9
- """AES-GCM encryption and decryption operations."""
10
-
11
- GCM_NONCE_LENGTH = 12
12
- GCM_TAG_LENGTH = 16
13
-
14
- def __init__(self, key: bytes):
15
- """Initialize AES-GCM cipher with key."""
16
- if not key or len(key) not in (16, 24, 32):
17
- raise ValueError("Invalid key size for GCM encryption")
18
- self.key = key
19
- self.aesgcm = AESGCM(key)
20
-
21
- def get_key(self) -> bytes:
22
- return self.key
23
-
24
- class Encrypted:
25
- """Encrypted data with initialization vector and ciphertext."""
26
-
27
- def __init__(self, iv: bytes, ciphertext: bytes):
28
- """Initialize encrypted data."""
29
- self.iv = iv
30
- self.ciphertext = ciphertext
31
-
32
- def as_bytes(self) -> bytes:
33
- return self.iv + self.ciphertext
34
-
35
- def encrypt(
36
- self, plaintext: bytes, offset: int = 0, length: int | None = None
37
- ) -> "AesGcm.Encrypted":
38
- if length is None:
39
- length = len(plaintext) - offset
40
- iv = os.urandom(self.GCM_NONCE_LENGTH)
41
- ct = self.aesgcm.encrypt(iv, plaintext[offset : offset + length], None)
42
- return self.Encrypted(iv, ct)
43
-
44
- def encrypt_with_iv(
45
- self,
46
- iv: bytes,
47
- auth_tag_len: int,
48
- plaintext: bytes,
49
- offset: int = 0,
50
- length: int | None = None,
51
- ) -> bytes:
52
- if length is None:
53
- length = len(plaintext) - offset
54
- ct = self.aesgcm.encrypt(iv, plaintext[offset : offset + length], None)
55
- return iv + ct
56
-
57
- def decrypt(self, encrypted: "AesGcm.Encrypted") -> bytes:
58
- return self.aesgcm.decrypt(encrypted.iv, encrypted.ciphertext, None)
59
-
60
- def decrypt_with_iv(
61
- self, iv: bytes, auth_tag_len: int, cipher_data: bytes
62
- ) -> bytes:
63
- return self.aesgcm.decrypt(iv, cipher_data, None)
@@ -1,105 +0,0 @@
1
- """Assertion configuration for TDF."""
2
-
3
- from enum import Enum, auto
4
- from typing import Any
5
-
6
-
7
- class Type(Enum):
8
- """Assertion type enumeration."""
9
-
10
- HANDLING_ASSERTION = "handling"
11
- BASE_ASSERTION = "base"
12
-
13
- def __str__(self):
14
- return self.value
15
-
16
-
17
- class Scope(Enum):
18
- """Assertion scope enumeration."""
19
-
20
- TRUSTED_DATA_OBJ = "tdo"
21
- PAYLOAD = "payload"
22
-
23
- def __str__(self):
24
- return self.value
25
-
26
-
27
- class AssertionKeyAlg(Enum):
28
- """Assertion key algorithm enumeration."""
29
-
30
- RS256 = auto()
31
- HS256 = auto()
32
- NOT_DEFINED = auto()
33
-
34
-
35
- class AppliesToState(Enum):
36
- """Assertion applies-to state enumeration."""
37
-
38
- ENCRYPTED = "encrypted"
39
- UNENCRYPTED = "unencrypted"
40
-
41
- def __str__(self):
42
- return self.value
43
-
44
-
45
- class BindingMethod(Enum):
46
- """Assertion binding method enumeration."""
47
-
48
- JWS = "jws"
49
-
50
- def __str__(self):
51
- return self.value
52
-
53
-
54
- class AssertionKey:
55
- """Assertion signing key configuration."""
56
-
57
- def __init__(self, alg: AssertionKeyAlg, key: Any):
58
- """Initialize assertion key."""
59
- self.alg = alg
60
- self.key = key
61
-
62
- def is_defined(self):
63
- return self.alg != AssertionKeyAlg.NOT_DEFINED
64
-
65
-
66
- class Statement:
67
- """Assertion statement with format, schema, and value."""
68
-
69
- def __init__(self, format: str, schema: str, value: str):
70
- """Initialize assertion statement."""
71
- self.format = format
72
- self.schema = schema
73
- self.value = value
74
-
75
- def __eq__(self, other):
76
- return (
77
- isinstance(other, Statement)
78
- and self.format == other.format
79
- and self.schema == other.schema
80
- and self.value == other.value
81
- )
82
-
83
- def __hash__(self):
84
- return hash((self.format, self.schema, self.value))
85
-
86
-
87
- class AssertionConfig:
88
- """TDF assertion configuration."""
89
-
90
- def __init__(
91
- self,
92
- id: str,
93
- type: Type,
94
- scope: Scope,
95
- applies_to_state: AppliesToState,
96
- statement: Statement,
97
- signing_key: AssertionKey | None = None,
98
- ):
99
- """Initialize assertion configuration."""
100
- self.id = id
101
- self.type = type
102
- self.scope = scope
103
- self.applies_to_state = applies_to_state
104
- self.statement = statement
105
- self.signing_key = signing_key
@@ -1,194 +0,0 @@
1
- """Asymmetric encryption and decryption utilities for RSA keys in PEM format."""
2
-
3
- import base64
4
- import re
5
-
6
- from cryptography.hazmat.backends import default_backend
7
- from cryptography.hazmat.primitives import hashes, serialization
8
- from cryptography.hazmat.primitives.asymmetric import padding, rsa
9
- from cryptography.x509 import load_pem_x509_certificate
10
-
11
- from .sdk_exceptions import SDKException
12
-
13
-
14
- class AsymDecryption:
15
- """Provides functionality for asymmetric decryption using an RSA private key.
16
-
17
- Supports both PEM string and key object initialization for flexibility.
18
- """
19
-
20
- CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
21
- PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----"
22
- PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----"
23
-
24
- def __init__(self, private_key_pem: str | None = None, private_key_obj=None):
25
- """Initialize with either a PEM string or a key object.
26
-
27
- Args:
28
- private_key_pem: Private key in PEM format (with or without headers)
29
- private_key_obj: Pre-loaded private key object from cryptography library
30
-
31
- Raises:
32
- SDKException: If key loading fails
33
-
34
- """
35
- if private_key_obj is not None:
36
- self.private_key = private_key_obj
37
- elif private_key_pem is not None:
38
- try:
39
- # Try direct PEM loading first (most common case)
40
- try:
41
- self.private_key = serialization.load_pem_private_key(
42
- private_key_pem.encode(),
43
- password=None,
44
- backend=default_backend(),
45
- )
46
- except Exception:
47
- # Fallback: strip headers and load as DER (for base64-only keys)
48
- private_key_pem = (
49
- private_key_pem.replace(self.PRIVATE_KEY_HEADER, "")
50
- .replace(self.PRIVATE_KEY_FOOTER, "")
51
- .replace("\n", "")
52
- .replace("\r", "")
53
- .replace(" ", "")
54
- )
55
- decoded = base64.b64decode(private_key_pem)
56
- self.private_key = serialization.load_der_private_key(
57
- decoded, password=None, backend=default_backend()
58
- )
59
- except Exception as e:
60
- raise SDKException(f"Failed to load private key: {e}") from e
61
- else:
62
- self.private_key = None
63
-
64
- def decrypt(self, data: bytes) -> bytes:
65
- """Decrypt data using RSA OAEP with SHA-1.
66
-
67
- Args:
68
- data: Encrypted bytes to decrypt
69
-
70
- Returns:
71
- Decrypted bytes
72
-
73
- Raises:
74
- SDKException: If decryption fails or key is not set
75
-
76
- """
77
- if self.private_key is None:
78
- raise SDKException("Failed to decrypt, private key is empty")
79
- try:
80
- return self.private_key.decrypt(
81
- data,
82
- padding.OAEP(
83
- mgf=padding.MGF1(algorithm=hashes.SHA1()),
84
- algorithm=hashes.SHA1(),
85
- label=None,
86
- ),
87
- )
88
- except Exception as e:
89
- raise SDKException(f"Error performing decryption: {e}") from e
90
-
91
-
92
- class AsymEncryption:
93
- """Provides functionality for asymmetric encryption using an RSA public key or certificate in PEM format.
94
-
95
- Supports PEM public keys, X.509 certificates, and pre-loaded key objects.
96
- Also handles base64-encoded keys without PEM headers.
97
- """
98
-
99
- PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----"
100
- PUBLIC_KEY_FOOTER = "-----END PUBLIC KEY-----"
101
- CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
102
-
103
- def __init__(self, public_key_pem: str | None = None, public_key_obj=None):
104
- """Initialize with either a PEM string or a key object.
105
-
106
- Args:
107
- public_key_pem: Public key in PEM format, X.509 certificate, or base64 string
108
- public_key_obj: Pre-loaded public key object from cryptography library
109
-
110
- Raises:
111
- SDKException: If key loading fails or key is not RSA
112
-
113
- """
114
- if public_key_obj is not None:
115
- self.public_key = public_key_obj
116
- elif public_key_pem is not None:
117
- try:
118
- if "BEGIN CERTIFICATE" in public_key_pem:
119
- # Load from X.509 certificate
120
- cert = load_pem_x509_certificate(
121
- public_key_pem.encode(), default_backend()
122
- )
123
- self.public_key = cert.public_key()
124
- else:
125
- # Try direct PEM loading first (most common case)
126
- try:
127
- self.public_key = serialization.load_pem_public_key(
128
- public_key_pem.encode(), backend=default_backend()
129
- )
130
- except Exception:
131
- # Fallback: strip headers and load as DER (for base64-only keys)
132
- pem_body = re.sub(r"-----BEGIN (.*)-----", "", public_key_pem)
133
- pem_body = re.sub(r"-----END (.*)-----", "", pem_body)
134
- pem_body = re.sub(r"\s", "", pem_body)
135
- decoded = base64.b64decode(pem_body)
136
- self.public_key = serialization.load_der_public_key(
137
- decoded, backend=default_backend()
138
- )
139
- except Exception as e:
140
- raise SDKException(f"Failed to load public key: {e}") from e
141
- else:
142
- self.public_key = None
143
-
144
- # Validate that it's an RSA key
145
- if self.public_key is not None and not isinstance(
146
- self.public_key, rsa.RSAPublicKey
147
- ):
148
- raise SDKException("Not an RSA PEM formatted public key")
149
-
150
- def encrypt(self, data: bytes) -> bytes:
151
- """Encrypt data using RSA OAEP with SHA-1.
152
-
153
- Args:
154
- data: Plaintext bytes to encrypt
155
-
156
- Returns:
157
- Encrypted bytes
158
-
159
- Raises:
160
- SDKException: If encryption fails or key is not set
161
-
162
- """
163
- if self.public_key is None:
164
- raise SDKException("Failed to encrypt, public key is empty")
165
- try:
166
- return self.public_key.encrypt(
167
- data,
168
- padding.OAEP(
169
- mgf=padding.MGF1(algorithm=hashes.SHA1()),
170
- algorithm=hashes.SHA1(),
171
- label=None,
172
- ),
173
- )
174
- except Exception as e:
175
- raise SDKException(f"Error performing encryption: {e}") from e
176
-
177
- def public_key_in_pem_format(self) -> str:
178
- """Export the public key to PEM format.
179
-
180
- Returns:
181
- Public key as PEM-encoded string
182
-
183
- Raises:
184
- SDKException: If export fails
185
-
186
- """
187
- try:
188
- pem = self.public_key.public_bytes(
189
- encoding=serialization.Encoding.PEM,
190
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
191
- )
192
- return pem.decode()
193
- except Exception as e:
194
- raise SDKException(f"Error exporting public key to PEM: {e}") from e
@@ -1,35 +0,0 @@
1
- """Authentication header management."""
2
-
3
- from dataclasses import dataclass
4
-
5
-
6
- @dataclass
7
- class AuthHeaders:
8
- """Represents authentication headers used in token-based authorization.
9
-
10
- This class holds authorization and DPoP (Demonstrating Proof of Possession) headers
11
- that are used in token-based API requests.
12
- """
13
-
14
- auth_header: str
15
- dpop_header: str = ""
16
-
17
- def get_auth_header(self) -> str:
18
- """Get the authorization header."""
19
- return self.auth_header
20
-
21
- def get_dpop_header(self) -> str:
22
- """Get the DPoP header."""
23
- return self.dpop_header
24
-
25
- def to_dict(self) -> dict[str, str]:
26
- """Convert authentication headers to a dictionary for use with HTTP clients.
27
-
28
- Returns:
29
- Dictionary with 'Authorization' header and optionally 'DPoP' header
30
-
31
- """
32
- headers = {"Authorization": self.auth_header}
33
- if self.dpop_header:
34
- headers["DPoP"] = self.dpop_header
35
- return headers