otdf-python 0.1.10__py3-none-any.whl → 0.3.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 (144) hide show
  1. otdf_python/__init__.py +25 -0
  2. otdf_python/__main__.py +12 -0
  3. otdf_python/address_normalizer.py +84 -0
  4. otdf_python/aesgcm.py +55 -0
  5. otdf_python/assertion_config.py +84 -0
  6. otdf_python/asym_crypto.py +85 -0
  7. otdf_python/asym_decryption.py +53 -0
  8. otdf_python/asym_encryption.py +75 -0
  9. otdf_python/auth_headers.py +21 -0
  10. otdf_python/autoconfigure_utils.py +113 -0
  11. otdf_python/cli.py +570 -0
  12. otdf_python/collection_store.py +41 -0
  13. otdf_python/collection_store_impl.py +22 -0
  14. otdf_python/config.py +69 -0
  15. otdf_python/connect_client.py +0 -0
  16. otdf_python/constants.py +1 -0
  17. otdf_python/crypto_utils.py +78 -0
  18. otdf_python/dpop.py +81 -0
  19. otdf_python/ecc_mode.py +32 -0
  20. otdf_python/eckeypair.py +75 -0
  21. otdf_python/header.py +143 -0
  22. otdf_python/invalid_zip_exception.py +8 -0
  23. otdf_python/kas_client.py +603 -0
  24. otdf_python/kas_connect_rpc_client.py +207 -0
  25. otdf_python/kas_info.py +25 -0
  26. otdf_python/kas_key_cache.py +52 -0
  27. otdf_python/key_type.py +31 -0
  28. otdf_python/key_type_constants.py +43 -0
  29. otdf_python/manifest.py +215 -0
  30. otdf_python/nanotdf.py +553 -0
  31. otdf_python/nanotdf_ecdsa_struct.py +132 -0
  32. otdf_python/nanotdf_type.py +43 -0
  33. otdf_python/policy_binding_serializer.py +39 -0
  34. otdf_python/policy_info.py +78 -0
  35. otdf_python/policy_object.py +22 -0
  36. otdf_python/policy_stub.py +2 -0
  37. otdf_python/resource_locator.py +44 -0
  38. otdf_python/sdk.py +528 -0
  39. otdf_python/sdk_builder.py +448 -0
  40. otdf_python/sdk_exceptions.py +16 -0
  41. otdf_python/symmetric_and_payload_config.py +30 -0
  42. otdf_python/tdf.py +479 -0
  43. otdf_python/tdf_reader.py +153 -0
  44. otdf_python/tdf_writer.py +23 -0
  45. otdf_python/token_source.py +34 -0
  46. otdf_python/version.py +57 -0
  47. otdf_python/zip_reader.py +47 -0
  48. otdf_python/zip_writer.py +70 -0
  49. otdf_python-0.3.0.dist-info/METADATA +231 -0
  50. otdf_python-0.3.0.dist-info/RECORD +137 -0
  51. {otdf_python-0.1.10.dist-info → otdf_python-0.3.0.dist-info}/WHEEL +1 -2
  52. {otdf_python-0.1.10.dist-info → otdf_python-0.3.0.dist-info/licenses}/LICENSE +1 -1
  53. otdf_python_proto/__init__.py +37 -0
  54. otdf_python_proto/authorization/__init__.py +1 -0
  55. otdf_python_proto/authorization/authorization_pb2.py +80 -0
  56. otdf_python_proto/authorization/authorization_pb2.pyi +161 -0
  57. otdf_python_proto/authorization/authorization_pb2_connect.py +191 -0
  58. otdf_python_proto/authorization/v2/authorization_pb2.py +105 -0
  59. otdf_python_proto/authorization/v2/authorization_pb2.pyi +134 -0
  60. otdf_python_proto/authorization/v2/authorization_pb2_connect.py +233 -0
  61. otdf_python_proto/common/__init__.py +1 -0
  62. otdf_python_proto/common/common_pb2.py +52 -0
  63. otdf_python_proto/common/common_pb2.pyi +61 -0
  64. otdf_python_proto/entity/__init__.py +1 -0
  65. otdf_python_proto/entity/entity_pb2.py +47 -0
  66. otdf_python_proto/entity/entity_pb2.pyi +50 -0
  67. otdf_python_proto/entityresolution/__init__.py +1 -0
  68. otdf_python_proto/entityresolution/entity_resolution_pb2.py +57 -0
  69. otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +55 -0
  70. otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +149 -0
  71. otdf_python_proto/entityresolution/v2/entity_resolution_pb2.py +55 -0
  72. otdf_python_proto/entityresolution/v2/entity_resolution_pb2.pyi +55 -0
  73. otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +149 -0
  74. otdf_python_proto/kas/__init__.py +9 -0
  75. otdf_python_proto/kas/kas_pb2.py +103 -0
  76. otdf_python_proto/kas/kas_pb2.pyi +170 -0
  77. otdf_python_proto/kas/kas_pb2_connect.py +192 -0
  78. otdf_python_proto/legacy_grpc/__init__.py +1 -0
  79. otdf_python_proto/legacy_grpc/authorization/authorization_pb2_grpc.py +163 -0
  80. otdf_python_proto/legacy_grpc/authorization/v2/authorization_pb2_grpc.py +206 -0
  81. otdf_python_proto/legacy_grpc/common/common_pb2_grpc.py +4 -0
  82. otdf_python_proto/legacy_grpc/entity/entity_pb2_grpc.py +4 -0
  83. otdf_python_proto/legacy_grpc/entityresolution/entity_resolution_pb2_grpc.py +122 -0
  84. otdf_python_proto/legacy_grpc/entityresolution/v2/entity_resolution_pb2_grpc.py +120 -0
  85. otdf_python_proto/legacy_grpc/kas/kas_pb2_grpc.py +172 -0
  86. otdf_python_proto/legacy_grpc/logger/audit/test_pb2_grpc.py +4 -0
  87. otdf_python_proto/legacy_grpc/policy/actions/actions_pb2_grpc.py +249 -0
  88. otdf_python_proto/legacy_grpc/policy/attributes/attributes_pb2_grpc.py +873 -0
  89. otdf_python_proto/legacy_grpc/policy/kasregistry/key_access_server_registry_pb2_grpc.py +602 -0
  90. otdf_python_proto/legacy_grpc/policy/keymanagement/key_management_pb2_grpc.py +251 -0
  91. otdf_python_proto/legacy_grpc/policy/namespaces/namespaces_pb2_grpc.py +427 -0
  92. otdf_python_proto/legacy_grpc/policy/objects_pb2_grpc.py +4 -0
  93. otdf_python_proto/legacy_grpc/policy/registeredresources/registered_resources_pb2_grpc.py +524 -0
  94. otdf_python_proto/legacy_grpc/policy/resourcemapping/resource_mapping_pb2_grpc.py +516 -0
  95. otdf_python_proto/legacy_grpc/policy/selectors_pb2_grpc.py +4 -0
  96. otdf_python_proto/legacy_grpc/policy/subjectmapping/subject_mapping_pb2_grpc.py +551 -0
  97. otdf_python_proto/legacy_grpc/policy/unsafe/unsafe_pb2_grpc.py +485 -0
  98. otdf_python_proto/legacy_grpc/wellknownconfiguration/wellknown_configuration_pb2_grpc.py +77 -0
  99. otdf_python_proto/logger/__init__.py +1 -0
  100. otdf_python_proto/logger/audit/test_pb2.py +43 -0
  101. otdf_python_proto/logger/audit/test_pb2.pyi +45 -0
  102. otdf_python_proto/policy/__init__.py +1 -0
  103. otdf_python_proto/policy/actions/actions_pb2.py +75 -0
  104. otdf_python_proto/policy/actions/actions_pb2.pyi +87 -0
  105. otdf_python_proto/policy/actions/actions_pb2_connect.py +275 -0
  106. otdf_python_proto/policy/attributes/attributes_pb2.py +234 -0
  107. otdf_python_proto/policy/attributes/attributes_pb2.pyi +328 -0
  108. otdf_python_proto/policy/attributes/attributes_pb2_connect.py +863 -0
  109. otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.py +266 -0
  110. otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.pyi +450 -0
  111. otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +611 -0
  112. otdf_python_proto/policy/keymanagement/key_management_pb2.py +79 -0
  113. otdf_python_proto/policy/keymanagement/key_management_pb2.pyi +87 -0
  114. otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +275 -0
  115. otdf_python_proto/policy/namespaces/namespaces_pb2.py +117 -0
  116. otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +147 -0
  117. otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +443 -0
  118. otdf_python_proto/policy/objects_pb2.py +150 -0
  119. otdf_python_proto/policy/objects_pb2.pyi +464 -0
  120. otdf_python_proto/policy/registeredresources/registered_resources_pb2.py +139 -0
  121. otdf_python_proto/policy/registeredresources/registered_resources_pb2.pyi +196 -0
  122. otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +527 -0
  123. otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.py +139 -0
  124. otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.pyi +194 -0
  125. otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +527 -0
  126. otdf_python_proto/policy/selectors_pb2.py +57 -0
  127. otdf_python_proto/policy/selectors_pb2.pyi +90 -0
  128. otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.py +127 -0
  129. otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.pyi +189 -0
  130. otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +569 -0
  131. otdf_python_proto/policy/unsafe/unsafe_pb2.py +113 -0
  132. otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +145 -0
  133. otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +485 -0
  134. otdf_python_proto/wellknownconfiguration/__init__.py +1 -0
  135. otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +51 -0
  136. otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +32 -0
  137. otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +107 -0
  138. otdf_python/_gotdf_python.cpython-312-darwin.so +0 -0
  139. otdf_python/build.py +0 -190
  140. otdf_python/go.py +0 -1478
  141. otdf_python/gotdf_python.py +0 -383
  142. otdf_python-0.1.10.dist-info/METADATA +0 -149
  143. otdf_python-0.1.10.dist-info/RECORD +0 -10
  144. otdf_python-0.1.10.dist-info/top_level.txt +0 -1
