otdf-python 0.1.9__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.
- 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 +85 -0
- otdf_python/asym_decryption.py +53 -0
- otdf_python/asym_encryption.py +75 -0
- otdf_python/auth_headers.py +21 -0
- otdf_python/autoconfigure_utils.py +113 -0
- otdf_python/cli.py +570 -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_mode.py +32 -0
- otdf_python/eckeypair.py +75 -0
- otdf_python/header.py +143 -0
- otdf_python/invalid_zip_exception.py +8 -0
- otdf_python/kas_client.py +603 -0
- otdf_python/kas_connect_rpc_client.py +207 -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 +553 -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 +78 -0
- otdf_python/policy_object.py +22 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +44 -0
- otdf_python/sdk.py +528 -0
- otdf_python/sdk_builder.py +448 -0
- otdf_python/sdk_exceptions.py +16 -0
- otdf_python/symmetric_and_payload_config.py +30 -0
- otdf_python/tdf.py +479 -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.0.dist-info/METADATA +231 -0
- otdf_python-0.3.0.dist-info/RECORD +137 -0
- {otdf_python-0.1.9.dist-info → otdf_python-0.3.0.dist-info}/WHEEL +1 -2
- {otdf_python-0.1.9.dist-info → otdf_python-0.3.0.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.9.dist-info/METADATA +0 -149
- otdf_python-0.1.9.dist-info/RECORD +0 -10
- otdf_python-0.1.9.dist-info/top_level.txt +0 -1
otdf_python/constants.py
ADDED
|
@@ -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
|
otdf_python/ecc_mode.py
ADDED
|
@@ -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)
|
otdf_python/eckeypair.py
ADDED
|
@@ -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)
|