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.
Files changed (262) hide show
  1. {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/check_entitlements.sh +0 -1
  2. {otdf_python-0.3.3 → otdf_python-0.3.5}/.pre-commit-config.yaml +1 -1
  3. otdf_python-0.3.5/.release-please-manifest.json +3 -0
  4. {otdf_python-0.3.3 → otdf_python-0.3.5}/CHANGELOG.md +18 -0
  5. {otdf_python-0.3.3 → otdf_python-0.3.5}/PKG-INFO +1 -3
  6. {otdf_python-0.3.3 → otdf_python-0.3.5}/README.md +0 -2
  7. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/README.md +5 -5
  8. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/pyproject.toml +1 -1
  9. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/scripts/generate_connect_proto.py +6 -6
  10. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/uv.lock +1 -1
  11. {otdf_python-0.3.3 → otdf_python-0.3.5}/pyproject.toml +2 -1
  12. otdf_python-0.3.5/src/otdf_python/asym_crypto.py +198 -0
  13. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/auth_headers.py +13 -1
  14. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/cli.py +17 -10
  15. otdf_python-0.3.5/src/otdf_python/ecc_constants.py +176 -0
  16. otdf_python-0.3.5/src/otdf_python/ecc_mode.py +83 -0
  17. otdf_python-0.3.5/src/otdf_python/ecdh.py +317 -0
  18. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/header.py +40 -2
  19. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_client.py +172 -66
  20. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_connect_rpc_client.py +7 -1
  21. otdf_python-0.3.5/src/otdf_python/nanotdf.py +863 -0
  22. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_info.py +5 -28
  23. otdf_python-0.3.5/src/otdf_python/resource_locator.py +172 -0
  24. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/sdk.py +7 -85
  25. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/sdk_builder.py +5 -37
  26. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/tdf.py +4 -3
  27. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/conftest.py +1 -1
  28. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_only/test_otdfctl_generated_fixtures.py +2 -2
  29. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_cli_comparison.py +2 -2
  30. otdf_python-0.3.5/tests/integration/otdfctl_to_python/test_nanotdf_cli_comparison.py +375 -0
  31. otdf_python-0.3.5/tests/integration/otdfctl_to_python/test_python_nanotdf_only.py +104 -0
  32. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_tdf_reader_integration.py +7 -7
  33. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_cli_integration.py +4 -4
  34. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_cli_tdf_validation.py +3 -3
  35. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_pe_interaction.py +3 -3
  36. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_common.py +2 -2
  37. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_asym_encryption.py +1 -2
  38. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_cli.py +5 -6
  39. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_crypto_utils.py +1 -1
  40. otdf_python-0.3.5/tests/test_ecdh.py +435 -0
  41. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_header.py +6 -3
  42. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_inner_classes.py +21 -0
  43. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_kas_key_management.py +1 -1
  44. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_manifest_format.py +2 -2
  45. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf.py +10 -21
  46. otdf_python-0.3.5/tests/test_nanotdf_ecdh.py +321 -0
  47. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf_integration.py +3 -3
  48. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk.py +0 -3
  49. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk_builder.py +5 -5
  50. otdf_python-0.3.5/tests/test_sdk_mock.py +31 -0
  51. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk_tdf_integration.py +4 -4
  52. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf.py +1 -1
  53. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf_key_management.py +1 -1
  54. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf_reader.py +2 -2
  55. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_url_normalization.py +0 -6
  56. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_validate_otdf_python.py +4 -4
  57. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_zip_reader.py +2 -1
  58. {otdf_python-0.3.3 → otdf_python-0.3.5}/uv.lock +21 -21
  59. otdf_python-0.3.3/.release-please-manifest.json +0 -3
  60. otdf_python-0.3.3/src/otdf_python/asym_crypto.py +0 -85
  61. otdf_python-0.3.3/src/otdf_python/asym_decryption.py +0 -53
  62. otdf_python-0.3.3/src/otdf_python/asym_encryption.py +0 -75
  63. otdf_python-0.3.3/src/otdf_python/ecc_mode.py +0 -32
  64. otdf_python-0.3.3/src/otdf_python/nanotdf.py +0 -553
  65. otdf_python-0.3.3/src/otdf_python/resource_locator.py +0 -44
  66. otdf_python-0.3.3/tests/test_sdk_mock.py +0 -58
  67. {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/start_opentdf_docker.sh +0 -0
  68. {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/build-python.yaml +0 -0
  69. {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/platform-integration-test.yaml +0 -0
  70. {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/release-please.yaml +0 -0
  71. {otdf_python-0.3.3 → otdf_python-0.3.5}/.github/workflows/test-suite.yaml +0 -0
  72. {otdf_python-0.3.3 → otdf_python-0.3.5}/.gitignore +0 -0
  73. {otdf_python-0.3.3 → otdf_python-0.3.5}/.release-please-config-develop.json +0 -0
  74. {otdf_python-0.3.3 → otdf_python-0.3.5}/.release-please-config.json +0 -0
  75. {otdf_python-0.3.3 → otdf_python-0.3.5}/.release-please-manifest-develop.json +0 -0
  76. {otdf_python-0.3.3 → otdf_python-0.3.5}/.vscode/extensions.json +0 -0
  77. {otdf_python-0.3.3 → otdf_python-0.3.5}/.vscode/settings.json +0 -0
  78. {otdf_python-0.3.3 → otdf_python-0.3.5}/LICENSE +0 -0
  79. {otdf_python-0.3.3 → otdf_python-0.3.5}/conftest.py +0 -0
  80. {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/CONNECT_RPC.md +0 -0
  81. {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/DEVELOPING.md +0 -0
  82. {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/LEGACY_VERSION.md +0 -0
  83. {otdf_python-0.3.3 → otdf_python-0.3.5}/docs/RELEASES.md +0 -0
  84. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/buf.gen.yaml +0 -0
  85. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/buf.lock +0 -0
  86. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/buf.yaml +0 -0
  87. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/authorization/authorization.proto +0 -0
  88. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/authorization/v2/authorization.proto +0 -0
  89. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/common/common.proto +0 -0
  90. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/entity/entity.proto +0 -0
  91. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/entityresolution/entity_resolution.proto +0 -0
  92. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/entityresolution/v2/entity_resolution.proto +0 -0
  93. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/kas/kas.proto +0 -0
  94. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/logger/audit/test.proto +0 -0
  95. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/actions/actions.proto +0 -0
  96. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/attributes/attributes.proto +0 -0
  97. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/kasregistry/key_access_server_registry.proto +0 -0
  98. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/keymanagement/key_management.proto +0 -0
  99. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/namespaces/namespaces.proto +0 -0
  100. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/objects.proto +0 -0
  101. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/registeredresources/registered_resources.proto +0 -0
  102. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/resourcemapping/resource_mapping.proto +0 -0
  103. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/selectors.proto +0 -0
  104. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/subjectmapping/subject_mapping.proto +0 -0
  105. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/policy/unsafe/unsafe.proto +0 -0
  106. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/proto-files/wellknownconfiguration/wellknown_configuration.proto +0 -0
  107. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/scripts/build_connect_proto.sh +0 -0
  108. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/scripts/setup_connect_rpc.py +0 -0
  109. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/__init__.py +0 -0
  110. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/__init__.py +0 -0
  111. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/authorization_pb2.py +0 -0
  112. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/authorization_pb2.pyi +0 -0
  113. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/authorization_pb2_connect.py +0 -0
  114. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/v2/authorization_pb2.py +0 -0
  115. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/authorization/v2/authorization_pb2.pyi +0 -0
  116. {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
  117. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/common/__init__.py +0 -0
  118. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/common/common_pb2.py +0 -0
  119. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/common/common_pb2.pyi +0 -0
  120. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entity/__init__.py +0 -0
  121. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entity/entity_pb2.py +0 -0
  122. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entity/entity_pb2.pyi +0 -0
  123. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/__init__.py +0 -0
  124. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/entity_resolution_pb2.py +0 -0
  125. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/entityresolution/entity_resolution_pb2.pyi +0 -0
  126. {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
  127. {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
  128. {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
  129. {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
  130. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/__init__.py +0 -0
  131. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/kas_pb2.py +0 -0
  132. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/kas_pb2.pyi +0 -0
  133. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/kas/kas_pb2_connect.py +0 -0
  134. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/legacy_grpc/__init__.py +0 -0
  135. {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
  136. {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
  137. {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
  138. {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
  139. {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
  140. {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
  141. {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
  142. {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
  143. {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
  144. {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
  145. {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
  146. {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
  147. {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
  148. {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
  149. {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
  150. {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
  151. {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
  152. {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
  153. {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
  154. {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
  155. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/logger/__init__.py +0 -0
  156. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/logger/audit/test_pb2.py +0 -0
  157. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/logger/audit/test_pb2.pyi +0 -0
  158. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/__init__.py +0 -0
  159. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/actions/actions_pb2.py +0 -0
  160. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/actions/actions_pb2.pyi +0 -0
  161. {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
  162. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/attributes/attributes_pb2.py +0 -0
  163. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/attributes/attributes_pb2.pyi +0 -0
  164. {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
  165. {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
  166. {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
  167. {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
  168. {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
  169. {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
  170. {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
  171. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/namespaces/namespaces_pb2.py +0 -0
  172. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/namespaces/namespaces_pb2.pyi +0 -0
  173. {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
  174. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/objects_pb2.py +0 -0
  175. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/objects_pb2.pyi +0 -0
  176. {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
  177. {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
  178. {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
  179. {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
  180. {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
  181. {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
  182. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/selectors_pb2.py +0 -0
  183. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/selectors_pb2.pyi +0 -0
  184. {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
  185. {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
  186. {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
  187. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/unsafe/unsafe_pb2.py +0 -0
  188. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/policy/unsafe/unsafe_pb2.pyi +0 -0
  189. {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
  190. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/__init__.py +0 -0
  191. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.py +0 -0
  192. {otdf_python-0.3.3 → otdf_python-0.3.5}/otdf-python-proto/src/otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2.pyi +0 -0
  193. {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
  194. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/__init__.py +0 -0
  195. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/__main__.py +0 -0
  196. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/address_normalizer.py +0 -0
  197. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/aesgcm.py +0 -0
  198. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/assertion_config.py +0 -0
  199. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/autoconfigure_utils.py +0 -0
  200. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/collection_store.py +0 -0
  201. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/collection_store_impl.py +0 -0
  202. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/config.py +0 -0
  203. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/connect_client.py +0 -0
  204. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/constants.py +0 -0
  205. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/crypto_utils.py +0 -0
  206. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/dpop.py +0 -0
  207. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/eckeypair.py +0 -0
  208. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/invalid_zip_exception.py +0 -0
  209. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_info.py +0 -0
  210. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/kas_key_cache.py +0 -0
  211. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/key_type.py +0 -0
  212. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/key_type_constants.py +0 -0
  213. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/manifest.py +0 -0
  214. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/nanotdf_ecdsa_struct.py +0 -0
  215. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/nanotdf_type.py +0 -0
  216. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_binding_serializer.py +0 -0
  217. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_object.py +0 -0
  218. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/policy_stub.py +0 -0
  219. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/sdk_exceptions.py +0 -0
  220. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/symmetric_and_payload_config.py +0 -0
  221. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/tdf_reader.py +0 -0
  222. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/tdf_writer.py +0 -0
  223. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/token_source.py +0 -0
  224. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/version.py +0 -0
  225. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/zip_reader.py +0 -0
  226. {otdf_python-0.3.3 → otdf_python-0.3.5}/src/otdf_python/zip_writer.py +0 -0
  227. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/__init__.py +0 -0
  228. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/config_pydantic.py +0 -0
  229. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_cli_decrypt.py +0 -0
  230. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/otdfctl_to_python/test_cli_inspect.py +0 -0
  231. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/python_only/test_kas_client_integration.py +0 -0
  232. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/support_sdk.py +0 -0
  233. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/empty_file.txt +0 -0
  234. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/sample_binary.png +0 -0
  235. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/sample_text.txt +0 -0
  236. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/integration/test_data/sample_with_attributes.txt +0 -0
  237. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/mock_crypto.py +0 -0
  238. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/server_logs.py +0 -0
  239. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_cli_args.py +0 -0
  240. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_otdfctl.py +0 -0
  241. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/support_otdfctl_args.py +0 -0
  242. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_address_normalizer.py +0 -0
  243. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_aesgcm.py +0 -0
  244. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_assertion_config.py +0 -0
  245. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_autoconfigure_utils.py +0 -0
  246. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_collection_store.py +0 -0
  247. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_config.py +0 -0
  248. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_eckeypair.py +0 -0
  249. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_kas_client.py +0 -0
  250. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_kas_key_cache.py +0 -0
  251. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_key_type.py +0 -0
  252. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_log_collection.py +0 -0
  253. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_manifest.py +0 -0
  254. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf_ecdsa_struct.py +0 -0
  255. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_nanotdf_type.py +0 -0
  256. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_policy_object.py +0 -0
  257. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_sdk_exceptions.py +0 -0
  258. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_tdf_writer.py +0 -0
  259. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_token_source.py +0 -0
  260. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_use_plaintext_flow.py +0 -0
  261. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_version.py +0 -0
  262. {otdf_python-0.3.3 → otdf_python-0.3.5}/tests/test_zip_writer.py +0 -0
@@ -17,7 +17,6 @@ echo ""
17
17
 
18
18
  get_token() {
19
19
  curl -k --location "$TOKEN_URL" \
20
- --header "X-VirtruPubKey;" \
21
20
  --header "Content-Type: application/x-www-form-urlencoded" \
22
21
  --data-urlencode "grant_type=client_credentials" \
23
22
  --data-urlencode "client_id=$OTDF_CLIENT" \
@@ -34,7 +34,7 @@ repos:
34
34
 
35
35
  - repo: https://github.com/astral-sh/ruff-pre-commit
36
36
  # Ruff version.
37
- rev: v0.12.12
37
+ rev: v0.13.3
38
38
  hooks:
39
39
  # Run the linter.
40
40
  - id: ruff-check
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.5"
3
+ }
@@ -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
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
@@ -95,8 +95,6 @@ decrypted_data = tdf_reader.payload
95
95
  with open("decrypted.txt", "wb") as f:
96
96
  f.write(decrypted_data)
97
97
 
98
- # Don't forget to close the SDK when done
99
- sdk.close()
100
98
  ```
101
99
 
102
100
  ## 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
- - `generated/`: Contains the generated Python protobuf and Connect RPC client files
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
- - `generated/*_connect.py` - Connect RPC clients (preferred)
44
- - `generated/*_pb2.py` - Standard protobuf classes
45
- - `generated/*_pb2.pyi` - Type stubs for better IDE support
46
- - `generated/legacy_grpc/*_pb2_grpc.py` - Legacy gRPC clients (backward compatibility)
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "otdf-python-proto"
3
- version = "0.3.3"
3
+ version = "0.3.5"
4
4
  description = "Generated protobuf files for OpenTDF Python SDK"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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(proto_file) as src:
99
+ with proto_file.open() as src:
100
100
  content = src.read()
101
101
 
102
- with open(dest_path, "w") as dst:
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(buf_gen_path) as f:
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(buf_gen_path, "w") as f:
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(proto_file, "r") as file: # noqa: UP015
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(proto_file, "w") as file:
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")
@@ -481,7 +481,7 @@ wheels = [
481
481
 
482
482
  [[package]]
483
483
  name = "otdf-python-proto"
484
- version = "0.3.3"
484
+ version = "0.3.5"
485
485
  source = { editable = "." }
486
486
  dependencies = [
487
487
  { name = "connect-python", extra = ["compiler"] },
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "otdf-python"
3
- version = "0.3.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(creds_path) as f:
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(input_path, "rb") as input_file:
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(output_path, "wb") as output_file:
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
- manifest, size, _ = sdk.create_tdf(
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
- manifest, size, _ = sdk.create_tdf(
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(input_path, "rb") as input_file:
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(output_path, "wb") as output_file:
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(input_path, "rb") as input_file:
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(input_path, "rb") as input_file:
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.debug("", exc_info=True)
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