otdf-python 0.1.10__py3-none-any.whl → 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otdf_python/__init__.py +25 -0
- otdf_python/__main__.py +12 -0
- otdf_python/address_normalizer.py +84 -0
- otdf_python/aesgcm.py +55 -0
- otdf_python/assertion_config.py +84 -0
- otdf_python/asym_crypto.py +198 -0
- otdf_python/auth_headers.py +33 -0
- otdf_python/autoconfigure_utils.py +113 -0
- otdf_python/cli.py +569 -0
- otdf_python/collection_store.py +41 -0
- otdf_python/collection_store_impl.py +22 -0
- otdf_python/config.py +69 -0
- otdf_python/connect_client.py +0 -0
- otdf_python/constants.py +1 -0
- otdf_python/crypto_utils.py +78 -0
- otdf_python/dpop.py +81 -0
- otdf_python/ecc_constants.py +176 -0
- otdf_python/ecc_mode.py +83 -0
- otdf_python/ecdh.py +317 -0
- otdf_python/eckeypair.py +75 -0
- otdf_python/header.py +181 -0
- otdf_python/invalid_zip_exception.py +8 -0
- otdf_python/kas_client.py +709 -0
- otdf_python/kas_connect_rpc_client.py +213 -0
- otdf_python/kas_info.py +25 -0
- otdf_python/kas_key_cache.py +52 -0
- otdf_python/key_type.py +31 -0
- otdf_python/key_type_constants.py +43 -0
- otdf_python/manifest.py +215 -0
- otdf_python/nanotdf.py +863 -0
- otdf_python/nanotdf_ecdsa_struct.py +132 -0
- otdf_python/nanotdf_type.py +43 -0
- otdf_python/policy_binding_serializer.py +39 -0
- otdf_python/policy_info.py +55 -0
- otdf_python/policy_object.py +22 -0
- otdf_python/policy_stub.py +2 -0
- otdf_python/resource_locator.py +172 -0
- otdf_python/sdk.py +436 -0
- otdf_python/sdk_builder.py +416 -0
- otdf_python/sdk_exceptions.py +16 -0
- otdf_python/symmetric_and_payload_config.py +30 -0
- otdf_python/tdf.py +480 -0
- otdf_python/tdf_reader.py +153 -0
- otdf_python/tdf_writer.py +23 -0
- otdf_python/token_source.py +34 -0
- otdf_python/version.py +57 -0
- otdf_python/zip_reader.py +47 -0
- otdf_python/zip_writer.py +70 -0
- otdf_python-0.3.5.dist-info/METADATA +153 -0
- otdf_python-0.3.5.dist-info/RECORD +137 -0
- {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info}/WHEEL +1 -2
- {otdf_python-0.1.10.dist-info → otdf_python-0.3.5.dist-info/licenses}/LICENSE +1 -1
- otdf_python_proto/__init__.py +37 -0
- otdf_python_proto/authorization/__init__.py +1 -0
- otdf_python_proto/authorization/authorization_pb2.py +80 -0
- otdf_python_proto/authorization/authorization_pb2.pyi +161 -0
- otdf_python_proto/authorization/authorization_pb2_connect.py +191 -0
- otdf_python_proto/authorization/v2/authorization_pb2.py +105 -0
- otdf_python_proto/authorization/v2/authorization_pb2.pyi +134 -0
- otdf_python_proto/authorization/v2/authorization_pb2_connect.py +233 -0
- otdf_python_proto/common/__init__.py +1 -0
- otdf_python_proto/common/common_pb2.py +52 -0
- otdf_python_proto/common/common_pb2.pyi +61 -0
- otdf_python_proto/entity/__init__.py +1 -0
- otdf_python_proto/entity/entity_pb2.py +47 -0
- otdf_python_proto/entity/entity_pb2.pyi +50 -0
- otdf_python_proto/entityresolution/__init__.py +1 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.py +57 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.py +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2.pyi +55 -0
- otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +149 -0
- otdf_python_proto/kas/__init__.py +9 -0
- otdf_python_proto/kas/kas_pb2.py +103 -0
- otdf_python_proto/kas/kas_pb2.pyi +170 -0
- otdf_python_proto/kas/kas_pb2_connect.py +192 -0
- otdf_python_proto/legacy_grpc/__init__.py +1 -0
- otdf_python_proto/legacy_grpc/authorization/authorization_pb2_grpc.py +163 -0
- otdf_python_proto/legacy_grpc/authorization/v2/authorization_pb2_grpc.py +206 -0
- otdf_python_proto/legacy_grpc/common/common_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entity/entity_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/entityresolution/entity_resolution_pb2_grpc.py +122 -0
- otdf_python_proto/legacy_grpc/entityresolution/v2/entity_resolution_pb2_grpc.py +120 -0
- otdf_python_proto/legacy_grpc/kas/kas_pb2_grpc.py +172 -0
- otdf_python_proto/legacy_grpc/logger/audit/test_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/actions/actions_pb2_grpc.py +249 -0
- otdf_python_proto/legacy_grpc/policy/attributes/attributes_pb2_grpc.py +873 -0
- otdf_python_proto/legacy_grpc/policy/kasregistry/key_access_server_registry_pb2_grpc.py +602 -0
- otdf_python_proto/legacy_grpc/policy/keymanagement/key_management_pb2_grpc.py +251 -0
- otdf_python_proto/legacy_grpc/policy/namespaces/namespaces_pb2_grpc.py +427 -0
- otdf_python_proto/legacy_grpc/policy/objects_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/registeredresources/registered_resources_pb2_grpc.py +524 -0
- otdf_python_proto/legacy_grpc/policy/resourcemapping/resource_mapping_pb2_grpc.py +516 -0
- otdf_python_proto/legacy_grpc/policy/selectors_pb2_grpc.py +4 -0
- otdf_python_proto/legacy_grpc/policy/subjectmapping/subject_mapping_pb2_grpc.py +551 -0
- otdf_python_proto/legacy_grpc/policy/unsafe/unsafe_pb2_grpc.py +485 -0
- otdf_python_proto/legacy_grpc/wellknownconfiguration/wellknown_configuration_pb2_grpc.py +77 -0
- otdf_python_proto/logger/__init__.py +1 -0
- otdf_python_proto/logger/audit/test_pb2.py +43 -0
- otdf_python_proto/logger/audit/test_pb2.pyi +45 -0
- otdf_python_proto/policy/__init__.py +1 -0
- otdf_python_proto/policy/actions/actions_pb2.py +75 -0
- otdf_python_proto/policy/actions/actions_pb2.pyi +87 -0
- otdf_python_proto/policy/actions/actions_pb2_connect.py +275 -0
- otdf_python_proto/policy/attributes/attributes_pb2.py +234 -0
- otdf_python_proto/policy/attributes/attributes_pb2.pyi +328 -0
- otdf_python_proto/policy/attributes/attributes_pb2_connect.py +863 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.py +266 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.pyi +450 -0
- otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +611 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.py +79 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2.pyi +87 -0
- otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +275 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.py +117 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +147 -0
- otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +443 -0
- otdf_python_proto/policy/objects_pb2.py +150 -0
- otdf_python_proto/policy/objects_pb2.pyi +464 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.py +139 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2.pyi +196 -0
- otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +527 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.py +139 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.pyi +194 -0
- otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +527 -0
- otdf_python_proto/policy/selectors_pb2.py +57 -0
- otdf_python_proto/policy/selectors_pb2.pyi +90 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.py +127 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.pyi +189 -0
- otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +569 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.py +113 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +145 -0
- otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +485 -0
- otdf_python_proto/wellknownconfiguration/__init__.py +1 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +51 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +32 -0
- otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +107 -0
- otdf_python/_gotdf_python.cpython-312-darwin.so +0 -0
- otdf_python/build.py +0 -190
- otdf_python/go.py +0 -1478
- otdf_python/gotdf_python.py +0 -383
- otdf_python-0.1.10.dist-info/METADATA +0 -149
- otdf_python-0.1.10.dist-info/RECORD +0 -10
- otdf_python-0.1.10.dist-info/top_level.txt +0 -1
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
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Elliptic Curve Constants for NanoTDF.
|
|
3
|
+
|
|
4
|
+
This module defines shared constants for elliptic curve operations used across
|
|
5
|
+
the SDK, particularly for NanoTDF encryption/decryption.
|
|
6
|
+
|
|
7
|
+
All supported curves follow the NanoTDF specification which uses compressed
|
|
8
|
+
public key encoding (X9.62 format) to minimize header size.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import ClassVar
|
|
12
|
+
|
|
13
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ECCConstants:
|
|
17
|
+
"""
|
|
18
|
+
Centralized constants for elliptic curve cryptography operations.
|
|
19
|
+
|
|
20
|
+
This class provides mappings between curve names, curve type integers,
|
|
21
|
+
cryptography curve objects, and compressed public key sizes.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Mapping from curve names (strings) to curve type integers (per NanoTDF spec)
|
|
25
|
+
# These integer values are encoded in the NanoTDF header's ECC mode byte
|
|
26
|
+
CURVE_NAME_TO_TYPE: ClassVar[dict[str, int]] = {
|
|
27
|
+
"secp256r1": 0, # NIST P-256 (most common)
|
|
28
|
+
"secp384r1": 1, # NIST P-384
|
|
29
|
+
"secp521r1": 2, # NIST P-521
|
|
30
|
+
"secp256k1": 3, # Bitcoin curve (secp256k1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Mapping from curve type integers to curve names
|
|
34
|
+
# Inverse of CURVE_NAME_TO_TYPE for reverse lookups
|
|
35
|
+
CURVE_TYPE_TO_NAME: ClassVar[dict[int, str]] = {
|
|
36
|
+
0: "secp256r1",
|
|
37
|
+
1: "secp384r1",
|
|
38
|
+
2: "secp521r1",
|
|
39
|
+
3: "secp256k1",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Compressed public key sizes (in bytes) for each curve
|
|
43
|
+
# Format: 1 byte prefix (0x02 or 0x03) + x-coordinate bytes
|
|
44
|
+
# Used by both ecc_mode.py (indexed by int) and ecdh.py (indexed by string)
|
|
45
|
+
COMPRESSED_KEY_SIZE_BY_TYPE: ClassVar[dict[int, int]] = {
|
|
46
|
+
0: 33, # secp256r1: 1 byte prefix + 32 bytes x-coordinate
|
|
47
|
+
1: 49, # secp384r1: 1 byte prefix + 48 bytes x-coordinate
|
|
48
|
+
2: 67, # secp521r1: 1 byte prefix + 66 bytes x-coordinate
|
|
49
|
+
3: 33, # secp256k1: 1 byte prefix + 32 bytes x-coordinate (same as secp256r1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
COMPRESSED_KEY_SIZE_BY_NAME: ClassVar[dict[str, int]] = {
|
|
53
|
+
"secp256r1": 33, # 1 byte prefix + 32 bytes
|
|
54
|
+
"secp384r1": 49, # 1 byte prefix + 48 bytes
|
|
55
|
+
"secp521r1": 67, # 1 byte prefix + 66 bytes
|
|
56
|
+
"secp256k1": 33, # 1 byte prefix + 32 bytes
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Mapping from curve names to cryptography library curve objects
|
|
60
|
+
# Used by ecdh.py for key generation and ECDH operations
|
|
61
|
+
CURVE_OBJECTS: ClassVar[dict[str, ec.EllipticCurve]] = {
|
|
62
|
+
"secp256r1": ec.SECP256R1(),
|
|
63
|
+
"secp384r1": ec.SECP384R1(),
|
|
64
|
+
"secp521r1": ec.SECP521R1(),
|
|
65
|
+
"secp256k1": ec.SECP256K1(),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def get_curve_name(cls, curve_type: int) -> str:
|
|
70
|
+
"""
|
|
71
|
+
Get curve name from curve type integer.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
curve_type: Curve type (0=secp256r1, 1=secp384r1, 2=secp521r1, 3=secp256k1)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Curve name as string (e.g., "secp256r1")
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If curve_type is not supported
|
|
81
|
+
"""
|
|
82
|
+
name = cls.CURVE_TYPE_TO_NAME.get(curve_type)
|
|
83
|
+
if name is None:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Unsupported curve type: {curve_type}. "
|
|
86
|
+
f"Supported types: {list(cls.CURVE_TYPE_TO_NAME.keys())}"
|
|
87
|
+
)
|
|
88
|
+
return name
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def get_curve_type(cls, curve_name: str) -> int:
|
|
92
|
+
"""
|
|
93
|
+
Get curve type integer from curve name.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
curve_name: Curve name (e.g., "secp256r1")
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Curve type as integer (0-3)
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
ValueError: If curve_name is not supported
|
|
103
|
+
"""
|
|
104
|
+
curve_type = cls.CURVE_NAME_TO_TYPE.get(curve_name.lower())
|
|
105
|
+
if curve_type is None:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Unsupported curve name: '{curve_name}'. "
|
|
108
|
+
f"Supported curves: {list(cls.CURVE_NAME_TO_TYPE.keys())}"
|
|
109
|
+
)
|
|
110
|
+
return curve_type
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def get_compressed_key_size_by_type(cls, curve_type: int) -> int:
|
|
114
|
+
"""
|
|
115
|
+
Get compressed public key size from curve type integer.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
curve_type: Curve type (0=secp256r1, 1=secp384r1, 2=secp521r1, 3=secp256k1)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Size in bytes of the compressed public key
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValueError: If curve_type is not supported
|
|
125
|
+
"""
|
|
126
|
+
size = cls.COMPRESSED_KEY_SIZE_BY_TYPE.get(curve_type)
|
|
127
|
+
if size is None:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Unsupported curve type: {curve_type}. "
|
|
130
|
+
f"Supported types: {list(cls.COMPRESSED_KEY_SIZE_BY_TYPE.keys())}"
|
|
131
|
+
)
|
|
132
|
+
return size
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def get_compressed_key_size_by_name(cls, curve_name: str) -> int:
|
|
136
|
+
"""
|
|
137
|
+
Get compressed public key size from curve name.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
curve_name: Curve name (e.g., "secp256r1")
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Size in bytes of the compressed public key
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ValueError: If curve_name is not supported
|
|
147
|
+
"""
|
|
148
|
+
size = cls.COMPRESSED_KEY_SIZE_BY_NAME.get(curve_name.lower())
|
|
149
|
+
if size is None:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"Unsupported curve name: '{curve_name}'. "
|
|
152
|
+
f"Supported curves: {list(cls.COMPRESSED_KEY_SIZE_BY_NAME.keys())}"
|
|
153
|
+
)
|
|
154
|
+
return size
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def get_curve_object(cls, curve_name: str) -> ec.EllipticCurve:
|
|
158
|
+
"""
|
|
159
|
+
Get cryptography library curve object from curve name.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
curve_name: Curve name (e.g., "secp256r1")
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Cryptography library EllipticCurve object
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ValueError: If curve_name is not supported
|
|
169
|
+
"""
|
|
170
|
+
curve = cls.CURVE_OBJECTS.get(curve_name.lower())
|
|
171
|
+
if curve is None:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"Unsupported curve name: '{curve_name}'. "
|
|
174
|
+
f"Supported curves: {list(cls.CURVE_OBJECTS.keys())}"
|
|
175
|
+
)
|
|
176
|
+
return curve
|
otdf_python/ecc_mode.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from otdf_python.ecc_constants import ECCConstants
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ECCMode:
|
|
5
|
+
"""
|
|
6
|
+
ECC (Elliptic Curve Cryptography) mode configuration for NanoTDF.
|
|
7
|
+
|
|
8
|
+
This class encapsulates the curve type and policy binding mode (GMAC vs ECDSA)
|
|
9
|
+
that are encoded in the NanoTDF header. It delegates to ECCConstants for
|
|
10
|
+
curve-related lookups to maintain a single source of truth.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, curve_mode: int = 0, use_ecdsa_binding: bool = False):
|
|
14
|
+
self.curve_mode = curve_mode
|
|
15
|
+
self.use_ecdsa_binding = use_ecdsa_binding
|
|
16
|
+
|
|
17
|
+
def set_ecdsa_binding(self, flag: bool):
|
|
18
|
+
self.use_ecdsa_binding = flag
|
|
19
|
+
|
|
20
|
+
def is_ecdsa_binding_enabled(self) -> bool:
|
|
21
|
+
return self.use_ecdsa_binding
|
|
22
|
+
|
|
23
|
+
def set_elliptic_curve(self, curve_mode: int):
|
|
24
|
+
self.curve_mode = curve_mode
|
|
25
|
+
|
|
26
|
+
def get_elliptic_curve_type(self) -> int:
|
|
27
|
+
return self.curve_mode
|
|
28
|
+
|
|
29
|
+
def get_curve_name(self) -> str:
|
|
30
|
+
"""Get the curve name as a string (e.g., 'secp256r1').
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Curve name corresponding to the current curve_mode
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If curve_mode is not supported
|
|
37
|
+
"""
|
|
38
|
+
# Delegate to ECCConstants for the authoritative mapping
|
|
39
|
+
return ECCConstants.get_curve_name(self.curve_mode)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def get_ec_compressed_pubkey_size(curve_type: int) -> int:
|
|
43
|
+
"""Get the compressed public key size for a given curve type.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
curve_type: Curve type identifier (0=secp256r1, 1=secp384r1, 2=secp521r1, 3=secp256k1)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Size in bytes of the compressed public key
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If curve_type is not supported
|
|
53
|
+
"""
|
|
54
|
+
# Delegate to ECCConstants for the authoritative mapping
|
|
55
|
+
return ECCConstants.get_compressed_key_size_by_type(curve_type)
|
|
56
|
+
|
|
57
|
+
def get_ecc_mode_as_byte(self) -> int:
|
|
58
|
+
# Most significant bit: use_ecdsa_binding, lower 3 bits: curve_mode
|
|
59
|
+
return ((1 if self.use_ecdsa_binding else 0) << 7) | (self.curve_mode & 0x07)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def from_string(curve_str: str) -> "ECCMode":
|
|
63
|
+
"""Create ECCMode from curve string or policy binding type.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
curve_str: Either a curve name ('secp256r1', 'secp384r1', 'secp521r1', 'secp256k1')
|
|
67
|
+
or a policy binding type ('gmac', 'ecdsa')
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
ECCMode instance configured with the appropriate curve and binding mode
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If curve_str is not a supported curve or binding type
|
|
74
|
+
"""
|
|
75
|
+
# Handle policy binding types (always use secp256r1 as default curve)
|
|
76
|
+
if curve_str.lower() == "gmac":
|
|
77
|
+
return ECCMode(0, False) # GMAC binding with default secp256r1 curve
|
|
78
|
+
elif curve_str.lower() == "ecdsa":
|
|
79
|
+
return ECCMode(0, True) # ECDSA binding with default secp256r1 curve
|
|
80
|
+
|
|
81
|
+
# Handle curve names - delegate to ECCConstants for the authoritative mapping
|
|
82
|
+
curve_mode = ECCConstants.get_curve_type(curve_str)
|
|
83
|
+
return ECCMode(curve_mode, False)
|