Skip to content

Conversation

@lla-dane
Copy link
Contributor

@lla-dane lla-dane commented Dec 9, 2025

This PR aims to add TLS-security as one of the security options in the TransportUpgrader.

Comment on lines 302 to 303
TLS_PROTOCOL_ID: TLSTransport (
key_pair
),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the option for TLS as the 1st priority option for security upgrade,

@lla-dane
Copy link
Contributor Author

lla-dane commented Dec 9, 2025

@seetadev: For auto-tls to work, the py-libp2p node needs to be on tls as the autotls-broker was negotiating for tls sec-opt ( as mentioned in this comment ). So presently, while using tls as the security-option, after the completion of security upgrade, the muxer upgrade is failing for some reason possibly during read/write operations, which were happing over a tls-secured stream. The logs are mentioned in this comment

@lla-dane lla-dane changed the title Add TLS in the security options WIP: Add TLS in the security options Dec 9, 2025
lla-dane and others added 2 commits December 11, 2025 15:31
- Changed default key generation back to Ed25519 from RSA
- Maintains compatibility with Rust/Go libp2p implementations
- TLS transport still works with Ed25519 keys
- Fix line-too-long errors by shortening log messages
- Fix import order in muxer_multistream.py (move logger after imports)
- Fix type error: handle TProtocol | None from multiselect.negotiate()
- Update logging messages for consistency
- All tests passing (1744 passed, 12 skipped)
- make pr passes: lint, typecheck, and tests all green
@acul71
Copy link
Contributor

acul71 commented Dec 12, 2025

Hi @seetadev @lla-dane
I've done linting, typecheck and test fix.
Review the changes.

Please take a look at #966 (comment) and comment. This will help me understanding better the TLS and certificates ..

@acul71
Copy link
Contributor

acul71 commented Dec 12, 2025

TLS Muxer Upgrade Fix - Response to @lla-dane

Issue Summary

After TLS security upgrade (TLS handshake) completion, the multiplexer upgrade was failing/hanging. The code would print SECRURITY UPGRADE: COMPLETE but never reach MUX UPGRADE: COMPLETE, indicating a problem during the read/write operations in the muxer handshake procedure.

Root Causes Identified

1. Debug Print Statements

  • print() statements were left in the code instead of using proper logging
  • These have been removed and replaced with structured logging

2. TLS Read Hanging Issue (Critical)

  • TLSStreamReadWriter.read() was trying to fill a 65536-byte buffer
  • Muxer negotiation sends small messages (e.g., 20 bytes)
  • The read operation would hang waiting for more data that never comes

3. Server-Side Muxer Negotiation Bug

  • Server was using client-side multiselect method
  • Both client and server were using multistream_client.select_one_of()
  • Server should use multiselect.negotiate() instead

Fixes Applied

Fix 1: TLS Read Returns Data Immediately (Critical Fix)

Problem: TLSStreamReadWriter.read() was trying to fill a 65536-byte buffer, causing it to hang on small messages.

Solution: Changed to return data immediately when available (stream semantics):

# In libp2p/security/tls/io.py - TLSStreamReadWriter.read()
data = self._ssl_socket.read(min(n if n else 65536, 65536))
if data:
    buffer.extend(data)
    logger.debug("TLS read: %d decrypted bytes", len(data))
    # Return immediately when we have data (stream semantics)
    break  # ← Critical: don't wait to fill buffer

Why this works: TLS is a stream protocol. Small messages (like multistream negotiation) should be processed immediately, not buffered until a large chunk is available.

Fix 2: Server-Side Muxer Negotiation

Problem: MuxerMultistream.new_conn() always used multistream_client.select_one_of(), even for server connections.

Solution: Use the correct multiselect method based on connection role:

# In libp2p/stream_muxer/muxer_multistream.py
if conn.is_initiator:
    # Client: select a protocol from the list
    protocol = await self.multistream_client.select_one_of(
        tuple(self.transports.keys()), communicator, self.negotiate_timeout
    )
else:
    # Server: negotiate with the client
    negotiated_protocol, _ = await self.multiselect.negotiate(
        communicator, self.negotiate_timeout
    )
    if negotiated_protocol is None:
        raise MultiselectError(
            "Fail to negotiate a stream muxer protocol: no protocol selected"
        )
    protocol = negotiated_protocol