@@ -0,0 +1 @@
1
+ MAGIC_NUMBER_AND_VERSION = bytes([0x4C, 0x31, 0x4C])
@@ -0,0 +1,78 @@
1
+ import hashlib
2
+ import hmac
3
+
4
+ from cryptography.hazmat.backends import default_backend
5
+ from cryptography.hazmat.primitives import serialization
6
+ from cryptography.hazmat.primitives.asymmetric import ec, rsa
7
+
8
+
9
+ class CryptoUtils:
10
+ KEYPAIR_SIZE = 2048
11
+
12
+ @staticmethod
13
+ def calculate_sha256_hmac(key: bytes, data: bytes) -> bytes:
14
+ return hmac.new(key, data, hashlib.sha256).digest()
15
+
16
+ @staticmethod
17
+ def generate_rsa_keypair() -> tuple[rsa.RSAPrivateKey, rsa.RSAPublicKey]:
18
+ private_key = rsa.generate_private_key(
19
+ public_exponent=65537,
20
+ key_size=CryptoUtils.KEYPAIR_SIZE,
21
+ backend=default_backend(),
22
+ )
23
+ return private_key, private_key.public_key()
24
+
25
+ @staticmethod
26
+ def generate_ec_keypair(
27
+ curve=None,
28
+ ) -> tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]:
29
+ if curve is None:
30
+ curve = ec.SECP256R1()
31
+ private_key = ec.generate_private_key(curve, default_backend())
32
+ return private_key, private_key.public_key()
33
+
34
+ @staticmethod
35
+ def get_public_key_pem(public_key) -> str:
36
+ return public_key.public_bytes(
37
+ serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo
38
+ ).decode()
39
+
40
+ @staticmethod
41
+ def get_private_key_pem(private_key) -> str:
42
+ return private_key.private_bytes(
43
+ serialization.Encoding.PEM,
44
+ serialization.PrivateFormat.PKCS8,
45
+ serialization.NoEncryption(),
46
+ ).decode()
47
+
48
+ @staticmethod
49
+ def get_rsa_public_key_pem(public_key) -> str:
50
+ if public_key.__class__.__name__ != "RSAPublicKey":
51
+ raise ValueError("Not an RSA public key")
52
+ return CryptoUtils.get_public_key_pem(public_key)
53
+
54
+ @staticmethod
55
+ def get_rsa_private_key_pem(private_key) -> str:
56
+ if private_key.__class__.__name__ != "RSAPrivateKey":
57
+ raise ValueError("Not an RSA private key")
58
+ return CryptoUtils.get_private_key_pem(private_key)
59
+
60
+ @staticmethod
61
+ def get_rsa_public_key_from_pem(pem_data: str) -> rsa.RSAPublicKey:
62
+ """Load RSA public key from PEM string."""
63
+ public_key = serialization.load_pem_public_key(
64
+ pem_data.encode(), backend=default_backend()
65
+ )
66
+ if not isinstance(public_key, rsa.RSAPublicKey):
67
+ raise ValueError("Not an RSA public key")
68
+ return public_key
69
+
70
+ @staticmethod
71
+ def get_rsa_private_key_from_pem(pem_data: str) -> rsa.RSAPrivateKey:
72
+ """Load RSA private key from PEM string."""
73
+ private_key = serialization.load_pem_private_key(
74
+ pem_data.encode(), password=None, backend=default_backend()
75
+ )
76
+ if not isinstance(private_key, rsa.RSAPrivateKey):
77
+ raise ValueError("Not an RSA private key")
78
+ return private_key
otdf_python/dpop.py ADDED
@@ -0,0 +1,81 @@
1
+ """
2
+ DPoP (Demonstration of Proof-of-Possession) token generation utilities.
3
+ """
4
+
5
+ import base64
6
+ import hashlib
7
+ import time
8
+
9
+ import jwt
10
+
11
+ from .crypto_utils import CryptoUtils
12
+
13
+
14
+ def create_dpop_token(
15
+ private_key_pem: str,
16
+ public_key_pem: str,
17
+ url: str,
18
+ method: str = "POST",
19
+ access_token: str | None = None,
20
+ ) -> str:
21
+ """
22
+ Create a DPoP (Demonstration of Proof-of-Possession) token.
23
+
24
+ Args:
25
+ private_key_pem: RSA private key in PEM format for signing
26
+ public_key_pem: RSA public key in PEM format for JWK
27
+ url: The URL being accessed
28
+ method: HTTP method (default: POST)
29
+ access_token: Optional access token for ath claim
30
+
31
+ Returns:
32
+ DPoP token as a string
33
+ """
34
+ # Parse the RSA public key to extract modulus and exponent
35
+ public_key_obj = CryptoUtils.get_rsa_public_key_from_pem(public_key_pem)
36
+ public_numbers = public_key_obj.public_numbers()
37
+
38
+ # Convert to base64url encoded values
39
+ def int_to_base64url(value):
40
+ # Convert integer to bytes, then to base64url
41
+ byte_length = (value.bit_length() + 7) // 8
42
+ value_bytes = value.to_bytes(byte_length, byteorder="big")
43
+ return base64.urlsafe_b64encode(value_bytes).decode("ascii").rstrip("=")
44
+
45
+ # Create JWK (JSON Web Key) representation
46
+ jwk = {
47
+ "kty": "RSA",
48
+ "n": int_to_base64url(public_numbers.n),
49
+ "e": int_to_base64url(public_numbers.e),
50
+ }
51
+
52
+ # Create DPoP header
53
+ now = int(time.time())
54
+
55
+ # Create JWT header with JWK
56
+ header = {"typ": "dpop+jwt", "alg": "RS256", "jwk": jwk}
57
+
58
+ # Create JWT payload
59
+ payload = {
60
+ "jti": base64.urlsafe_b64encode(
61
+ hashlib.sha256(f"{url}{method}{now}".encode()).digest()
62
+ )
63
+ .decode("ascii")
64
+ .rstrip("="),
65
+ "htm": method,
66
+ "htu": url,
67
+ "iat": now,
68
+ }
69
+
70
+ # Add access token hash if provided
71
+ if access_token:
72
+ # Create SHA-256 hash of access token
73
+ token_hash = hashlib.sha256(access_token.encode()).digest()
74
+ payload["ath"] = (
75
+ base64.urlsafe_b64encode(token_hash).decode("ascii").rstrip("=")
76
+ )
77
+
78
+ # Sign the DPoP token
79
+ dpop_token = jwt.encode(payload, private_key_pem, algorithm="RS256", headers=header)
80
+
81
+ return dpop_token
@@ -0,0 +1,32 @@
1
+ class ECCMode:
2
+ def __init__(self, curve_mode: int = 0, use_ecdsa_binding: bool = False):
3
+ self.curve_mode = curve_mode
4
+ self.use_ecdsa_binding = use_ecdsa_binding
5
+
6
+ def set_ecdsa_binding(self, flag: bool):
7
+ self.use_ecdsa_binding = flag
8
+
9
+ def is_ecdsa_binding_enabled(self) -> bool:
10
+ return self.use_ecdsa_binding
11
+
12
+ def set_elliptic_curve(self, curve_mode: int):
13
+ self.curve_mode = curve_mode
14
+
15
+ def get_elliptic_curve_type(self) -> int:
16
+ return self.curve_mode
17
+
18
+ @staticmethod
19
+ def get_ec_compressed_pubkey_size(curve_type: int) -> int:
20
+ # 0: secp256r1, 1: secp384r1, 2: secp521r1
21
+ if curve_type == 0:
22
+ return 33
23
+ elif curve_type == 1:
24
+ return 49
25
+ elif curve_type == 2:
26
+ return 67
27
+ else:
28
+ raise ValueError("Unsupported ECC algorithm.")
29
+
30
+ def get_ecc_mode_as_byte(self) -> int:
31
+ # Most significant bit: use_ecdsa_binding, lower 3 bits: curve_mode
32
+ return ((1 if self.use_ecdsa_binding else 0) << 7) | (self.curve_mode & 0x07)
@@ -0,0 +1,75 @@
1
+ from cryptography.exceptions import InvalidSignature
2
+ from cryptography.hazmat.backends import default_backend
3
+ from cryptography.hazmat.primitives import hashes, serialization
4
+ from cryptography.hazmat.primitives.asymmetric import ec
5
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
6
+ from cryptography.hazmat.primitives.serialization import (
7
+ Encoding,
8
+ NoEncryption,
9
+ PrivateFormat,
10
+ PublicFormat,
11
+ )
12
+
13
+
14
+ class ECKeyPair:
15
+ def __init__(self, curve=None):
16
+ if curve is None:
17
+ curve = ec.SECP256R1()
18
+ self.private_key = ec.generate_private_key(curve, default_backend())
19
+ self.public_key = self.private_key.public_key()
20
+ self.curve = curve
21
+
22
+ def public_key_pem(self):
23
+ return self.public_key.public_bytes(
24
+ Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
25
+ ).decode()
26
+
27
+ def private_key_pem(self):
28
+ return self.private_key.private_bytes(
29
+ Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()
30
+ ).decode()
31
+
32
+ def key_size(self):
33
+ return self.private_key.key_size
34
+
35
+ def compress_public_key(self):
36
+ return self.public_key.public_bytes(Encoding.X962, PublicFormat.CompressedPoint)
37
+
38
+ @staticmethod
39
+ def public_key_from_pem(pem):
40
+ return serialization.load_pem_public_key(
41
+ pem.encode(), backend=default_backend()
42
+ )
43
+
44
+ @staticmethod
45
+ def private_key_from_pem(pem):
46
+ return serialization.load_pem_private_key(
47
+ pem.encode(), password=None, backend=default_backend()
48
+ )
49
+
50
+ @staticmethod
51
+ def compute_ecdh_key(public_key, private_key):
52
+ return private_key.exchange(ec.ECDH(), public_key)
53
+
54
+ @staticmethod
55
+ def calculate_hkdf(salt, secret, length=32):
56
+ hkdf = HKDF(
57
+ algorithm=hashes.SHA256(),
58
+ length=length,
59
+ salt=salt,
60
+ info=None,
61
+ backend=default_backend(),
62
+ )
63
+ return hkdf.derive(secret)
64
+
65
+ @staticmethod
66
+ def sign_ecdsa(data, private_key):
67
+ return private_key.sign(data, ec.ECDSA(hashes.SHA256()))
68
+
69
+ @staticmethod
70
+ def verify_ecdsa(data, signature, public_key):
71
+ try:
72
+ public_key.verify(signature, data, ec.ECDSA(hashes.SHA256()))
73
+ return True
74
+ except InvalidSignature:
75
+ return False
otdf_python/header.py ADDED
@@ -0,0 +1,143 @@
1
+ from otdf_python.constants import MAGIC_NUMBER_AND_VERSION
2
+ from otdf_python.ecc_mode import ECCMode
3
+ from otdf_python.policy_info import PolicyInfo
4
+ from otdf_python.resource_locator import ResourceLocator
5
+ from otdf_python.symmetric_and_payload_config import SymmetricAndPayloadConfig
6
+
7
+
8
+ class Header:
9
+ def __init__(self):
10
+ self.kas_locator: ResourceLocator | None = None
11
+ self.ecc_mode: ECCMode | None = None
12
+ self.payload_config: SymmetricAndPayloadConfig | None = None
13
+ self.policy_info: PolicyInfo | None = None
14
+ self.ephemeral_key: bytes | None = None
15
+
16
+ @classmethod
17
+ def from_bytes(cls, buffer: bytes):
18
+ # Parse header from bytes, validate magic/version
19
+ offset = 0
20
+ magic = buffer[offset : offset + 3]
21
+ if magic != MAGIC_NUMBER_AND_VERSION:
22
+ raise ValueError("Invalid magic number and version in nano tdf.")
23
+ offset += 3
24
+ kas_locator, kas_size = ResourceLocator.from_bytes_with_size(buffer[offset:])
25
+ offset += kas_size
26
+ ecc_mode = ECCMode(buffer[offset])
27
+ offset += 1
28
+ payload_config = SymmetricAndPayloadConfig(buffer[offset])
29
+ offset += 1
30
+ policy_info, policy_size = PolicyInfo.from_bytes_with_size(
31
+ buffer[offset:], ecc_mode
32
+ )
33
+ offset += policy_size
34
+ compressed_pubkey_size = ECCMode.get_ec_compressed_pubkey_size(
35
+ ecc_mode.get_elliptic_curve_type()
36
+ )
37
+ ephemeral_key = buffer[offset : offset + compressed_pubkey_size]
38
+ if len(ephemeral_key) != compressed_pubkey_size:
39
+ raise ValueError("Failed to read ephemeral key - invalid buffer size.")
40
+ obj = cls()
41
+ obj.kas_locator = kas_locator
42
+ obj.ecc_mode = ecc_mode
43
+ obj.payload_config = payload_config
44
+ obj.policy_info = policy_info
45
+ obj.ephemeral_key = ephemeral_key
46
+ return obj
47
+
48
+ @staticmethod
49
+ def peek_length(buffer: bytes) -> int:
50
+ offset = 0
51
+ # MAGIC_NUMBER_AND_VERSION (3 bytes)
52
+ offset += 3
53
+ # ResourceLocator
54
+ kas_locator, kas_size = ResourceLocator.from_bytes_with_size(buffer[offset:])
55
+ offset += kas_size
56
+ # ECC mode (1 byte)
57
+ ecc_mode = ECCMode(buffer[offset])
58
+ offset += 1
59
+ # Payload config (1 byte)
60
+ offset += 1
61
+ # PolicyInfo
62
+ policy_info, policy_size = PolicyInfo.from_bytes_with_size(
63
+ buffer[offset:], ecc_mode
64
+ )
65
+ offset += policy_size
66
+ # Ephemeral key (size depends on curve)
67
+ compressed_pubkey_size = ECCMode.get_ec_compressed_pubkey_size(
68
+ ecc_mode.get_elliptic_curve_type()
69
+ )
70
+ offset += compressed_pubkey_size
71
+ return offset
72
+
73
+ def set_kas_locator(self, kas_locator: ResourceLocator):
74
+ self.kas_locator = kas_locator
75
+
76
+ def get_kas_locator(self) -> ResourceLocator | None:
77
+ return self.kas_locator
78
+
79
+ def set_ecc_mode(self, ecc_mode: ECCMode):
80
+ self.ecc_mode = ecc_mode
81
+
82
+ def get_ecc_mode(self) -> ECCMode | None:
83
+ return self.ecc_mode
84
+
85
+ def set_payload_config(self, payload_config: SymmetricAndPayloadConfig):
86
+ self.payload_config = payload_config
87
+
88
+ def get_payload_config(self) -> SymmetricAndPayloadConfig | None:
89
+ return self.payload_config
90
+
91
+ def set_policy_info(self, policy_info: PolicyInfo):
92
+ self.policy_info = policy_info
93
+
94
+ def get_policy_info(self) -> PolicyInfo | None:
95
+ return self.policy_info
96
+
97
+ def set_ephemeral_key(self, ephemeral_key: bytes):
98
+ if self.ecc_mode is not None:
99
+ expected_size = ECCMode.get_ec_compressed_pubkey_size(
100
+ self.ecc_mode.get_elliptic_curve_type()
101
+ )
102
+ if len(ephemeral_key) != expected_size:
103
+ raise ValueError("Failed to read ephemeral key - invalid buffer size.")
104
+ self.ephemeral_key = ephemeral_key
105
+
106
+ def get_ephemeral_key(self) -> bytes | None:
107
+ return self.ephemeral_key
108
+
109
+ def get_total_size(self) -> int:
110
+ total = 0
111
+ total += self.kas_locator.get_total_size() if self.kas_locator else 0
112
+ total += 1 # ECC mode
113
+ total += 1 # payload config
114
+ total += self.policy_info.get_total_size() if self.policy_info else 0
115
+ total += len(self.ephemeral_key) if self.ephemeral_key else 0
116
+ return total
117
+
118
+ def write_into_buffer(self, buffer: bytearray) -> int:
119
+ total_size = self.get_total_size()
120
+ if len(buffer) < total_size:
121
+ raise ValueError("Failed to write header - invalid buffer size.")
122
+ offset = 0
123
+ # ResourceLocator
124
+ n = self.kas_locator.write_into_buffer(buffer, offset)
125
+ offset += n
126
+ # ECCMode (1 byte)
127
+ buffer[offset] = self.ecc_mode.get_ecc_mode_as_byte()
128
+ offset += 1
129
+ # SymmetricAndPayloadConfig (1 byte)
130
+ buffer[offset] = self.payload_config.get_symmetric_and_payload_config_as_byte()
131
+ offset += 1
132
+ # PolicyInfo
133
+ n = self.policy_info.write_into_buffer(buffer, offset)
134
+ offset += n
135
+ # Ephemeral key
136
+ buffer[offset : offset + len(self.ephemeral_key)] = self.ephemeral_key
137
+ offset += len(self.ephemeral_key)
138
+ return offset
139
+
140
+ def to_bytes(self):
141
+ buf = bytearray(self.get_total_size())
142
+ self.write_into_buffer(buf)
143
+ return bytes(buf)
@@ -0,0 +1,8 @@
1
+ class InvalidZipException(Exception):
2
+ """
3
+ Raised when a ZIP file is invalid or corrupted.
4
+ Based on Java implementation.
5
+ """
6
+
7
+ def __init__(self, message: str):
8
+ super().__init__(message)