temp: impl security modile

This commit is contained in:
Akash Mondal
2025-06-13 08:33:07 +00:00
committed by lla-dane
parent bc2ac47594
commit ce76641ef5
5 changed files with 1275 additions and 357 deletions

View File

@ -1,35 +1,477 @@
"""
Basic QUIC Security implementation for Module 1.
This provides minimal TLS configuration for QUIC transport.
Full implementation will be in Module 5.
QUIC Security implementation for py-libp2p Module 5.
Implements libp2p TLS specification for QUIC transport with peer identity integration.
Based on go-libp2p and js-libp2p security patterns.
"""
from dataclasses import dataclass
import os
import tempfile
import logging
import time
from typing import Optional, Tuple
from libp2p.crypto.keys import PrivateKey
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.x509.oid import NameOID
from libp2p.crypto.ed25519 import Ed25519PublicKey
from libp2p.crypto.keys import PrivateKey, PublicKey
from libp2p.crypto.secp256k1 import Secp256k1PublicKey
from libp2p.peer.id import ID
from .exceptions import QUICSecurityError
from .exceptions import (
QUICCertificateError,
QUICPeerVerificationError,
)
logger = logging.getLogger(__name__)
# libp2p TLS Extension OID - Official libp2p specification
LIBP2P_TLS_EXTENSION_OID = x509.ObjectIdentifier("1.3.6.1.4.1.53594.1.1")
# Certificate validity period
CERTIFICATE_VALIDITY_DAYS = 365
CERTIFICATE_NOT_BEFORE_BUFFER = 3600 # 1 hour before now
@dataclass
class TLSConfig:
"""TLS configuration for QUIC transport."""
"""TLS configuration for QUIC transport with libp2p extensions."""
cert_file: str
key_file: str
ca_file: str | None = None
certificate: x509.Certificate
private_key: ec.EllipticCurvePrivateKey | rsa.RSAPrivateKey
peer_id: ID
def get_certificate_der(self) -> bytes:
"""Get certificate in DER format for aioquic."""
return self.certificate.public_bytes(serialization.Encoding.DER)
def get_private_key_der(self) -> bytes:
"""Get private key in DER format for aioquic."""
return self.private_key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
class LibP2PExtensionHandler:
"""
Handles libp2p-specific TLS extensions for peer identity verification.
Based on libp2p TLS specification:
https://github.com/libp2p/specs/blob/master/tls/tls.md
"""
@staticmethod
def create_signed_key_extension(
libp2p_private_key: PrivateKey, cert_public_key: bytes
) -> bytes:
"""
Create the libp2p Public Key Extension with signed key proof.
The extension contains:
1. The libp2p public key
2. A signature proving ownership of the private key
Args:
libp2p_private_key: The libp2p identity private key
cert_public_key: The certificate's public key bytes
Returns:
ASN.1 encoded extension value
"""
try:
# Get the libp2p public key
libp2p_public_key = libp2p_private_key.get_public_key()
# Create the signature payload: "libp2p-tls-handshake:" + cert_public_key
signature_payload = b"libp2p-tls-handshake:" + cert_public_key
# Sign the payload with the libp2p private key
signature = libp2p_private_key.sign(signature_payload)
# Create the SignedKey structure (simplified ASN.1 encoding)
# In a full implementation, this would use proper ASN.1 encoding
public_key_bytes = libp2p_public_key.serialize()
# Simple encoding: [public_key_length][public_key][signature_length][signature]
extension_data = (
len(public_key_bytes).to_bytes(4, byteorder="big")
+ public_key_bytes
+ len(signature).to_bytes(4, byteorder="big")
+ signature
)
return extension_data
except Exception as e:
raise QUICCertificateError(
f"Failed to create signed key extension: {e}"
) from e
@staticmethod
def parse_signed_key_extension(extension_data: bytes) -> Tuple[PublicKey, bytes]:
"""
Parse the libp2p Public Key Extension to extract public key and signature.
Args:
extension_data: The extension data bytes
Returns:
Tuple of (libp2p_public_key, signature)
Raises:
QUICCertificateError: If extension parsing fails
"""
try:
offset = 0
# Parse public key length and data
if len(extension_data) < 4:
raise QUICCertificateError("Extension too short for public key length")
public_key_length = int.from_bytes(
extension_data[offset : offset + 4], byteorder="big"
)
offset += 4
if len(extension_data) < offset + public_key_length:
raise QUICCertificateError("Extension too short for public key data")
public_key_bytes = extension_data[offset : offset + public_key_length]
offset += public_key_length
# Parse signature length and data
if len(extension_data) < offset + 4:
raise QUICCertificateError("Extension too short for signature length")
signature_length = int.from_bytes(
extension_data[offset : offset + 4], byteorder="big"
)
offset += 4
if len(extension_data) < offset + signature_length:
raise QUICCertificateError("Extension too short for signature data")
signature = extension_data[offset : offset + signature_length]
# Deserialize the public key
# This is a simplified approach - full implementation would handle all key types
public_key = LibP2PKeyConverter.deserialize_public_key(public_key_bytes)
return public_key, signature
except Exception as e:
raise QUICCertificateError(
f"Failed to parse signed key extension: {e}"
) from e
class LibP2PKeyConverter:
"""
Converts between libp2p key formats and cryptography library formats.
Handles different key types: Ed25519, Secp256k1, RSA, ECDSA.
"""
@staticmethod
def libp2p_to_tls_private_key(
libp2p_key: PrivateKey,
) -> ec.EllipticCurvePrivateKey | rsa.RSAPrivateKey:
"""
Convert libp2p private key to TLS-compatible private key.
For certificate generation, we create a separate ephemeral key
rather than using the libp2p identity key directly.
"""
# For QUIC, we prefer ECDSA keys for smaller certificates
# Generate ephemeral P-256 key for certificate signing
private_key = ec.generate_private_key(ec.SECP256R1())
return private_key
@staticmethod
def serialize_public_key(public_key: PublicKey) -> bytes:
"""Serialize libp2p public key to bytes."""
return public_key.serialize()
@staticmethod
def deserialize_public_key(key_bytes: bytes) -> PublicKey:
"""
Deserialize libp2p public key from bytes.
This is a simplified implementation - full version would handle
all libp2p key types and proper deserialization.
"""
# For now, assume Ed25519 keys (most common in libp2p)
# Full implementation would detect key type from bytes
try:
return Ed25519PublicKey.deserialize(key_bytes)
except Exception:
# Fallback to other key types
try:
return Secp256k1PublicKey.deserialize(key_bytes)
except Exception:
raise QUICCertificateError("Unsupported key type in extension")
class CertificateGenerator:
"""
Generates X.509 certificates with libp2p peer identity extensions.
Follows libp2p TLS specification for QUIC transport.
"""
def __init__(self):
self.extension_handler = LibP2PExtensionHandler()
self.key_converter = LibP2PKeyConverter()
def generate_certificate(
self,
libp2p_private_key: PrivateKey,
peer_id: ID,
validity_days: int = CERTIFICATE_VALIDITY_DAYS,
) -> TLSConfig:
"""
Generate a TLS certificate with embedded libp2p peer identity.
Args:
libp2p_private_key: The libp2p identity private key
peer_id: The libp2p peer ID
validity_days: Certificate validity period in days
Returns:
TLSConfig with certificate and private key
Raises:
QUICCertificateError: If certificate generation fails
"""
try:
# Generate ephemeral private key for certificate
cert_private_key = self.key_converter.libp2p_to_tls_private_key(
libp2p_private_key
)
cert_public_key = cert_private_key.public_key()
# Get certificate public key bytes for extension
cert_public_key_bytes = cert_public_key.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
# Create libp2p extension with signed key proof
extension_data = self.extension_handler.create_signed_key_extension(
libp2p_private_key, cert_public_key_bytes
)
# Set validity period
now = time.time()
not_before = time.gmtime(now - CERTIFICATE_NOT_BEFORE_BUFFER)
not_after = time.gmtime(now + (validity_days * 24 * 3600))
# Build certificate
certificate = (
x509.CertificateBuilder()
.subject_name(
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, str(peer_id))])
)
.issuer_name(
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, str(peer_id))])
)
.public_key(cert_public_key)
.serial_number(int(now)) # Use timestamp as serial number
.not_valid_before(time.struct_time(not_before))
.not_valid_after(time.struct_time(not_after))
.add_extension(
x509.UnrecognizedExtension(
oid=LIBP2P_TLS_EXTENSION_OID, value=extension_data
),
critical=True, # This extension is critical for libp2p
)
.sign(cert_private_key, hashes.SHA256())
)
logger.info(f"Generated libp2p TLS certificate for peer {peer_id}")
return TLSConfig(
certificate=certificate, private_key=cert_private_key, peer_id=peer_id
)
except Exception as e:
raise QUICCertificateError(f"Failed to generate certificate: {e}") from e
class PeerAuthenticator:
"""
Authenticates remote peers using libp2p TLS certificates.
Validates both TLS certificate integrity and libp2p peer identity.
"""
def __init__(self):
self.extension_handler = LibP2PExtensionHandler()
def verify_peer_certificate(
self, certificate: x509.Certificate, expected_peer_id: Optional[ID] = None
) -> ID:
"""
Verify a peer's TLS certificate and extract/validate peer identity.
Args:
certificate: The peer's TLS certificate
expected_peer_id: Expected peer ID (for outbound connections)
Returns:
The verified peer ID
Raises:
QUICPeerVerificationError: If verification fails
"""
try:
# Extract libp2p extension
libp2p_extension = None
for extension in certificate.extensions:
if extension.oid == LIBP2P_TLS_EXTENSION_OID:
libp2p_extension = extension
break
if not libp2p_extension:
raise QUICPeerVerificationError("Certificate missing libp2p extension")
# Parse the extension to get public key and signature
public_key, signature = self.extension_handler.parse_signed_key_extension(
libp2p_extension.value
)
# Get certificate public key for signature verification
cert_public_key_bytes = certificate.public_key().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
# Verify the signature proves ownership of the libp2p private key
signature_payload = b"libp2p-tls-handshake:" + cert_public_key_bytes
try:
public_key.verify(signature, signature_payload)
except Exception as e:
raise QUICPeerVerificationError(
f"Invalid signature in libp2p extension: {e}"
)
# Derive peer ID from public key
derived_peer_id = ID.from_pubkey(public_key)
# Verify against expected peer ID if provided
if expected_peer_id and derived_peer_id != expected_peer_id:
raise QUICPeerVerificationError(
f"Peer ID mismatch: expected {expected_peer_id}, got {derived_peer_id}"
)
logger.info(f"Successfully verified peer certificate for {derived_peer_id}")
return derived_peer_id
except QUICPeerVerificationError:
raise
except Exception as e:
raise QUICPeerVerificationError(
f"Certificate verification failed: {e}"
) from e
class QUICTLSConfigManager:
"""
Manages TLS configuration for QUIC transport with libp2p security.
Integrates with aioquic's TLS configuration system.
"""
def __init__(self, libp2p_private_key: PrivateKey, peer_id: ID):
self.libp2p_private_key = libp2p_private_key
self.peer_id = peer_id
self.certificate_generator = CertificateGenerator()
self.peer_authenticator = PeerAuthenticator()
# Generate certificate for this peer
self.tls_config = self.certificate_generator.generate_certificate(
libp2p_private_key, peer_id
)
def create_server_config(self) -> dict:
"""
Create aioquic server configuration with libp2p TLS settings.
Returns:
Configuration dictionary for aioquic QuicConfiguration
"""
return {
"certificate": self.tls_config.get_certificate_der(),
"private_key": self.tls_config.get_private_key_der(),
"alpn_protocols": ["libp2p"], # Required ALPN protocol
"verify_mode": True, # Require client certificates
}
def create_client_config(self) -> dict:
"""
Create aioquic client configuration with libp2p TLS settings.
Returns:
Configuration dictionary for aioquic QuicConfiguration
"""
return {
"certificate": self.tls_config.get_certificate_der(),
"private_key": self.tls_config.get_private_key_der(),
"alpn_protocols": ["libp2p"], # Required ALPN protocol
"verify_mode": True, # Verify server certificate
}
def verify_peer_identity(
self, peer_certificate: x509.Certificate, expected_peer_id: Optional[ID] = None
) -> ID:
"""
Verify remote peer's identity from their TLS certificate.
Args:
peer_certificate: Remote peer's TLS certificate
expected_peer_id: Expected peer ID (for outbound connections)
Returns:
Verified peer ID
"""
return self.peer_authenticator.verify_peer_certificate(
peer_certificate, expected_peer_id
)
def get_local_peer_id(self) -> ID:
"""Get the local peer ID."""
return self.peer_id
# Factory function for creating QUIC security transport
def create_quic_security_transport(
libp2p_private_key: PrivateKey, peer_id: ID
) -> QUICTLSConfigManager:
"""
Factory function to create QUIC security transport.
Args:
libp2p_private_key: The libp2p identity private key
peer_id: The libp2p peer ID
Returns:
Configured QUIC TLS manager
"""
return QUICTLSConfigManager(libp2p_private_key, peer_id)
# Legacy compatibility functions for existing code
def generate_libp2p_tls_config(private_key: PrivateKey, peer_id: ID) -> TLSConfig:
"""
Generate TLS configuration with libp2p peer identity.
This is a basic implementation for Module 1.
Full implementation with proper libp2p TLS spec compliance
will be provided in Module 5.
Legacy function for compatibility with existing transport code.
Args:
private_key: libp2p private key
@ -38,85 +480,17 @@ def generate_libp2p_tls_config(private_key: PrivateKey, peer_id: ID) -> TLSConfi
Returns:
TLS configuration
Raises:
QUICSecurityError: If TLS configuration generation fails
"""
try:
# TODO: Implement proper libp2p TLS certificate generation
# This should follow the libp2p TLS specification:
# https://github.com/libp2p/specs/blob/master/tls/tls.md
# For now, create a basic self-signed certificate
# This is a placeholder implementation
# Create temporary files for cert and key
with tempfile.NamedTemporaryFile(
mode="w", suffix=".pem", delete=False
) as cert_file:
cert_path = cert_file.name
# Write placeholder certificate
cert_file.write(_generate_placeholder_cert(peer_id))
with tempfile.NamedTemporaryFile(
mode="w", suffix=".key", delete=False
) as key_file:
key_path = key_file.name
# Write placeholder private key
key_file.write(_generate_placeholder_key(private_key))
return TLSConfig(cert_file=cert_path, key_file=key_path)
except Exception as e:
raise QUICSecurityError(f"Failed to generate TLS config: {e}") from e
def _generate_placeholder_cert(peer_id: ID) -> str:
"""
Generate a placeholder certificate.
This is a temporary implementation for Module 1.
Real implementation will embed the peer ID in the certificate
following the libp2p TLS specification.
"""
# This is a placeholder - real implementation needed
return f"""-----BEGIN CERTIFICATE-----
# Placeholder certificate for peer {peer_id}
# TODO: Implement proper libp2p TLS certificate generation
# This should embed the peer ID in a certificate extension
# according to the libp2p TLS specification
-----END CERTIFICATE-----"""
def _generate_placeholder_key(private_key: PrivateKey) -> str:
"""
Generate a placeholder private key.
This is a temporary implementation for Module 1.
Real implementation will use the actual libp2p private key.
"""
# This is a placeholder - real implementation needed
return """-----BEGIN PRIVATE KEY-----
# Placeholder private key
# TODO: Convert libp2p private key to TLS-compatible format
-----END PRIVATE KEY-----"""
generator = CertificateGenerator()
return generator.generate_certificate(private_key, peer_id)
def cleanup_tls_config(config: TLSConfig) -> None:
"""
Clean up temporary TLS files.
Args:
config: TLS configuration to clean up
Clean up TLS configuration.
For the new implementation, this is mostly a no-op since we don't use
temporary files, but kept for compatibility.
"""
try:
if os.path.exists(config.cert_file):
os.unlink(config.cert_file)
if os.path.exists(config.key_file):
os.unlink(config.key_file)
if config.ca_file and os.path.exists(config.ca_file):
os.unlink(config.ca_file)
except Exception:
# Ignore cleanup errors
pass
# New implementation doesn't use temporary files
logger.debug("TLS config cleanup completed")