Why this works: The client (initiator) selects a protocol from a list, while the server (non-initiator) negotiates with the client's request. Using the wrong method causes protocol mismatch.

Fix 3: Removed Debug Prints

All print() statements were replaced with proper logger.debug() calls for structured logging:

# Before:
print("SECRURITY UPGRADE: COMPLETE")
print("MUX UPGRADE: COMPLETE")

# After:
logger.debug("Swarm: security upgrade completed for peer %s", peer_id)
logger.debug("Swarm: muxer upgrade completed for peer %s", peer_id)

Verification

All issues have been resolved and verified:

  • All tests passing: 1744 passed, 12 skipped
  • Bitswap integration tests: 5/5 passing
  • TLS-specific tests: 3/3 passing
  • Rendezvous tests: All passing
  • make pr passes: Lint, typecheck, and tests all green

Technical Details

Why TLS Read Was Hanging

TLS is a stream protocol, not a message-based protocol like Noise. The original implementation tried to read large chunks (65536 bytes), which works for bulk data transfer but fails for small protocol negotiation messages.

Before (hanging):

# Tries to read up to 65536 bytes
data = await self.stream_writer.read(65536)
# If only 20 bytes available, waits for more...

After (fixed):

# Returns immediately when any data is available
data = self._ssl_socket.read(min(n if n else 65536, 65536))
if data:
    break  # Return immediately, don't wait

Why Server-Side Negotiation Was Wrong

The multistream protocol has two roles:

  • Client (Initiator): Sends a list of supported protocols, server responds with selection
  • Server (Non-initiator): Receives client's protocol list, responds with selection

Using the wrong method causes the negotiation to fail because the message flow doesn't match the expected protocol.

Files Modified

  1. libp2p/security/tls/io.py - Fixed TLS read semantics
  2. libp2p/stream_muxer/muxer_multistream.py - Fixed server-side negotiation
  3. libp2p/network/swarm.py - Removed print statements, added proper logging
  4. libp2p/protocol_muxer/multiselect_client.py - Improved logging
  5. libp2p/protocol_muxer/multiselect.py - Improved logging
  6. libp2p/security/security_multistream.py - Improved logging

Conclusion

The muxer upgrade now completes successfully after TLS security upgrade. The root cause was a combination of:

  1. TLS read semantics (waiting for large buffers instead of returning immediately)
  2. Incorrect server-side negotiation logic

Both issues have been fixed and all tests are passing.

@acul71
Copy link
Contributor

acul71 commented Dec 12, 2025

AI Pull Request Review: PR #1085

PR Title: WIP: Add TLS in the security options
Author: lla-dane
Review Date: 2024-12-11
Review Version: 1


1. Summary of Changes

This PR adds TLS (Transport Layer Security) as a security option in the TransportUpgrader, enabling TLS 1.3 encryption for libp2p connections. The implementation follows the libp2p TLS specification and includes:

Key Changes:

  • New TLS Transport Module: Complete TLS 1.3 implementation with certificate generation, peer ID verification, and ALPN support
  • Integration with TransportUpgrader: TLS added as a security option alongside Noise, Secio, and Plaintext
  • Stream-based TLS I/O: Proper handling of TLS as a stream protocol (not message-based like Noise)
  • Mutual TLS Support: Workaround for Python SSL limitations using trusted peer certificates
  • Comprehensive Logging: Replaced debug prints with structured logging throughout
  • Bug Fixes: Fixed TLS read hanging issue and server-side muxer negotiation

Related Context:

Affected Modules:

  • libp2p/__init__.py - Added TLS transport to default security options (Noise primary, TLS fallback)
  • libp2p/security/tls/transport.py - New TLS transport implementation
  • libp2p/security/tls/io.py - TLS stream I/O with proper read/write semantics
  • libp2p/security/tls/certificate.py - Certificate generation and verification utilities
  • libp2p/network/swarm.py - Enhanced logging for security/muxer upgrade tracking
  • libp2p/protocol_muxer/multiselect.py - Added logging for protocol negotiation
  • libp2p/protocol_muxer/multiselect_client.py - Enhanced logging for client-side negotiation
  • libp2p/security/security_multistream.py - Added logging for security upgrade
  • libp2p/stream_muxer/muxer_multistream.py - Fixed server-side muxer negotiation

