fix(tesla): make PEM key handling non-blocking and configurable - #106
Merged
Conversation
added 2 commits
August 2, 2026 15:05
… optional Reading an existing RSA/EC PEM deserialized it inline in _load_pem_private_key, stalling the event loop by ~200ms for a 4096-bit RSA key. Wrap it in asyncio.to_thread like the generation path already does, add get_rsa_private_key(skip_rsa_key_validation=..., defaults False) to let a caller skip cryptography's RSA consistency check for a trusted externally-supplied key, and always skip it for a PEM the library just generated (it can't be mathematically malformed). async_rsa_key_creation (default True) exposes an opt-out back to the older blocking in-process generator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Make PEM key handling non-blocking and optional in tesla_fleet_api/tesla/tesla.py. Today, reading an existing on-disk RSA/EC PEM key deserializes it inline (serialization.load_pem_private_key called synchronously) inside _load_pem_private_key, which stalls the asyncio event loop by ~200ms for a 4096-bit RSA key. Fixed by wrapping that deserialization in asyncio.to_thread, matching the non-blocking pattern already used for RSA key generation (added in PR #105/fm/tfa-rsa-keygen-blocking). Added two new optional constructor params to get_rsa_private_key: (1) skip_rsa_key_validation (default False, preserves current full-validation behavior) which forwards cryptography's unsafe_skip_rsa_key_validation flag when loading an EXISTING externally-supplied RSA PEM from disk, letting a caller who trusts the key skip the expensive RSA consistency check; (2) async_rsa_key_creation (default True, preserves current behavior of using the isolated-subprocess keygen from PR #105) which, when set False, opts back into the older simpler blocking in-process RSA key generator instead of the subprocess-based one - useful for tests or environments where subprocess spawning is undesired. Also: a PEM this library just generated internally (via _deserialize_rsa_pem, right after generation) now always skips RSA validation unconditionally regardless of the new param, since a freshly-generated key by this library cannot be mathematically malformed (only structurally invalid, which still raises ValueError on PEM parse failure) - so revalidating it was pure wasted stall. Deliberately no redesign of key handling: pure parameter plumbing and scoped application of an existing cryptography library flag. Added 12 new tests in tests/test_tesla_private_key.py covering: default full validation on existing-key read, skip_rsa_key_validation forwarding, internal-PEM scoped skip, async_rsa_key_creation True/False behavior (subprocess called vs not), non-blocking existing-key read via heartbeat test, and default-behavior parity. Also added one bullet to AGENTS.md documenting this. Full test suite (524 tests), ruff check/format, and pyright strict all pass.
What Changed
Risk Assessment
✅ Low: The change is narrowly scoped and consistently moves existing PEM deserialization off the event loop while preserving validation and asynchronous-creation defaults and correctly limiting validation skipping to authorized RSA paths.
Testing
All 30 targeted private-key tests passed, including the new optional-validation and non-blocking cases; a public-API transcript additionally demonstrated round-trip key integrity, 39 heartbeat ticks during two delayed PEM loads, default/opt-out validation forwarding, and subprocess-free creation. No UI evidence was applicable because this is a Python library API change.
Evidence: Public API PEM behavior evidence
existing_key_round_trip=True validation_flags_default_then_opt_out=[False, True] heartbeat_ticks_during_two_200ms_deserializations=39 event_loop_remained_responsive=True blocking_creation_subprocess_calls=0 generated_key_type=RSAPrivateKey generated_pem_exists=TruePipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
uv run pytest -q tests/test_tesla_private_key.py::GetRsaPrivateKeyOptionalValidationTestsuv run pytest -q tests/test_tesla_private_key.pyPublic API demonstration using a real on-disk 2048-bit RSA PEM, two deliberately delayed deserializations with an asyncio heartbeat, default and skipped validation modes, andasync_rsa_key_creation=Falsewhile monitoring subprocess callsgit status --shortconfirmed testing left no transient worktree changes✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.