otdf-python 0.3.3__tar.gz → 0.3.5__tar.gz
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-0.3.3 → otdf_python-0.3.5}/.github/check_entitlements.sh +0 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.pre-commit-config.yaml +1 -1
- otdf_python-0.3.5/.release-please-manifest.json +3 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/CHANGELOG.md +18 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/PKG-INFO +1 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/README.md +0 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/README.md +5 -5
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/pyproject.toml +1 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/scripts/generate_connect_proto.py +6 -6
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/uv.lock +1 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/pyproject.toml +2 -1
- otdf_python-0.3.5/src/otdf_python/asym_crypto.py +198 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/auth_headers.py +13 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/cli.py +17 -10
- otdf_python-0.3.5/src/otdf_python/ecc_constants.py +176 -0
- otdf_python-0.3.5/src/otdf_python/ecc_mode.py +83 -0
- otdf_python-0.3.5/src/otdf_python/ecdh.py +317 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/header.py +40 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_client.py +172 -66
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_connect_rpc_client.py +7 -1
- otdf_python-0.3.5/src/otdf_python/nanotdf.py +863 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_info.py +5 -28
- otdf_python-0.3.5/src/otdf_python/resource_locator.py +172 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/sdk.py +7 -85
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/sdk_builder.py +5 -37
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/tdf.py +4 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/conftest.py +1 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_only/test_otdfctl_generated_fixtures.py +2 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_cli_comparison.py +2 -2
- otdf_python-0.3.5/tests/integration/otdfctl_to_python/test_nanotdf_cli_comparison.py +375 -0
- otdf_python-0.3.5/tests/integration/otdfctl_to_python/test_python_nanotdf_only.py +104 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_tdf_reader_integration.py +7 -7
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_cli_integration.py +4 -4
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_cli_tdf_validation.py +3 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_pe_interaction.py +3 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_common.py +2 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_asym_encryption.py +1 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_cli.py +5 -6
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_crypto_utils.py +1 -1
- otdf_python-0.3.5/tests/test_ecdh.py +435 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_header.py +6 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_inner_classes.py +21 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_kas_key_management.py +1 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_manifest_format.py +2 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf.py +10 -21
- otdf_python-0.3.5/tests/test_nanotdf_ecdh.py +321 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf_integration.py +3 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk.py +0 -3
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk_builder.py +5 -5
- otdf_python-0.3.5/tests/test_sdk_mock.py +31 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk_tdf_integration.py +4 -4
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf.py +1 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf_key_management.py +1 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf_reader.py +2 -2
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_url_normalization.py +0 -6
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_validate_otdf_python.py +4 -4
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_zip_reader.py +2 -1
- {otdf_python-0.3.3 → otdf_python-0.3.5}/uv.lock +21 -21
- otdf_python-0.3.3/.release-please-manifest.json +0 -3
- otdf_python-0.3.3/src/otdf_python/asym_crypto.py +0 -85
- otdf_python-0.3.3/src/otdf_python/asym_decryption.py +0 -53
- otdf_python-0.3.3/src/otdf_python/asym_encryption.py +0 -75
- otdf_python-0.3.3/src/otdf_python/ecc_mode.py +0 -32
- otdf_python-0.3.3/src/otdf_python/nanotdf.py +0 -553
- otdf_python-0.3.3/src/otdf_python/resource_locator.py +0 -44
- otdf_python-0.3.3/tests/test_sdk_mock.py +0 -58
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/start_opentdf_docker.sh +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/build-python.yaml +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/platform-integration-test.yaml +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/release-please.yaml +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/test-suite.yaml +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.gitignore +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.release-please-config-develop.json +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.release-please-config.json +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.release-please-manifest-develop.json +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.vscode/extensions.json +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/.vscode/settings.json +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/LICENSE +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/conftest.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/CONNECT_RPC.md +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/DEVELOPING.md +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/LEGACY_VERSION.md +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/RELEASES.md +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/buf.gen.yaml +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/buf.lock +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/buf.yaml +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/authorization/authorization.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/authorization/v2/authorization.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/common/common.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/entity/entity.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/entityresolution/entity_resolution.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/entityresolution/v2/entity_resolution.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/kas/kas.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/logger/audit/test.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/actions/actions.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/attributes/attributes.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/kasregistry/key_access_server_registry.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/keymanagement/key_management.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/namespaces/namespaces.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/objects.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/registeredresources/registered_resources.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/resourcemapping/resource_mapping.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/selectors.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/subjectmapping/subject_mapping.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/unsafe/unsafe.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/wellknownconfiguration/wellknown_configuration.proto +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/scripts/build_connect_proto.sh +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/scripts/setup_connect_rpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/authorization_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/authorization_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/authorization_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/v2/authorization_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/v2/authorization_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/v2/authorization_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/common/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/common/common_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/common/common_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entity/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entity/entity_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entity/entity_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/entity_resolution_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/v2/entity_resolution_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/v2/entity_resolution_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/kas_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/kas_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/kas_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/authorization/authorization_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/authorization/v2/authorization_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/common/common_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/entity/entity_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/entityresolution/entity_resolution_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/entityresolution/v2/entity_resolution_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/kas/kas_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/logger/audit/test_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/actions/actions_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/attributes/attributes_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/kasregistry/key_access_server_registry_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/keymanagement/key_management_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/namespaces/namespaces_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/objects_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/registeredresources/registered_resources_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/resourcemapping/resource_mapping_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/selectors_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/subjectmapping/subject_mapping_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/policy/unsafe/unsafe_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/wellknownconfiguration/wellknown_configuration_pb2_grpc.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/logger/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/logger/audit/test_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/logger/audit/test_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/actions/actions_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/actions/actions_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/actions/actions_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/attributes/attributes_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/attributes/attributes_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/attributes/attributes_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/keymanagement/key_management_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/keymanagement/key_management_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/namespaces/namespaces_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/objects_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/objects_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/registeredresources/registered_resources_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/registeredresources/registered_resources_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/resourcemapping/resource_mapping_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/selectors_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/selectors_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/subjectmapping/subject_mapping_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/unsafe/unsafe_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/__main__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/address_normalizer.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/aesgcm.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/assertion_config.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/autoconfigure_utils.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/collection_store.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/collection_store_impl.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/config.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/connect_client.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/constants.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/crypto_utils.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/dpop.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/eckeypair.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/invalid_zip_exception.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_info.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_key_cache.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/key_type.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/key_type_constants.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/manifest.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/nanotdf_ecdsa_struct.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/nanotdf_type.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_binding_serializer.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_object.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_stub.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/sdk_exceptions.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/symmetric_and_payload_config.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/tdf_reader.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/tdf_writer.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/token_source.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/version.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/zip_reader.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/zip_writer.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/__init__.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/config_pydantic.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_cli_decrypt.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_cli_inspect.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/python_only/test_kas_client_integration.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/support_sdk.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/empty_file.txt +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/sample_binary.png +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/sample_text.txt +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/sample_with_attributes.txt +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/mock_crypto.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/server_logs.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_cli_args.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_otdfctl.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_otdfctl_args.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_address_normalizer.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_aesgcm.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_assertion_config.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_autoconfigure_utils.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_collection_store.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_config.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_eckeypair.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_kas_client.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_kas_key_cache.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_key_type.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_log_collection.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_manifest.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf_ecdsa_struct.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf_type.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_policy_object.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk_exceptions.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf_writer.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_token_source.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_use_plaintext_flow.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_version.py +0 -0
- {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_zip_writer.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.5](https://github.com/b-long/opentdf-python-sdk/compare/otdf-python-v0.3.4...otdf-python-v0.3.5) (2025-11-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* NanoTDF support ([#114](https://github.com/b-long/opentdf-python-sdk/issues/114)) ([8f09297](https://github.com/b-long/opentdf-python-sdk/commit/8f092976f6473db7738a86d7ec30dc9ebbcb6a3a))
|
|
9
|
+
|
|
10
|
+
## [0.3.4](https://github.com/b-long/opentdf-python-sdk/compare/otdf-python-v0.3.3...otdf-python-v0.3.4) (2025-10-04)
|
|
11
|
+
|
|
12
|
+
### Chores
|
|
13
|
+
|
|
14
|
+
* chore: remove placeholders ([#110](https://github.com/b-long/opentdf-python-sdk/issues/110))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* update ruff ([#108](https://github.com/b-long/opentdf-python-sdk/issues/108)) ([5e4c796](https://github.com/b-long/opentdf-python-sdk/commit/5e4c796a8c1fc10b206cd2769f7c8548903ad3c1))
|
|
20
|
+
|
|
3
21
|
## [0.3.3](https://github.com/b-long/opentdf-python-sdk/compare/otdf-python-v0.3.2...otdf-python-v0.3.3) (2025-09-17)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: otdf-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Unofficial OpenTDF SDK for Python
|
|
5
5
|
Author-email: b-long <b-long@users.noreply.github.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -114,8 +114,6 @@ decrypted_data = tdf_reader.payload
|
|
|
114
114
|
with open("decrypted.txt", "wb") as f:
|
|
115
115
|
f.write(decrypted_data)
|
|
116
116
|
|
|
117
|
-
# Don't forget to close the SDK when done
|
|
118
|
-
sdk.close()
|
|
119
117
|
```
|
|
120
118
|
|
|
121
119
|
## Project Structure
|
|
@@ -17,7 +17,7 @@ See [CONNECT_RPC.md](../docs/CONNECT_RPC.md) for additional information.
|
|
|
17
17
|
## Structure
|
|
18
18
|
|
|
19
19
|
- `proto-files/`: Contains the raw .proto files downloaded from the OpenTDF platform
|
|
20
|
-
- `
|
|
20
|
+
- `src/otdf_python_proto/`: Contains the generated Python protobuf and Connect RPC client files
|
|
21
21
|
- `scripts/`: Contains build scripts for generating protobuf and Connect RPC files
|
|
22
22
|
- `buf.yaml`: Buf configuration for proto validation and management
|
|
23
23
|
- `buf.gen.yaml`: Buf generation configuration for Connect RPC and protobuf
|
|
@@ -40,10 +40,10 @@ Or use the convenience script:
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
This generates:
|
|
43
|
-
- `
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
46
|
-
- `
|
|
43
|
+
- `src/otdf_python_proto/**/*_connect.py` - Connect RPC clients (preferred)
|
|
44
|
+
- `src/otdf_python_proto/**/*_pb2.py` - Standard protobuf classes
|
|
45
|
+
- `src/otdf_python_proto/**/*_pb2.pyi` - Type stubs for better IDE support
|
|
46
|
+
- `src/otdf_python_proto/legacy_grpc/**/*_pb2_grpc.py` - Legacy gRPC clients (backward compatibility)
|
|
47
47
|
|
|
48
48
|
### Legacy gRPC Generation
|
|
49
49
|
|
|
@@ -96,10 +96,10 @@ def copy_opentdf_proto_files(proto_gen_dir: Path) -> bool:
|
|
|
96
96
|
print(f" Copying {relative_path}...")
|
|
97
97
|
|
|
98
98
|
# Copy the file content
|
|
99
|
-
with open(
|
|
99
|
+
with proto_file.open() as src:
|
|
100
100
|
content = src.read()
|
|
101
101
|
|
|
102
|
-
with open(
|
|
102
|
+
with dest_path.open("w") as dst:
|
|
103
103
|
dst.write(content)
|
|
104
104
|
|
|
105
105
|
copied_files += 1
|
|
@@ -155,7 +155,7 @@ def run_buf_generate(proto_gen_dir: Path) -> bool:
|
|
|
155
155
|
|
|
156
156
|
# Update buf.gen.yaml with the correct path
|
|
157
157
|
buf_gen_path = proto_gen_dir / "buf.gen.yaml"
|
|
158
|
-
with open(
|
|
158
|
+
with buf_gen_path.open() as f:
|
|
159
159
|
content = f.read()
|
|
160
160
|
|
|
161
161
|
# Replace the local plugin path
|
|
@@ -163,7 +163,7 @@ def run_buf_generate(proto_gen_dir: Path) -> bool:
|
|
|
163
163
|
"- local: protoc-gen-connect_python", f"- local: {connect_plugin_path}"
|
|
164
164
|
)
|
|
165
165
|
|
|
166
|
-
with open(
|
|
166
|
+
with buf_gen_path.open("w") as f:
|
|
167
167
|
f.write(updated_content)
|
|
168
168
|
|
|
169
169
|
# Run buf generate
|
|
@@ -221,7 +221,7 @@ def _fix_ignore_if_default_value(proto_files_dir):
|
|
|
221
221
|
# Iterate all .proto files in the directory
|
|
222
222
|
for proto_file in proto_files_dir.glob("**/*.proto"):
|
|
223
223
|
try:
|
|
224
|
-
with open(
|
|
224
|
+
with proto_file.open("r") as file:
|
|
225
225
|
content = file.read()
|
|
226
226
|
|
|
227
227
|
# Replace the old enum value with the new one
|
|
@@ -230,7 +230,7 @@ def _fix_ignore_if_default_value(proto_files_dir):
|
|
|
230
230
|
)
|
|
231
231
|
|
|
232
232
|
# Write the updated content back to the file
|
|
233
|
-
with open(
|
|
233
|
+
with proto_file.open("w") as file:
|
|
234
234
|
file.write(updated_content)
|
|
235
235
|
|
|
236
236
|
print(f"Updated {proto_file.name} to use IGNORE_IF_ZERO_VALUE")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "otdf-python"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.5"
|
|
4
4
|
description = "Unofficial OpenTDF SDK for Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -73,6 +73,7 @@ lint.select = [
|
|
|
73
73
|
"I",
|
|
74
74
|
# Performance-related rules
|
|
75
75
|
"PERF", # Ruff's performance rules
|
|
76
|
+
"PTH", # pathlib (path handling)
|
|
76
77
|
# Additional useful rules
|
|
77
78
|
"UP", # pyupgrade (modern Python features)
|
|
78
79
|
"SIM", # flake8-simplify (simplifications)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asymmetric encryption and decryption utilities for RSA keys in PEM format.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from cryptography.hazmat.backends import default_backend
|
|
9
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
10
|
+
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
11
|
+
from cryptography.x509 import load_pem_x509_certificate
|
|
12
|
+
|
|
13
|
+
from .sdk_exceptions import SDKException
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsymDecryption:
|
|
17
|
+
"""
|
|
18
|
+
Provides functionality for asymmetric decryption using an RSA private key.
|
|
19
|
+
|
|
20
|
+
Supports both PEM string and key object initialization for flexibility.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
|
|
24
|
+
PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----"
|
|
25
|
+
PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----"
|
|
26
|
+
|
|
27
|
+
def __init__(self, private_key_pem: str | None = None, private_key_obj=None):
|
|
28
|
+
"""
|
|
29
|
+
Initialize with either a PEM string or a key object.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
private_key_pem: Private key in PEM format (with or without headers)
|
|
33
|
+
private_key_obj: Pre-loaded private key object from cryptography library
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
SDKException: If key loading fails
|
|
37
|
+
"""
|
|
38
|
+
if private_key_obj is not None:
|
|
39
|
+
self.private_key = private_key_obj
|
|
40
|
+
elif private_key_pem is not None:
|
|
41
|
+
try:
|
|
42
|
+
# Try direct PEM loading first (most common case)
|
|
43
|
+
try:
|
|
44
|
+
self.private_key = serialization.load_pem_private_key(
|
|
45
|
+
private_key_pem.encode(),
|
|
46
|
+
password=None,
|
|
47
|
+
backend=default_backend(),
|
|
48
|
+
)
|
|
49
|
+
except Exception:
|
|
50
|
+
# Fallback: strip headers and load as DER (for base64-only keys)
|
|
51
|
+
private_key_pem = (
|
|
52
|
+
private_key_pem.replace(self.PRIVATE_KEY_HEADER, "")
|
|
53
|
+
.replace(self.PRIVATE_KEY_FOOTER, "")
|
|
54
|
+
.replace("\n", "")
|
|
55
|
+
.replace("\r", "")
|
|
56
|
+
.replace(" ", "")
|
|
57
|
+
)
|
|
58
|
+
decoded = base64.b64decode(private_key_pem)
|
|
59
|
+
self.private_key = serialization.load_der_private_key(
|
|
60
|
+
decoded, password=None, backend=default_backend()
|
|
61
|
+
)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
raise SDKException(f"Failed to load private key: {e}")
|
|
64
|
+
else:
|
|
65
|
+
self.private_key = None
|
|
66
|
+
|
|
67
|
+
def decrypt(self, data: bytes) -> bytes:
|
|
68
|
+
"""
|
|
69
|
+
Decrypt data using RSA OAEP with SHA-1.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
data: Encrypted bytes to decrypt
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Decrypted bytes
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
SDKException: If decryption fails or key is not set
|
|
79
|
+
"""
|
|
80
|
+
if self.private_key is None:
|
|
81
|
+
raise SDKException("Failed to decrypt, private key is empty")
|
|
82
|
+
try:
|
|
83
|
+
return self.private_key.decrypt(
|
|
84
|
+
data,
|
|
85
|
+
padding.OAEP(
|
|
86
|
+
mgf=padding.MGF1(algorithm=hashes.SHA1()),
|
|
87
|
+
algorithm=hashes.SHA1(),
|
|
88
|
+
label=None,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise SDKException(f"Error performing decryption: {e}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class AsymEncryption:
|
|
96
|
+
"""
|
|
97
|
+
Provides functionality for asymmetric encryption using an RSA public key or certificate in PEM format.
|
|
98
|
+
|
|
99
|
+
Supports PEM public keys, X.509 certificates, and pre-loaded key objects.
|
|
100
|
+
Also handles base64-encoded keys without PEM headers.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----"
|
|
104
|
+
PUBLIC_KEY_FOOTER = "-----END PUBLIC KEY-----"
|
|
105
|
+
CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
|
|
106
|
+
|
|
107
|
+
def __init__(self, public_key_pem: str | None = None, public_key_obj=None):
|
|
108
|
+
"""
|
|
109
|
+
Initialize with either a PEM string or a key object.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
public_key_pem: Public key in PEM format, X.509 certificate, or base64 string
|
|
113
|
+
public_key_obj: Pre-loaded public key object from cryptography library
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
SDKException: If key loading fails or key is not RSA
|
|
117
|
+
"""
|
|
118
|
+
if public_key_obj is not None:
|
|
119
|
+
self.public_key = public_key_obj
|
|
120
|
+
elif public_key_pem is not None:
|
|
121
|
+
try:
|
|
122
|
+
if "BEGIN CERTIFICATE" in public_key_pem:
|
|
123
|
+
# Load from X.509 certificate
|
|
124
|
+
cert = load_pem_x509_certificate(
|
|
125
|
+
public_key_pem.encode(), default_backend()
|
|
126
|
+
)
|
|
127
|
+
self.public_key = cert.public_key()
|
|
128
|
+
else:
|
|
129
|
+
# Try direct PEM loading first (most common case)
|
|
130
|
+
try:
|
|
131
|
+
self.public_key = serialization.load_pem_public_key(
|
|
132
|
+
public_key_pem.encode(), backend=default_backend()
|
|
133
|
+
)
|
|
134
|
+
except Exception:
|
|
135
|
+
# Fallback: strip headers and load as DER (for base64-only keys)
|
|
136
|
+
pem_body = re.sub(r"-----BEGIN (.*)-----", "", public_key_pem)
|
|
137
|
+
pem_body = re.sub(r"-----END (.*)-----", "", pem_body)
|
|
138
|
+
pem_body = re.sub(r"\s", "", pem_body)
|
|
139
|
+
decoded = base64.b64decode(pem_body)
|
|
140
|
+
self.public_key = serialization.load_der_public_key(
|
|
141
|
+
decoded, backend=default_backend()
|
|
142
|
+
)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise SDKException(f"Failed to load public key: {e}")
|
|
145
|
+
else:
|
|
146
|
+
self.public_key = None
|
|
147
|
+
|
|
148
|
+
# Validate that it's an RSA key
|
|
149
|
+
if self.public_key is not None and not isinstance(
|
|
150
|
+
self.public_key, rsa.RSAPublicKey
|
|
151
|
+
):
|
|
152
|
+
raise SDKException("Not an RSA PEM formatted public key")
|
|
153
|
+
|
|
154
|
+
def encrypt(self, data: bytes) -> bytes:
|
|
155
|
+
"""
|
|
156
|
+
Encrypt data using RSA OAEP with SHA-1.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
data: Plaintext bytes to encrypt
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Encrypted bytes
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
SDKException: If encryption fails or key is not set
|
|
166
|
+
"""
|
|
167
|
+
if self.public_key is None:
|
|
168
|
+
raise SDKException("Failed to encrypt, public key is empty")
|
|
169
|
+
try:
|
|
170
|
+
return self.public_key.encrypt(
|
|
171
|
+
data,
|
|
172
|
+
padding.OAEP(
|
|
173
|
+
mgf=padding.MGF1(algorithm=hashes.SHA1()),
|
|
174
|
+
algorithm=hashes.SHA1(),
|
|
175
|
+
label=None,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
raise SDKException(f"Error performing encryption: {e}")
|
|
180
|
+
|
|
181
|
+
def public_key_in_pem_format(self) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Export the public key to PEM format.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Public key as PEM-encoded string
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
SDKException: If export fails
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
pem = self.public_key.public_bytes(
|
|
193
|
+
encoding=serialization.Encoding.PEM,
|
|
194
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
195
|
+
)
|
|
196
|
+
return pem.decode()
|
|
197
|
+
except Exception as e:
|
|
198
|
+
raise SDKException(f"Error exporting public key to PEM: {e}")
|
|
@@ -10,7 +10,7 @@ class AuthHeaders:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
auth_header: str
|
|
13
|
-
dpop_header: str
|
|
13
|
+
dpop_header: str = ""
|
|
14
14
|
|
|
15
15
|
def get_auth_header(self) -> str:
|
|
16
16
|
"""Returns the authorization header."""
|
|
@@ -19,3 +19,15 @@ class AuthHeaders:
|
|
|
19
19
|
def get_dpop_header(self) -> str:
|
|
20
20
|
"""Returns the DPoP header."""
|
|
21
21
|
return self.dpop_header
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> dict[str, str]:
|
|
24
|
+
"""
|
|
25
|
+
Convert authentication headers to a dictionary for use with HTTP clients.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary with 'Authorization' header and optionally 'DPoP' header
|
|
29
|
+
"""
|
|
30
|
+
headers = {"Authorization": self.auth_header}
|
|
31
|
+
if self.dpop_header:
|
|
32
|
+
headers["DPoP"] = self.dpop_header
|
|
33
|
+
return headers
|
|
@@ -88,7 +88,7 @@ def load_client_credentials(creds_file_path: str) -> tuple[str, str]:
|
|
|
88
88
|
"CRITICAL", f"Credentials file does not exist: {creds_file_path}"
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
-
with open(
|
|
91
|
+
with creds_path.open() as f:
|
|
92
92
|
creds = json.load(f)
|
|
93
93
|
|
|
94
94
|
client_id = creds.get("clientId")
|
|
@@ -201,6 +201,13 @@ def create_nano_tdf_config(sdk: SDK, args) -> NanoTDFConfig:
|
|
|
201
201
|
kas_endpoints = parse_kas_endpoints(args.kas_endpoint)
|
|
202
202
|
kas_info_list = [KASInfo(url=kas_url) for kas_url in kas_endpoints]
|
|
203
203
|
config.kas_info_list.extend(kas_info_list)
|
|
204
|
+
elif args.platform_url:
|
|
205
|
+
# If no explicit KAS endpoint provided, derive from platform URL
|
|
206
|
+
# This matches the default KAS path convention
|
|
207
|
+
kas_url = args.platform_url.rstrip("/") + "/kas"
|
|
208
|
+
logger.debug(f"Deriving KAS endpoint from platform URL: {kas_url}")
|
|
209
|
+
kas_info = KASInfo(url=kas_url)
|
|
210
|
+
config.kas_info_list.append(kas_info)
|
|
204
211
|
|
|
205
212
|
if hasattr(args, "policy_binding") and args.policy_binding:
|
|
206
213
|
if args.policy_binding.lower() == "ecdsa":
|
|
@@ -223,13 +230,13 @@ def cmd_encrypt(args):
|
|
|
223
230
|
|
|
224
231
|
try:
|
|
225
232
|
# Read input file
|
|
226
|
-
with open(
|
|
233
|
+
with input_path.open("rb") as input_file:
|
|
227
234
|
payload = input_file.read()
|
|
228
235
|
|
|
229
236
|
# Determine output
|
|
230
237
|
if args.output:
|
|
231
238
|
output_path = Path(args.output)
|
|
232
|
-
with open(
|
|
239
|
+
with output_path.open("wb") as output_file:
|
|
233
240
|
try:
|
|
234
241
|
# Create appropriate config based on container type
|
|
235
242
|
container_type = getattr(args, "container_type", "tdf")
|
|
@@ -247,7 +254,7 @@ def cmd_encrypt(args):
|
|
|
247
254
|
logger.debug("Creating TDF")
|
|
248
255
|
config = create_tdf_config(sdk, args)
|
|
249
256
|
output_stream = BytesIO()
|
|
250
|
-
|
|
257
|
+
_manifest, size, _ = sdk.create_tdf(
|
|
251
258
|
BytesIO(payload), config, output_stream
|
|
252
259
|
)
|
|
253
260
|
output_file.write(output_stream.getvalue())
|
|
@@ -274,7 +281,7 @@ def cmd_encrypt(args):
|
|
|
274
281
|
logger.debug("Creating TDF")
|
|
275
282
|
config = create_tdf_config(sdk, args)
|
|
276
283
|
output_stream = BytesIO()
|
|
277
|
-
|
|
284
|
+
_manifest, size, _ = sdk.create_tdf(
|
|
278
285
|
BytesIO(payload), config, output_stream
|
|
279
286
|
)
|
|
280
287
|
output_file.write(output_stream.getvalue())
|
|
@@ -296,13 +303,13 @@ def cmd_decrypt(args):
|
|
|
296
303
|
|
|
297
304
|
try:
|
|
298
305
|
# Read encrypted file
|
|
299
|
-
with open(
|
|
306
|
+
with input_path.open("rb") as input_file:
|
|
300
307
|
encrypted_data = input_file.read()
|
|
301
308
|
|
|
302
309
|
# Determine output
|
|
303
310
|
if args.output:
|
|
304
311
|
output_path = Path(args.output)
|
|
305
|
-
with open(
|
|
312
|
+
with output_path.open("wb") as output_file:
|
|
306
313
|
try:
|
|
307
314
|
# Try to determine if it's a NanoTDF or regular TDF
|
|
308
315
|
# NanoTDFs have a specific header format, regular TDFs are ZIP files
|
|
@@ -359,7 +366,7 @@ def cmd_inspect(args):
|
|
|
359
366
|
|
|
360
367
|
try:
|
|
361
368
|
# Read encrypted file
|
|
362
|
-
with open(
|
|
369
|
+
with input_path.open("rb") as input_file:
|
|
363
370
|
encrypted_data = input_file.read()
|
|
364
371
|
|
|
365
372
|
if encrypted_data.startswith(b"PK"):
|
|
@@ -400,7 +407,7 @@ def cmd_inspect(args):
|
|
|
400
407
|
except Exception as e:
|
|
401
408
|
# If we can't inspect due to auth issues, show what we can
|
|
402
409
|
logger.warning(f"Limited inspection due to: {e}")
|
|
403
|
-
with open(
|
|
410
|
+
with input_path.open("rb") as input_file:
|
|
404
411
|
encrypted_data = input_file.read()
|
|
405
412
|
|
|
406
413
|
file_type = "TDF" if encrypted_data.startswith(b"PK") else "NanoTDF"
|
|
@@ -554,7 +561,7 @@ def main():
|
|
|
554
561
|
sys.exit(1)
|
|
555
562
|
except Exception as e:
|
|
556
563
|
logger.error(f"Unexpected error: {e}")
|
|
557
|
-
logger.
|
|
564
|
+
logger.error("", exc_info=True) # Always print traceback for unexpected errors
|
|
558
565
|
sys.exit(1)
|
|
559
566
|
|
|
560
567
|
|
|
@@ -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
|