Breaking Changes:

  • None - TLS is added as an additional option, not replacing existing security transports
  • ✅ Default key type remains Ed25519 (was temporarily changed to RSA but reverted)
  • ✅ Noise remains primary security protocol, TLS is fallback

2. Branch Sync Status and Merge Conflicts

Branch Sync Status

  • Status: ℹ️ Ahead of origin/main
  • Details: Branch is 0 commits behind and 3 commits ahead of origin/main
  • Interpretation: The PR branch contains 3 commits that are not yet in origin/main
  • Recommendation: This is normal for a PR branch. No rebase needed unless requested by maintainers.

Merge Conflict Analysis

  • Conflicts Detected:No conflicts - PR can be merged cleanly
  • The test merge completed successfully without conflicts
  • All files can be merged without manual resolution

3. Strengths

Architecture and Design

  • Well-structured TLS implementation: Follows libp2p TLS specification with proper certificate generation and peer ID verification
  • Stream-based approach: Correctly treats TLS as a stream protocol (not message-based), matching Go's implementation
  • Proper separation of concerns: TLS transport, I/O, and certificate handling are cleanly separated
  • ALPN support: Implements Application-Layer Protocol Negotiation for early muxer negotiation
  • Python SSL limitations documented: Clear comments and documentation about Python's SSL module limitations

Code Quality

  • Comprehensive logging: All debug prints replaced with structured logging using logger.debug()
  • Error handling: Proper exception handling and error propagation throughout
  • Type hints: Good type annotations for better code clarity
  • Code organization: Clean module structure with logical separation

Testing and Reliability

  • All tests passing: 1744 tests passed, 12 skipped, 0 failed
  • TLS-specific tests: Dedicated test suite for TLS functionality
  • Integration tests: Bitswap and Rendezvous tests all passing with TLS
  • Bug fixes verified: Critical TLS read hanging issue and server-side muxer negotiation fixed

Security

  • TLS 1.3 only: Enforces minimum TLS version 1.3 for security
  • Peer ID verification: Implements libp2p peer identity verification from certificates
  • Certificate validation: Proper certificate chain verification with libp2p extensions
  • Mutual TLS support: Workaround implemented for trusted peer scenarios

4. Issues Found

Critical

None - All critical issues have been resolved in this version of the PR.

Major

1. Missing Issue Reference

2. Missing Newsfragment

  • File: newsfragments/ directory
  • Issue: No newsfragment file found for this PR
  • Impact: PR cannot be approved without a newsfragment
  • Suggestion:
    • Create newsfragments/<ISSUE_NUMBER>.feature.rst (or appropriate type)
    • Content should describe user-facing aspects of TLS support
    • Must end with a newline character
    • This is a BLOCKER for PR approval (see section 7)

Minor

1. Documentation Could Be Enhanced

  • File: libp2p/security/tls/transport.py
  • Issue: Some docstrings could be more detailed about Python SSL limitations
  • Suggestion: Consider adding more examples or links to TLS_ANALYSIS.md in docstrings

2. Test Coverage for Edge Cases

  • File: tests/core/security/tls/test_tls.py
  • Issue: Could add more tests for error scenarios (certificate verification failures, handshake timeouts, etc.)
  • Suggestion: Additional edge case tests would improve robustness

5. Security Review

Security Strengths

  • TLS 1.3 enforcement: Only allows TLS 1.3, preventing use of older, less secure versions
  • Peer ID verification: Cryptographically verifies peer identity from certificates
  • Certificate validation: Proper validation of certificate chains and libp2p extensions
  • Self-signed certificate handling: Secure handling of libp2p self-signed certificates

Security Considerations

1. Python SSL Limitations

  • Risk: Python's ssl module cannot request client certificates without CA verification
  • Impact: Medium - Server cannot verify client identity by default (uses placeholder peer ID)
  • Mitigation:
    • Workaround implemented via trust_peer_cert_pem() for trusted peers
    • Client can still verify server identity
    • Documented in TLS_ANALYSIS.md
    • Consider PyOpenSSL for future enhancement

2. Mutual TLS Default Behavior

  • Risk: Default inbound connections use placeholder peer ID when client cert not available
  • Impact: Low - Client can still verify server, and trusted peer scenarios work correctly
  • Mitigation:
    • Well-documented limitation
    • Workaround available for trusted peers
    • Server can identify clients via libp2p identify protocol

3. Certificate Generation

  • Risk: Ephemeral ECDSA P-256 keys used for TLS certificates
  • Impact: Low - This is standard practice and matches libp2p specification
  • Mitigation: Keys are properly generated using secure random number generation

Overall Security Assessment

  • Security Impact: Low to Medium
  • Recommendation: The implementation follows security best practices within Python SSL limitations. The documented workarounds are appropriate for the current constraints.

6. Documentation and Examples

Documentation Status

  • Code comments: Good inline documentation explaining TLS implementation details
  • Python SSL limitations: Well-documented in code comments and TLS_ANALYSIS.md
  • ALPN handling: Clear comments about "libp2p" fallback and server preference
  • ⚠️ API documentation: Could benefit from more detailed docstrings in public methods
  • ⚠️ Usage examples: No explicit usage examples in docstrings or README

Suggestions

  • Consider adding usage examples showing how to use TLS transport
  • Add more detailed docstrings for public API methods
  • Link to TLS_ANALYSIS.md from relevant docstrings

7. Newsfragment Requirement

⚠️ CRITICAL: Newsfragments are MANDATORY for PR approval. Missing or invalid newsfragments are BLOCKERS.

Current Status

  • No newsfragment file found in newsfragments/ directory
  • No issue reference in PR description

Requirements

  1. Issue Reference: PR must reference an issue (e.g., "Fixes AutoTLS Support for py-libp2p #555", "Implements AutoTLS Support for py-libp2p #555")
  2. Newsfragment File: Must create <ISSUE_NUMBER>.<TYPE>.rst in newsfragments/ directory
  3. Content: Must describe user-facing aspects of the change
  4. Format: Must be ReST-formatted and end with a newline

Action Required

  • Severity: CRITICAL / BLOCKER
  • Impact: PR cannot be approved without:
    1. A linked issue (if missing, issue must be opened first - this is mandatory)
    2. A valid newsfragment file using the issue number
  • Suggestion:
    • First, verify the PR references an issue (check if this relates to issue AutoTLS Support for py-libp2p #555 for Auto-TLS)
    • If no issue exists: The PR author MUST open an issue first to describe the change
    • Once an issue exists: Add newsfragment under newsfragments/ following the format <ISSUE_NUMBER>.<TYPE>.rst (e.g., 555.feature.rst)
    • The file must contain ReST-formatted user-facing description and end with a newline
  • Example newsfragment content:
    Added TLS 1.3 as a security option for libp2p connections, enabling encrypted peer-to-peer communication with certificate-based peer identity verification.
    

8. Tests and Validation

Test Execution Summary

  • Total Tests: 1756
  • Passed: 1744 ✅
  • Failed: 0 ✅
  • Skipped: 12
  • Errored: 0 ✅
  • Exit Code: 0 (Success)
  • Execution Time: 91.60s

Test Coverage by Category

TLS-Specific Tests

  • test_tls_basic_handshake - PASSED
  • test_tls_transport - PASSED
  • test_tls_connection - PASSED

Integration Tests

  • Bitswap Integration Tests: 5/5 passed
    • test_file_transfer_between_two_nodes - PASSED
    • test_multiple_blocks_transfer - PASSED
    • test_large_file_transfer - PASSED
    • test_bidirectional_exchange - PASSED
    • test_dont_have_response - PASSED
  • Rendezvous Tests: All passed
  • Pubsub Tests: All passed
  • Other Integration Tests: All passed

Warnings

  • ⚠️ 1 warning in test_muxer_multistream.py::test_new_conn_passes_timeout_to_multistream_client:
    • RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
    • Impact: Low - Test still passes, but should be fixed for clean test output
    • Suggestion: Fix the async mock usage in the test

Linting Results

  • All checks passed
  • ruff (legacy alias): Passed
  • ruff format: Passed
  • pyupgrade: Passed
  • Other pre-commit hooks: All passed

Type Checking Results

  • mypy: Passed
  • pyrefly: Passed
  • No type errors or warnings

Documentation Build Results

  • Documentation build: Successful
  • No build errors or warnings

Overall Test Assessment

  • Excellent test coverage - All critical paths tested
  • No regressions - All existing tests still pass
  • Integration tests verify TLS works correctly with other libp2p components
  • ⚠️ Minor warning in test code should be addressed

9. Recommendations for Improvement

High Priority (Blockers)

  1. ⚠️ Add issue reference - PR must reference an issue (e.g., "Fixes AutoTLS Support for py-libp2p #555" or create new issue)
  2. ⚠️ Create newsfragment - Add <ISSUE_NUMBER>.feature.rst in newsfragments/ directory
  3. ⚠️ Fix test warning - Resolve RuntimeWarning in test_new_conn_passes_timeout_to_multistream_client

Medium Priority

  1. 📝 Enhance documentation - Add more detailed docstrings and usage examples
  2. 📝 Add edge case tests - Test error scenarios (certificate failures, handshake timeouts)
  3. 📝 Consider PyOpenSSL - For future enhancement to address Python SSL limitations

Low Priority

  1. 🔧 Code cleanup - Minor improvements to docstrings and comments
  2. 🔧 Performance optimization - Consider optimizations for certificate generation if needed

10. Questions for the Author

  1. Issue Reference: Does this PR address issue AutoTLS Support for py-libp2p #555 (Auto-TLS support)? If so, please add "Fixes AutoTLS Support for py-libp2p #555" or "Implements AutoTLS Support for py-libp2p #555" to the PR description.

  2. Newsfragment: What issue number should be used for the newsfragment? If no issue exists, one must be opened first.

  3. TLS as Primary: The PR currently uses Noise as primary and TLS as fallback. Is this the intended behavior, or should TLS be primary in certain scenarios?

  4. Python SSL Limitations: Have you considered using PyOpenSSL for more advanced certificate handling, or is the current workaround sufficient for the use case?

  5. Test Warning: The RuntimeWarning in test_new_conn_passes_timeout_to_multistream_client - should this be fixed, or is it acceptable?


11. Overall Assessment

Quality Rating

Excellent - The PR demonstrates high-quality code with proper architecture, comprehensive testing, and good documentation. All critical bugs have been fixed, and the implementation follows libp2p specifications.

Security Impact

Low to Medium - The implementation follows security best practices. Python SSL limitations are well-documented and workarounds are appropriate. No security vulnerabilities identified.

Merge Readiness

Needs fixes - While the code quality is excellent, the PR cannot be approved without:

  1. An issue reference in the PR description
  2. A newsfragment file

These are mandatory requirements, not optional.

Confidence

High - The implementation is solid, all tests pass, and the code follows best practices. The only blockers are administrative requirements (issue reference and newsfragment).

Summary

This PR successfully adds TLS 1.3 support to py-libp2p with excellent code quality and comprehensive testing. The implementation correctly handles TLS as a stream protocol, includes proper peer ID verification, and addresses Python SSL limitations appropriately. All critical bugs (TLS read hanging, server-side muxer negotiation) have been fixed.

The PR is technically ready for merge but requires:

  1. Issue reference in PR description
  2. Newsfragment file creation

Once these administrative requirements are met, the PR can be approved and merged.


Appendix: Key Fixes Applied

Fix 1: TLS Read Hanging Issue (Critical)

  • Problem: TLSStreamReadWriter.read() was trying to fill a 65536-byte buffer, causing hangs on small messages
  • Solution: Return data immediately when available (stream semantics)
  • Status: ✅ Fixed

Fix 2: Server-Side Muxer Negotiation

  • Problem: Server was using client-side multiselect method
  • Solution: Use correct multiselect method based on connection role (initiator vs non-initiator)
  • Status: ✅ Fixed

Fix 3: Debug Print Statements

  • Problem: Debug print() statements left in code
  • Solution: Replaced with structured logger.debug() calls
  • Status: ✅ Fixed

Fix 4: Default Key Type

  • Problem: Temporarily changed from Ed25519 to RSA
  • Solution: Reverted to Ed25519 for better interoperability
  • Status: ✅ Fixed

Review completed: 2024-12-11
Reviewer: AI Assistant
Next steps: Address blockers (issue reference and newsfragment) before approval

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants