diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/REQUESTED b/Backend/=1.0.1 similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/REQUESTED rename to Backend/=1.0.1 diff --git a/Backend/alembic/versions/08e2f866e131_add_mfa_fields_to_users.py b/Backend/alembic/versions/08e2f866e131_add_mfa_fields_to_users.py new file mode 100644 index 00000000..0874374e --- /dev/null +++ b/Backend/alembic/versions/08e2f866e131_add_mfa_fields_to_users.py @@ -0,0 +1,31 @@ +"""add_mfa_fields_to_users + +Revision ID: 08e2f866e131 +Revises: add_badges_to_page_content +Create Date: 2025-11-19 11:13:30.376194 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '08e2f866e131' +down_revision = 'add_badges_to_page_content' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Add MFA fields to users table + op.add_column('users', sa.Column('mfa_enabled', sa.Boolean(), nullable=False, server_default='0')) + op.add_column('users', sa.Column('mfa_secret', sa.String(255), nullable=True)) + op.add_column('users', sa.Column('mfa_backup_codes', sa.Text(), nullable=True)) + + +def downgrade() -> None: + # Remove MFA fields from users table + op.drop_column('users', 'mfa_backup_codes') + op.drop_column('users', 'mfa_secret') + op.drop_column('users', 'mfa_enabled') + diff --git a/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc b/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc new file mode 100644 index 00000000..be1ff3d8 Binary files /dev/null and b/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc b/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc new file mode 100644 index 00000000..c8a64c25 Binary files /dev/null and b/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc differ diff --git a/Backend/alembic/versions/d9aff6c5f0d4_add_paypal_payment_method.py b/Backend/alembic/versions/d9aff6c5f0d4_add_paypal_payment_method.py new file mode 100644 index 00000000..b3798634 --- /dev/null +++ b/Backend/alembic/versions/d9aff6c5f0d4_add_paypal_payment_method.py @@ -0,0 +1,51 @@ +"""add_paypal_payment_method + +Revision ID: d9aff6c5f0d4 +Revises: 08e2f866e131 +Create Date: 2025-11-19 12:07:50.703320 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + + +# revision identifiers, used by Alembic. +revision = 'd9aff6c5f0d4' +down_revision = '08e2f866e131' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Note: MySQL ENUM modifications can be tricky. + # If payments table already has data with existing enum values, + # we need to preserve them when adding 'paypal' + + # For MySQL, we need to alter the ENUM column to include the new value + # Check if we're using MySQL + bind = op.get_bind() + if bind.dialect.name == 'mysql': + # Alter the ENUM column to include 'paypal' + # This preserves existing values and adds 'paypal' + op.execute( + "ALTER TABLE payments MODIFY COLUMN payment_method ENUM('cash', 'credit_card', 'debit_card', 'bank_transfer', 'e_wallet', 'stripe', 'paypal') NOT NULL" + ) + else: + # For other databases (PostgreSQL, SQLite), enum changes are handled differently + # For SQLite, this might not be needed as it doesn't enforce enum constraints + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # Remove 'paypal' from the ENUM (be careful if there are existing paypal payments) + bind = op.get_bind() + if bind.dialect.name == 'mysql': + # First, check if there are any paypal payments - if so, this will fail + # In production, you'd want to migrate existing paypal payments first + op.execute( + "ALTER TABLE payments MODIFY COLUMN payment_method ENUM('cash', 'credit_card', 'debit_card', 'bank_transfer', 'e_wallet', 'stripe') NOT NULL" + ) + # ### end Alembic commands ### + diff --git a/Backend/requirements.txt b/Backend/requirements.txt index f805fe39..48b60df4 100644 --- a/Backend/requirements.txt +++ b/Backend/requirements.txt @@ -3,7 +3,7 @@ uvicorn[standard]==0.24.0 python-dotenv==1.0.0 sqlalchemy==2.0.23 pymysql==1.1.0 -cryptography==41.0.7 +cryptography>=41.0.7 python-jose[cryptography]==3.3.0 bcrypt==4.1.2 python-multipart==0.0.6 @@ -17,6 +17,9 @@ aiosmtplib==3.0.1 jinja2==3.1.2 alembic==1.12.1 stripe>=13.2.0 +paypal-checkout-serversdk>=1.0.3 +pyotp==2.9.0 +qrcode[pil]==7.4.2 # Enterprise features (optional but recommended) # redis==5.0.1 # Uncomment if using Redis caching diff --git a/Backend/src/config/__pycache__/settings.cpython-312.pyc b/Backend/src/config/__pycache__/settings.cpython-312.pyc index ed6ad98b..0e678cda 100644 Binary files a/Backend/src/config/__pycache__/settings.cpython-312.pyc and b/Backend/src/config/__pycache__/settings.cpython-312.pyc differ diff --git a/Backend/src/config/settings.py b/Backend/src/config/settings.py index e21d4689..733779c8 100644 --- a/Backend/src/config/settings.py +++ b/Backend/src/config/settings.py @@ -96,6 +96,11 @@ class Settings(BaseSettings): STRIPE_PUBLISHABLE_KEY: str = Field(default="", description="Stripe publishable key") STRIPE_WEBHOOK_SECRET: str = Field(default="", description="Stripe webhook secret") + # PayPal Payment Gateway + PAYPAL_CLIENT_ID: str = Field(default="", description="PayPal client ID") + PAYPAL_CLIENT_SECRET: str = Field(default="", description="PayPal client secret") + PAYPAL_MODE: str = Field(default="sandbox", description="PayPal mode: sandbox or live") + @property def database_url(self) -> str: """Construct database URL""" diff --git a/Backend/src/models/__pycache__/payment.cpython-312.pyc b/Backend/src/models/__pycache__/payment.cpython-312.pyc index b053a6df..08a69330 100644 Binary files a/Backend/src/models/__pycache__/payment.cpython-312.pyc and b/Backend/src/models/__pycache__/payment.cpython-312.pyc differ diff --git a/Backend/src/models/__pycache__/user.cpython-312.pyc b/Backend/src/models/__pycache__/user.cpython-312.pyc index bb54f889..9ca05695 100644 Binary files a/Backend/src/models/__pycache__/user.cpython-312.pyc and b/Backend/src/models/__pycache__/user.cpython-312.pyc differ diff --git a/Backend/src/models/payment.py b/Backend/src/models/payment.py index 4171c49f..d06f1b26 100644 --- a/Backend/src/models/payment.py +++ b/Backend/src/models/payment.py @@ -12,6 +12,7 @@ class PaymentMethod(str, enum.Enum): bank_transfer = "bank_transfer" e_wallet = "e_wallet" stripe = "stripe" + paypal = "paypal" class PaymentType(str, enum.Enum): diff --git a/Backend/src/models/user.py b/Backend/src/models/user.py index ef7babf3..a89c85f8 100644 --- a/Backend/src/models/user.py +++ b/Backend/src/models/user.py @@ -17,6 +17,9 @@ class User(Base): avatar = Column(String(255), nullable=True) currency = Column(String(3), nullable=False, default='VND') # ISO 4217 currency code is_active = Column(Boolean, nullable=False, default=True) + mfa_enabled = Column(Boolean, nullable=False, default=False) + mfa_secret = Column(String(255), nullable=True) # TOTP secret key (encrypted in production) + mfa_backup_codes = Column(Text, nullable=True) # JSON array of backup codes (hashed) created_at = Column(DateTime, default=datetime.utcnow, nullable=False) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) diff --git a/Backend/src/routes/__pycache__/auth_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/auth_routes.cpython-312.pyc index 8a473243..29636dae 100644 Binary files a/Backend/src/routes/__pycache__/auth_routes.cpython-312.pyc and b/Backend/src/routes/__pycache__/auth_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/booking_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/booking_routes.cpython-312.pyc index ec9b477c..d517e811 100644 Binary files a/Backend/src/routes/__pycache__/booking_routes.cpython-312.pyc and b/Backend/src/routes/__pycache__/booking_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/payment_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/payment_routes.cpython-312.pyc index ee76f0cd..081e677d 100644 Binary files a/Backend/src/routes/__pycache__/payment_routes.cpython-312.pyc and b/Backend/src/routes/__pycache__/payment_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/room_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/room_routes.cpython-312.pyc index 18c8c867..3655bf7b 100644 Binary files a/Backend/src/routes/__pycache__/room_routes.cpython-312.pyc and b/Backend/src/routes/__pycache__/room_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/__pycache__/system_settings_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/system_settings_routes.cpython-312.pyc index 694e174c..e3efd573 100644 Binary files a/Backend/src/routes/__pycache__/system_settings_routes.cpython-312.pyc and b/Backend/src/routes/__pycache__/system_settings_routes.cpython-312.pyc differ diff --git a/Backend/src/routes/auth_routes.py b/Backend/src/routes/auth_routes.py index bf42926f..f19ea597 100644 --- a/Backend/src/routes/auth_routes.py +++ b/Backend/src/routes/auth_routes.py @@ -1,6 +1,10 @@ -from fastapi import APIRouter, Depends, HTTPException, status, Cookie, Response +from fastapi import APIRouter, Depends, HTTPException, status, Cookie, Response, Request, UploadFile, File from fastapi.responses import JSONResponse from sqlalchemy.orm import Session +from pathlib import Path +import aiofiles +import uuid +import os from ..config.database import get_db from ..services.auth_service import auth_service @@ -12,7 +16,11 @@ from ..schemas.auth import ( ResetPasswordRequest, AuthResponse, TokenResponse, - MessageResponse + MessageResponse, + MFAInitResponse, + EnableMFARequest, + VerifyMFARequest, + MFAStatusResponse ) from ..middleware.auth import get_current_user from ..models.user import User @@ -20,6 +28,22 @@ from ..models.user import User router = APIRouter(prefix="/auth", tags=["auth"]) +def get_base_url(request: Request) -> str: + """Get base URL for image normalization""" + return os.getenv("SERVER_URL") or f"http://{request.headers.get('host', 'localhost:8000')}" + + +def normalize_image_url(image_url: str, base_url: str) -> str: + """Normalize image URL to absolute URL""" + if not image_url: + return image_url + if image_url.startswith('http://') or image_url.startswith('https://'): + return image_url + if image_url.startswith('/'): + return f"{base_url}{image_url}" + return f"{base_url}/{image_url}" + + @router.post("/register", status_code=status.HTTP_201_CREATED) async def register( request: RegisterRequest, @@ -79,9 +103,18 @@ async def login( db=db, email=request.email, password=request.password, - remember_me=request.rememberMe or False + remember_me=request.rememberMe or False, + mfa_token=request.mfaToken ) + # Check if MFA is required + if result.get("requires_mfa"): + return { + "status": "success", + "requires_mfa": True, + "user_id": result["user_id"] + } + # Set refresh token as HttpOnly cookie max_age = 7 * 24 * 60 * 60 if request.rememberMe else 1 * 24 * 60 * 60 response.set_cookie( @@ -104,7 +137,7 @@ async def login( } except ValueError as e: error_message = str(e) - status_code = status.HTTP_401_UNAUTHORIZED if "Invalid email or password" in error_message else status.HTTP_400_BAD_REQUEST + status_code = status.HTTP_401_UNAUTHORIZED if "Invalid email or password" in error_message or "Invalid MFA token" in error_message else status.HTTP_400_BAD_REQUEST return JSONResponse( status_code=status_code, content={ @@ -260,3 +293,229 @@ async def reset_password( detail=str(e) ) + +# MFA Routes +from ..services.mfa_service import mfa_service +from ..config.settings import settings + + +@router.get("/mfa/init") +async def init_mfa( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Initialize MFA setup - generate secret and QR code""" + try: + if current_user.mfa_enabled: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="MFA is already enabled" + ) + + secret = mfa_service.generate_secret() + app_name = getattr(settings, 'APP_NAME', 'Hotel Booking') + qr_code = mfa_service.generate_qr_code(secret, current_user.email, app_name) + + return { + "status": "success", + "data": { + "secret": secret, + "qr_code": qr_code + } + } + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error initializing MFA: {str(e)}" + ) + + +@router.post("/mfa/enable") +async def enable_mfa( + request: EnableMFARequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Enable MFA after verifying token""" + try: + success, backup_codes = mfa_service.enable_mfa( + db=db, + user_id=current_user.id, + secret=request.secret, + verification_token=request.verification_token + ) + + return { + "status": "success", + "message": "MFA enabled successfully", + "data": { + "backup_codes": backup_codes + } + } + except ValueError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e) + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error enabling MFA: {str(e)}" + ) + + +@router.post("/mfa/disable") +async def disable_mfa( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Disable MFA""" + try: + mfa_service.disable_mfa(db=db, user_id=current_user.id) + return { + "status": "success", + "message": "MFA disabled successfully" + } + except ValueError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e) + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error disabling MFA: {str(e)}" + ) + + +@router.get("/mfa/status", response_model=MFAStatusResponse) +async def get_mfa_status( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Get MFA status for current user""" + try: + status_data = mfa_service.get_mfa_status(db=db, user_id=current_user.id) + return status_data + except ValueError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e) + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error getting MFA status: {str(e)}" + ) + + +@router.post("/mfa/regenerate-backup-codes") +async def regenerate_backup_codes( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Regenerate backup codes for MFA""" + try: + backup_codes = mfa_service.regenerate_backup_codes(db=db, user_id=current_user.id) + return { + "status": "success", + "message": "Backup codes regenerated successfully", + "data": { + "backup_codes": backup_codes + } + } + except ValueError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e) + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error regenerating backup codes: {str(e)}" + ) + + +@router.post("/avatar/upload") +async def upload_avatar( + request: Request, + image: UploadFile = File(...), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Upload user avatar""" + try: + # Validate file type + if not image.content_type or not image.content_type.startswith('image/'): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="File must be an image" + ) + + # Validate file size (max 2MB) + content = await image.read() + if len(content) > 2 * 1024 * 1024: # 2MB + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Avatar file size must be less than 2MB" + ) + + # Create uploads directory + upload_dir = Path(__file__).parent.parent.parent / "uploads" / "avatars" + upload_dir.mkdir(parents=True, exist_ok=True) + + # Delete old avatar if exists + if current_user.avatar: + old_avatar_path = Path(__file__).parent.parent.parent / current_user.avatar.lstrip('/') + if old_avatar_path.exists() and old_avatar_path.is_file(): + try: + old_avatar_path.unlink() + except Exception: + pass # Ignore deletion errors + + # Generate filename + ext = Path(image.filename).suffix or '.png' + filename = f"avatar-{current_user.id}-{uuid.uuid4()}{ext}" + file_path = upload_dir / filename + + # Save file + async with aiofiles.open(file_path, 'wb') as f: + await f.write(content) + + # Update user avatar + image_url = f"/uploads/avatars/{filename}" + current_user.avatar = image_url + db.commit() + db.refresh(current_user) + + # Return the image URL + base_url = get_base_url(request) + full_url = normalize_image_url(image_url, base_url) + + return { + "status": "success", + "message": "Avatar uploaded successfully", + "data": { + "avatar_url": image_url, + "full_url": full_url, + "user": { + "id": current_user.id, + "name": current_user.full_name, + "email": current_user.email, + "phone": current_user.phone, + "avatar": image_url, + "role": current_user.role.name if current_user.role else "customer" + } + } + } + except HTTPException: + raise + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error uploading avatar: {str(e)}" + ) + diff --git a/Backend/src/routes/booking_routes.py b/Backend/src/routes/booking_routes.py index 5bd28582..7f677b51 100644 --- a/Backend/src/routes/booking_routes.py +++ b/Backend/src/routes/booking_routes.py @@ -202,6 +202,16 @@ async def create_booking( ): """Create new booking""" try: + import logging + logger = logging.getLogger(__name__) + + # Validate that booking_data is a dict + if not isinstance(booking_data, dict): + logger.error(f"Invalid booking_data type: {type(booking_data)}, value: {booking_data}") + raise HTTPException(status_code=400, detail="Invalid request body. Expected JSON object.") + + logger.info(f"Received booking request from user {current_user.id}: {booking_data}") + room_id = booking_data.get("room_id") check_in_date = booking_data.get("check_in_date") check_out_date = booking_data.get("check_out_date") @@ -210,8 +220,21 @@ async def create_booking( notes = booking_data.get("notes") payment_method = booking_data.get("payment_method", "cash") - if not all([room_id, check_in_date, check_out_date, total_price]): - raise HTTPException(status_code=400, detail="Missing required booking fields") + # Detailed validation with specific error messages + missing_fields = [] + if not room_id: + missing_fields.append("room_id") + if not check_in_date: + missing_fields.append("check_in_date") + if not check_out_date: + missing_fields.append("check_out_date") + if total_price is None: + missing_fields.append("total_price") + + if missing_fields: + error_msg = f"Missing required booking fields: {', '.join(missing_fields)}" + logger.error(error_msg) + raise HTTPException(status_code=400, detail=error_msg) # Check if room exists room = db.query(Room).filter(Room.id == room_id).first() @@ -250,15 +273,15 @@ async def create_booking( booking_number = generate_booking_number() # Determine if deposit is required - # Cash requires deposit, Stripe doesn't require deposit (full payment or deposit handled via payment flow) + # Cash requires deposit, Stripe and PayPal don't require deposit (full payment or deposit handled via payment flow) requires_deposit = payment_method == "cash" deposit_percentage = 20 if requires_deposit else 0 deposit_amount = (float(total_price) * deposit_percentage) / 100 if requires_deposit else 0 - # For Stripe, booking can be confirmed immediately after payment + # For Stripe and PayPal, booking can be confirmed immediately after payment initial_status = BookingStatus.pending - if payment_method == "stripe": - # Will be confirmed after successful Stripe payment + if payment_method in ["stripe", "paypal"]: + # Will be confirmed after successful payment initial_status = BookingStatus.pending # Create booking @@ -279,19 +302,32 @@ async def create_booking( db.add(booking) db.flush() - # Create payment record if Stripe payment method is selected - if payment_method == "stripe": + # Create payment record if Stripe or PayPal payment method is selected + if payment_method in ["stripe", "paypal"]: from ..models.payment import Payment, PaymentMethod, PaymentStatus, PaymentType + if payment_method == "stripe": + payment_method_enum = PaymentMethod.stripe + elif payment_method == "paypal": + payment_method_enum = PaymentMethod.paypal + else: + # This shouldn't happen, but just in case + logger.warning(f"Unexpected payment_method: {payment_method}, defaulting to stripe") + payment_method_enum = PaymentMethod.stripe + + logger.info(f"Creating payment for booking {booking.id} with payment_method: {payment_method} -> enum: {payment_method_enum.value}") + payment = Payment( booking_id=booking.id, amount=total_price, - payment_method=PaymentMethod.stripe, + payment_method=payment_method_enum, payment_type=PaymentType.full, payment_status=PaymentStatus.pending, payment_date=None, ) db.add(payment) db.flush() + + logger.info(f"Payment created: ID={payment.id}, method={payment.payment_method.value if hasattr(payment.payment_method, 'value') else payment.payment_method}") # Create deposit payment if required (for cash method) # Note: For cash payments, deposit is paid on arrival, so we don't create a pending payment record @@ -301,7 +337,7 @@ async def create_booking( services = booking_data.get("services", []) if services: from ..models.service import Service - from ..models.service_usage import ServiceUsage + # ServiceUsage is already imported at the top of the file for service_item in services: service_id = service_item.get("service_id") @@ -354,8 +390,10 @@ async def create_booking( except Exception as e: # Log error but don't fail booking creation if invoice creation fails import logging + import traceback logger = logging.getLogger(__name__) logger.error(f"Failed to create invoice for booking {booking.id}: {str(e)}") + logger.error(f"Traceback: {traceback.format_exc()}") # Fetch with relations for proper serialization (eager load payments and service_usages) from sqlalchemy.orm import joinedload, selectinload @@ -369,12 +407,25 @@ async def create_booking( payment_status_from_payments = "unpaid" if booking.payments: latest_payment = sorted(booking.payments, key=lambda p: p.created_at, reverse=True)[0] - payment_method_from_payments = latest_payment.payment_method.value if isinstance(latest_payment.payment_method, PaymentMethod) else latest_payment.payment_method + # Safely extract payment method value + if isinstance(latest_payment.payment_method, PaymentMethod): + payment_method_from_payments = latest_payment.payment_method.value + elif hasattr(latest_payment.payment_method, 'value'): + payment_method_from_payments = latest_payment.payment_method.value + else: + payment_method_from_payments = str(latest_payment.payment_method) + + logger.info(f"Booking {booking.id} - Latest payment method: {payment_method_from_payments}, raw: {latest_payment.payment_method}") + if latest_payment.payment_status == PaymentStatus.completed: payment_status_from_payments = "paid" elif latest_payment.payment_status == PaymentStatus.refunded: payment_status_from_payments = "refunded" + # Use payment_method from payments if available, otherwise fall back to request payment_method + final_payment_method = payment_method_from_payments if payment_method_from_payments else payment_method + logger.info(f"Booking {booking.id} - Final payment_method: {final_payment_method} (from_payments: {payment_method_from_payments}, request: {payment_method})") + # Serialize booking properly booking_dict = { "id": booking.id, @@ -386,7 +437,7 @@ async def create_booking( "guest_count": booking.num_guests, "total_price": float(booking.total_price) if booking.total_price else 0.0, "status": booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, - "payment_method": payment_method_from_payments or payment_method, + "payment_method": final_payment_method, "payment_status": payment_status_from_payments, "deposit_paid": booking.deposit_paid, "requires_deposit": booking.requires_deposit, @@ -408,7 +459,7 @@ async def create_booking( "id": p.id, "booking_id": p.booking_id, "amount": float(p.amount) if p.amount else 0.0, - "payment_method": p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method, + "payment_method": p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else (p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method)), "payment_type": p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type, "deposit_percentage": p.deposit_percentage, "payment_status": p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, @@ -495,6 +546,11 @@ async def create_booking( except HTTPException: raise except Exception as e: + import logging + import traceback + logger = logging.getLogger(__name__) + logger.error(f"Error creating booking (payment_method: {payment_method}): {str(e)}") + logger.error(f"Traceback: {traceback.format_exc()}") db.rollback() raise HTTPException(status_code=500, detail=str(e)) @@ -530,17 +586,33 @@ async def get_booking_by_id( # Determine payment_method and payment_status from payments # Get latest payment efficiently (already loaded via joinedload) - payment_method = None + import logging + logger = logging.getLogger(__name__) + + payment_method_from_payments = None payment_status = "unpaid" if booking.payments: # Find latest payment (payments are already loaded, so this is fast) latest_payment = max(booking.payments, key=lambda p: p.created_at if p.created_at else datetime.min) - payment_method = latest_payment.payment_method.value if isinstance(latest_payment.payment_method, PaymentMethod) else latest_payment.payment_method + # Safely extract payment method value + if isinstance(latest_payment.payment_method, PaymentMethod): + payment_method_from_payments = latest_payment.payment_method.value + elif hasattr(latest_payment.payment_method, 'value'): + payment_method_from_payments = latest_payment.payment_method.value + else: + payment_method_from_payments = str(latest_payment.payment_method) + + logger.info(f"Get booking {id} - Latest payment method: {payment_method_from_payments}, raw: {latest_payment.payment_method}") + if latest_payment.payment_status == PaymentStatus.completed: payment_status = "paid" elif latest_payment.payment_status == PaymentStatus.refunded: payment_status = "refunded" + # Use payment_method from payments, fallback to "cash" if no payments + final_payment_method = payment_method_from_payments if payment_method_from_payments else "cash" + logger.info(f"Get booking {id} - Final payment_method: {final_payment_method}") + booking_dict = { "id": booking.id, "booking_number": booking.booking_number, @@ -551,7 +623,7 @@ async def get_booking_by_id( "guest_count": booking.num_guests, # Frontend expects guest_count "total_price": float(booking.total_price) if booking.total_price else 0.0, "status": booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, - "payment_method": payment_method or "cash", + "payment_method": final_payment_method, "payment_status": payment_status, "deposit_paid": booking.deposit_paid, "requires_deposit": booking.requires_deposit, @@ -605,7 +677,7 @@ async def get_booking_by_id( { "id": p.id, "amount": float(p.amount) if p.amount else 0.0, - "payment_method": p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method, + "payment_method": p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else (p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method)), "payment_status": p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, } for p in booking.payments diff --git a/Backend/src/routes/payment_routes.py b/Backend/src/routes/payment_routes.py index a12ff975..9b18ee13 100644 --- a/Backend/src/routes/payment_routes.py +++ b/Backend/src/routes/payment_routes.py @@ -13,6 +13,7 @@ from ..models.booking import Booking, BookingStatus from ..utils.mailer import send_email from ..utils.email_templates import payment_confirmation_email_template from ..services.stripe_service import StripeService +from ..services.paypal_service import PayPalService router = APIRouter(prefix="/payments", tags=["payments"]) @@ -588,3 +589,187 @@ async def stripe_webhook( except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/paypal/create-order") +async def create_paypal_order( + order_data: dict, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Create a PayPal order""" + try: + # Check if PayPal is configured + from ..services.paypal_service import get_paypal_client_id, get_paypal_client_secret + client_id = get_paypal_client_id(db) + if not client_id: + client_id = settings.PAYPAL_CLIENT_ID + + client_secret = get_paypal_client_secret(db) + if not client_secret: + client_secret = settings.PAYPAL_CLIENT_SECRET + + if not client_id or not client_secret: + raise HTTPException( + status_code=500, + detail="PayPal is not configured. Please configure PayPal settings in Admin Panel or set PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET environment variables." + ) + + booking_id = order_data.get("booking_id") + amount = float(order_data.get("amount", 0)) + currency = order_data.get("currency", "USD") + + if not booking_id or amount <= 0: + raise HTTPException( + status_code=400, + detail="booking_id and amount are required" + ) + + # Validate amount + if amount > 100000: + raise HTTPException( + status_code=400, + detail=f"Amount ${amount:,.2f} exceeds PayPal's maximum of $100,000. Please contact support for large payments." + ) + + # Verify booking exists and user has access + booking = db.query(Booking).filter(Booking.id == booking_id).first() + if not booking: + raise HTTPException(status_code=404, detail="Booking not found") + + if current_user.role_id != 1 and booking.user_id != current_user.id: + raise HTTPException(status_code=403, detail="Forbidden") + + # Get return URLs from request or use defaults + client_url = settings.CLIENT_URL or os.getenv("CLIENT_URL", "http://localhost:5173") + return_url = order_data.get("return_url", f"{client_url}/payment/paypal/return") + cancel_url = order_data.get("cancel_url", f"{client_url}/payment/paypal/cancel") + + # Create PayPal order + order = PayPalService.create_order( + amount=amount, + currency=currency, + metadata={ + "booking_id": str(booking_id), + "booking_number": booking.booking_number, + "user_id": str(current_user.id), + "description": f"Hotel Booking Payment - {booking.booking_number}", + "return_url": return_url, + "cancel_url": cancel_url, + }, + db=db + ) + + if not order.get("approval_url"): + raise HTTPException( + status_code=500, + detail="Failed to create PayPal order. Approval URL is missing." + ) + + return { + "status": "success", + "message": "PayPal order created successfully", + "data": { + "order_id": order["id"], + "approval_url": order["approval_url"], + "status": order["status"], + } + } + except HTTPException: + raise + except ValueError as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"PayPal order creation error: {str(e)}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"Unexpected error creating PayPal order: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/paypal/capture") +async def capture_paypal_payment( + payment_data: dict, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """Capture a PayPal payment""" + try: + order_id = payment_data.get("order_id") + booking_id = payment_data.get("booking_id") + + if not order_id: + raise HTTPException( + status_code=400, + detail="order_id is required" + ) + + # Confirm payment (this commits the transaction internally) + payment = PayPalService.confirm_payment( + order_id=order_id, + db=db, + booking_id=booking_id + ) + + # Ensure the transaction is committed + try: + db.commit() + except Exception: + pass + + # Get fresh booking from database + booking = db.query(Booking).filter(Booking.id == payment["booking_id"]).first() + if booking: + db.refresh(booking) + + # Send payment confirmation email (non-blocking) + if booking and booking.user: + try: + client_url = settings.CLIENT_URL or os.getenv("CLIENT_URL", "http://localhost:5173") + email_html = payment_confirmation_email_template( + booking_number=booking.booking_number, + guest_name=booking.user.full_name, + amount=payment["amount"], + payment_method="paypal", + transaction_id=payment["transaction_id"], + client_url=client_url + ) + await send_email( + to=booking.user.email, + subject=f"Payment Confirmed - {booking.booking_number}", + html=email_html + ) + except Exception as e: + import logging + logger = logging.getLogger(__name__) + logger.warning(f"Failed to send payment confirmation email: {e}") + + return { + "status": "success", + "message": "Payment confirmed successfully", + "data": { + "payment": payment, + "booking": { + "id": booking.id if booking else None, + "booking_number": booking.booking_number if booking else None, + "status": booking.status.value if booking else None, + } + } + } + except HTTPException: + db.rollback() + raise + except ValueError as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"PayPal payment confirmation error: {str(e)}") + db.rollback() + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"Unexpected error confirming PayPal payment: {str(e)}", exc_info=True) + db.rollback() + raise HTTPException(status_code=500, detail=str(e)) diff --git a/Backend/src/routes/room_routes.py b/Backend/src/routes/room_routes.py index 968e9696..7b07d220 100644 --- a/Backend/src/routes/room_routes.py +++ b/Backend/src/routes/room_routes.py @@ -122,17 +122,80 @@ async def search_available_rooms( request: Request, from_date: str = Query(..., alias="from"), to_date: str = Query(..., alias="to"), + roomId: Optional[int] = Query(None, alias="roomId"), type: Optional[str] = Query(None), capacity: Optional[int] = Query(None), page: int = Query(1, ge=1), limit: int = Query(12, ge=1, le=100), db: Session = Depends(get_db) ): - """Search for available rooms""" + """Search for available rooms or check specific room availability""" try: - check_in = datetime.fromisoformat(from_date.replace('Z', '+00:00')) - check_out = datetime.fromisoformat(to_date.replace('Z', '+00:00')) + # Parse dates - handle both date-only and datetime formats + try: + if 'T' in from_date or 'Z' in from_date or '+' in from_date: + check_in = datetime.fromisoformat(from_date.replace('Z', '+00:00')) + else: + check_in = datetime.strptime(from_date, '%Y-%m-%d') + except ValueError: + raise HTTPException(status_code=400, detail=f"Invalid from date format: {from_date}") + try: + if 'T' in to_date or 'Z' in to_date or '+' in to_date: + check_out = datetime.fromisoformat(to_date.replace('Z', '+00:00')) + else: + check_out = datetime.strptime(to_date, '%Y-%m-%d') + except ValueError: + raise HTTPException(status_code=400, detail=f"Invalid to date format: {to_date}") + + # If checking a specific room, handle it differently + if roomId: + # Check if room exists + room = db.query(Room).filter(Room.id == roomId).first() + if not room: + raise HTTPException(status_code=404, detail="Room not found") + + # Check if room is available + if room.status != RoomStatus.available: + return { + "status": "success", + "data": { + "available": False, + "message": "Room is not available", + "room_id": roomId + } + } + + # Check for overlapping bookings + overlapping = db.query(Booking).filter( + and_( + Booking.room_id == roomId, + Booking.status != BookingStatus.cancelled, + Booking.check_in_date < check_out, + Booking.check_out_date > check_in + ) + ).first() + + if overlapping: + return { + "status": "success", + "data": { + "available": False, + "message": "Room is already booked for the selected dates", + "room_id": roomId + } + } + + return { + "status": "success", + "data": { + "available": True, + "message": "Room is available", + "room_id": roomId + } + } + + # Original search functionality if check_in >= check_out: raise HTTPException( status_code=400, diff --git a/Backend/src/routes/system_settings_routes.py b/Backend/src/routes/system_settings_routes.py index ea83a785..ec599fd4 100644 --- a/Backend/src/routes/system_settings_routes.py +++ b/Backend/src/routes/system_settings_routes.py @@ -323,6 +323,162 @@ async def update_stripe_settings( raise HTTPException(status_code=500, detail=str(e)) +@router.get("/paypal") +async def get_paypal_settings( + current_user: User = Depends(authorize_roles("admin")), + db: Session = Depends(get_db) +): + """Get PayPal payment settings (Admin only)""" + try: + client_id_setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_client_id" + ).first() + + client_secret_setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_client_secret" + ).first() + + mode_setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_mode" + ).first() + + # Mask secret for security (only show last 4 characters) + def mask_key(key_value: str) -> str: + if not key_value or len(key_value) < 4: + return "" + return "*" * (len(key_value) - 4) + key_value[-4:] + + result = { + "paypal_client_id": "", + "paypal_client_secret": "", + "paypal_mode": "sandbox", + "paypal_client_secret_masked": "", + "has_client_id": False, + "has_client_secret": False, + } + + if client_id_setting: + result["paypal_client_id"] = client_id_setting.value + result["has_client_id"] = bool(client_id_setting.value) + result["updated_at"] = client_id_setting.updated_at.isoformat() if client_id_setting.updated_at else None + result["updated_by"] = client_id_setting.updated_by.full_name if client_id_setting.updated_by else None + + if client_secret_setting: + result["paypal_client_secret"] = client_secret_setting.value + result["paypal_client_secret_masked"] = mask_key(client_secret_setting.value) + result["has_client_secret"] = bool(client_secret_setting.value) + + if mode_setting: + result["paypal_mode"] = mode_setting.value or "sandbox" + + return { + "status": "success", + "data": result + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.put("/paypal") +async def update_paypal_settings( + paypal_data: dict, + current_user: User = Depends(authorize_roles("admin")), + db: Session = Depends(get_db) +): + """Update PayPal payment settings (Admin only)""" + try: + client_id = paypal_data.get("paypal_client_id", "").strip() + client_secret = paypal_data.get("paypal_client_secret", "").strip() + mode = paypal_data.get("paypal_mode", "sandbox").strip().lower() + + # Validate mode + if mode and mode not in ["sandbox", "live"]: + raise HTTPException( + status_code=400, + detail="Invalid PayPal mode. Must be 'sandbox' or 'live'" + ) + + # Update or create client ID setting + if client_id: + setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_client_id" + ).first() + + if setting: + setting.value = client_id + setting.updated_by_id = current_user.id + else: + setting = SystemSettings( + key="paypal_client_id", + value=client_id, + description="PayPal client ID for processing payments", + updated_by_id=current_user.id + ) + db.add(setting) + + # Update or create client secret setting + if client_secret: + setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_client_secret" + ).first() + + if setting: + setting.value = client_secret + setting.updated_by_id = current_user.id + else: + setting = SystemSettings( + key="paypal_client_secret", + value=client_secret, + description="PayPal client secret for processing payments", + updated_by_id=current_user.id + ) + db.add(setting) + + # Update or create mode setting + if mode: + setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_mode" + ).first() + + if setting: + setting.value = mode + setting.updated_by_id = current_user.id + else: + setting = SystemSettings( + key="paypal_mode", + value=mode, + description="PayPal mode: sandbox or live", + updated_by_id=current_user.id + ) + db.add(setting) + + db.commit() + + # Return masked values + def mask_key(key_value: str) -> str: + if not key_value or len(key_value) < 4: + return "" + return "*" * (len(key_value) - 4) + key_value[-4:] + + return { + "status": "success", + "message": "PayPal settings updated successfully", + "data": { + "paypal_client_id": client_id if client_id else "", + "paypal_client_secret": client_secret if client_secret else "", + "paypal_mode": mode, + "paypal_client_secret_masked": mask_key(client_secret) if client_secret else "", + "has_client_id": bool(client_id), + "has_client_secret": bool(client_secret), + } + } + except HTTPException: + raise + except Exception as e: + db.rollback() + raise HTTPException(status_code=500, detail=str(e)) + + @router.get("/smtp") async def get_smtp_settings( current_user: User = Depends(authorize_roles("admin")), diff --git a/Backend/src/schemas/__pycache__/auth.cpython-312.pyc b/Backend/src/schemas/__pycache__/auth.cpython-312.pyc index e7889c5a..52afc7b8 100644 Binary files a/Backend/src/schemas/__pycache__/auth.cpython-312.pyc and b/Backend/src/schemas/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/src/schemas/auth.py b/Backend/src/schemas/auth.py index 2009f0bd..6f0e28f3 100644 --- a/Backend/src/schemas/auth.py +++ b/Backend/src/schemas/auth.py @@ -31,6 +31,7 @@ class LoginRequest(BaseModel): email: EmailStr password: str rememberMe: Optional[bool] = False + mfaToken: Optional[str] = None class RefreshTokenRequest(BaseModel): @@ -85,3 +86,23 @@ class MessageResponse(BaseModel): status: str message: str + +class MFAInitResponse(BaseModel): + secret: str + qr_code: str # Base64 data URL + + +class EnableMFARequest(BaseModel): + secret: str + verification_token: str + + +class VerifyMFARequest(BaseModel): + token: str + is_backup_code: Optional[bool] = False + + +class MFAStatusResponse(BaseModel): + mfa_enabled: bool + backup_codes_count: int + diff --git a/Backend/src/services/__pycache__/auth_service.cpython-312.pyc b/Backend/src/services/__pycache__/auth_service.cpython-312.pyc index cdd0f9ed..c4839a65 100644 Binary files a/Backend/src/services/__pycache__/auth_service.cpython-312.pyc and b/Backend/src/services/__pycache__/auth_service.cpython-312.pyc differ diff --git a/Backend/src/services/__pycache__/invoice_service.cpython-312.pyc b/Backend/src/services/__pycache__/invoice_service.cpython-312.pyc index 31adb076..f61ce899 100644 Binary files a/Backend/src/services/__pycache__/invoice_service.cpython-312.pyc and b/Backend/src/services/__pycache__/invoice_service.cpython-312.pyc differ diff --git a/Backend/src/services/__pycache__/mfa_service.cpython-312.pyc b/Backend/src/services/__pycache__/mfa_service.cpython-312.pyc new file mode 100644 index 00000000..d4e19515 Binary files /dev/null and b/Backend/src/services/__pycache__/mfa_service.cpython-312.pyc differ diff --git a/Backend/src/services/__pycache__/paypal_service.cpython-312.pyc b/Backend/src/services/__pycache__/paypal_service.cpython-312.pyc new file mode 100644 index 00000000..ada04cf6 Binary files /dev/null and b/Backend/src/services/__pycache__/paypal_service.cpython-312.pyc differ diff --git a/Backend/src/services/auth_service.py b/Backend/src/services/auth_service.py index d58627c7..5fa6b266 100644 --- a/Backend/src/services/auth_service.py +++ b/Backend/src/services/auth_service.py @@ -144,8 +144,8 @@ class AuthService: "refreshToken": tokens["refreshToken"] } - async def login(self, db: Session, email: str, password: str, remember_me: bool = False) -> dict: - """Login user""" + async def login(self, db: Session, email: str, password: str, remember_me: bool = False, mfa_token: str = None) -> dict: + """Login user with optional MFA verification""" # Find user with role and password user = db.query(User).filter(User.email == email).first() if not user: @@ -158,6 +158,21 @@ class AuthService: if not self.verify_password(password, user.password): raise ValueError("Invalid email or password") + # Check if MFA is enabled + if user.mfa_enabled: + if not mfa_token: + # Return special response indicating MFA is required + return { + "requires_mfa": True, + "user_id": user.id + } + + # Verify MFA token + from ..services.mfa_service import mfa_service + is_backup_code = len(mfa_token) == 8 # Backup codes are 8 characters + if not mfa_service.verify_mfa(db, user.id, mfa_token, is_backup_code): + raise ValueError("Invalid MFA token") + # Generate tokens tokens = self.generate_tokens(user.id) diff --git a/Backend/src/services/invoice_service.py b/Backend/src/services/invoice_service.py index b2fc492c..e3137cc7 100644 --- a/Backend/src/services/invoice_service.py +++ b/Backend/src/services/invoice_service.py @@ -66,7 +66,8 @@ class InvoiceService: booking = db.query(Booking).options( selectinload(Booking.service_usages).selectinload("service"), - selectinload(Booking.room).selectinload("room_type") + selectinload(Booking.room).selectinload("room_type"), + selectinload(Booking.payments) ).filter(Booking.id == booking_id).first() if not booking: raise ValueError("Booking not found") @@ -82,6 +83,10 @@ class InvoiceService: # Initial subtotal is booking total (room + services) subtotal = float(booking.total_price) + # Calculate tax and total amounts + tax_amount = (subtotal - discount_amount) * (tax_rate / 100) + total_amount = subtotal + tax_amount - discount_amount + # Calculate amount paid from completed payments amount_paid = sum( float(p.amount) for p in booking.payments @@ -134,6 +139,7 @@ class InvoiceService: ) db.add(invoice) + db.flush() # Flush to get invoice.id before creating invoice items # Create invoice items from booking # Calculate room price (total_price includes services, so subtract services) diff --git a/Backend/src/services/mfa_service.py b/Backend/src/services/mfa_service.py new file mode 100644 index 00000000..6b252e89 --- /dev/null +++ b/Backend/src/services/mfa_service.py @@ -0,0 +1,299 @@ +""" +Multi-Factor Authentication (MFA) Service +Handles TOTP-based MFA functionality +""" +import pyotp +import qrcode +import secrets +import hashlib +import json +import base64 +import io +from typing import List, Optional, Dict, Tuple +from sqlalchemy.orm import Session +from ..models.user import User +import logging + +logger = logging.getLogger(__name__) + + +class MFAService: + """Service for managing Multi-Factor Authentication""" + + @staticmethod + def generate_secret() -> str: + """Generate a new TOTP secret""" + return pyotp.random_base32() + + @staticmethod + def generate_qr_code(secret: str, email: str, app_name: str = "Hotel Booking") -> str: + """ + Generate QR code data URL for TOTP setup + + Args: + secret: TOTP secret key + email: User's email address + app_name: Application name for the authenticator app + + Returns: + Base64 encoded QR code image data URL + """ + # Create provisioning URI for authenticator apps + totp_uri = pyotp.totp.TOTP(secret).provisioning_uri( + name=email, + issuer_name=app_name + ) + + # Generate QR code + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(totp_uri) + qr.make(fit=True) + + # Create image + img = qr.make_image(fill_color="black", back_color="white") + + # Convert to base64 data URL + buffer = io.BytesIO() + img.save(buffer, format='PNG') + img_data = base64.b64encode(buffer.getvalue()).decode() + + return f"data:image/png;base64,{img_data}" + + @staticmethod + def generate_backup_codes(count: int = 10) -> List[str]: + """ + Generate backup codes for MFA recovery + + Args: + count: Number of backup codes to generate (default: 10) + + Returns: + List of backup codes (8-character alphanumeric) + """ + codes = [] + for _ in range(count): + # Generate 8-character alphanumeric code + code = secrets.token_urlsafe(6).upper()[:8] + codes.append(code) + return codes + + @staticmethod + def hash_backup_code(code: str) -> str: + """ + Hash a backup code for storage (SHA-256) + + Args: + code: Plain backup code + + Returns: + Hashed backup code + """ + return hashlib.sha256(code.encode()).hexdigest() + + @staticmethod + def verify_backup_code(code: str, hashed_codes: List[str]) -> bool: + """ + Verify if a backup code matches any hashed code + + Args: + code: Plain backup code to verify + hashed_codes: List of hashed backup codes + + Returns: + True if code matches, False otherwise + """ + code_hash = MFAService.hash_backup_code(code) + return code_hash in hashed_codes + + @staticmethod + def verify_totp(token: str, secret: str) -> bool: + """ + Verify a TOTP token + + Args: + token: 6-digit TOTP token from authenticator app + secret: User's TOTP secret + + Returns: + True if token is valid, False otherwise + """ + try: + totp = pyotp.TOTP(secret) + # Allow tokens from current and previous/next time window for clock skew + return totp.verify(token, valid_window=1) + except Exception as e: + logger.error(f"Error verifying TOTP: {str(e)}") + return False + + @staticmethod + def enable_mfa( + db: Session, + user_id: int, + secret: str, + verification_token: str + ) -> Tuple[bool, List[str]]: + """ + Enable MFA for a user after verifying the token + + Args: + db: Database session + user_id: User ID + secret: TOTP secret + verification_token: Token from authenticator app to verify + + Returns: + Tuple of (success, backup_codes) + """ + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise ValueError("User not found") + + # Verify the token before enabling + if not MFAService.verify_totp(verification_token, secret): + raise ValueError("Invalid verification token") + + # Generate backup codes + backup_codes = MFAService.generate_backup_codes() + hashed_codes = [MFAService.hash_backup_code(code) for code in backup_codes] + + # Update user + user.mfa_enabled = True + user.mfa_secret = secret + user.mfa_backup_codes = json.dumps(hashed_codes) + + db.commit() + + # Return plain backup codes (only shown once) + return True, backup_codes + + @staticmethod + def disable_mfa(db: Session, user_id: int) -> bool: + """ + Disable MFA for a user + + Args: + db: Database session + user_id: User ID + + Returns: + True if successful + """ + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise ValueError("User not found") + + user.mfa_enabled = False + user.mfa_secret = None + user.mfa_backup_codes = None + + db.commit() + return True + + @staticmethod + def verify_mfa( + db: Session, + user_id: int, + token: str, + is_backup_code: bool = False + ) -> bool: + """ + Verify MFA token or backup code for a user + + Args: + db: Database session + user_id: User ID + token: TOTP token or backup code + is_backup_code: Whether the token is a backup code + + Returns: + True if verification successful, False otherwise + """ + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise ValueError("User not found") + + if not user.mfa_enabled or not user.mfa_secret: + raise ValueError("MFA is not enabled for this user") + + if is_backup_code: + # Verify backup code + if not user.mfa_backup_codes: + return False + + hashed_codes = json.loads(user.mfa_backup_codes) + if not MFAService.verify_backup_code(token, hashed_codes): + return False + + # Remove used backup code + code_hash = MFAService.hash_backup_code(token) + hashed_codes.remove(code_hash) + user.mfa_backup_codes = json.dumps(hashed_codes) if hashed_codes else None + db.commit() + return True + else: + # Verify TOTP token + return MFAService.verify_totp(token, user.mfa_secret) + + @staticmethod + def regenerate_backup_codes(db: Session, user_id: int) -> List[str]: + """ + Regenerate backup codes for a user + + Args: + db: Database session + user_id: User ID + + Returns: + List of new backup codes (plain, shown once) + """ + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise ValueError("User not found") + + if not user.mfa_enabled: + raise ValueError("MFA is not enabled for this user") + + # Generate new backup codes + backup_codes = MFAService.generate_backup_codes() + hashed_codes = [MFAService.hash_backup_code(code) for code in backup_codes] + + user.mfa_backup_codes = json.dumps(hashed_codes) + db.commit() + + # Return plain backup codes (only shown once) + return backup_codes + + @staticmethod + def get_mfa_status(db: Session, user_id: int) -> Dict: + """ + Get MFA status for a user + + Args: + db: Database session + user_id: User ID + + Returns: + Dictionary with MFA status information + """ + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise ValueError("User not found") + + backup_codes_count = 0 + if user.mfa_backup_codes: + backup_codes_count = len(json.loads(user.mfa_backup_codes)) + + return { + "mfa_enabled": user.mfa_enabled, + "backup_codes_count": backup_codes_count + } + + +# Create singleton instance +mfa_service = MFAService() + diff --git a/Backend/src/services/paypal_service.py b/Backend/src/services/paypal_service.py new file mode 100644 index 00000000..ba3696bc --- /dev/null +++ b/Backend/src/services/paypal_service.py @@ -0,0 +1,429 @@ +""" +PayPal payment service for processing PayPal payments +""" +from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment, LiveEnvironment +from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersGetRequest, OrdersCaptureRequest +from paypalcheckoutsdk.payments import CapturesRefundRequest +from typing import Optional, Dict, Any +from ..config.settings import settings +from ..models.payment import Payment, PaymentMethod, PaymentType, PaymentStatus +from ..models.booking import Booking, BookingStatus +from ..models.system_settings import SystemSettings +from sqlalchemy.orm import Session +from datetime import datetime +import json + + +def get_paypal_client_id(db: Session) -> Optional[str]: + """Get PayPal client ID from database or environment variable""" + try: + setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_client_id" + ).first() + if setting and setting.value: + return setting.value + except Exception: + pass + # Fallback to environment variable + return settings.PAYPAL_CLIENT_ID if settings.PAYPAL_CLIENT_ID else None + + +def get_paypal_client_secret(db: Session) -> Optional[str]: + """Get PayPal client secret from database or environment variable""" + try: + setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_client_secret" + ).first() + if setting and setting.value: + return setting.value + except Exception: + pass + # Fallback to environment variable + return settings.PAYPAL_CLIENT_SECRET if settings.PAYPAL_CLIENT_SECRET else None + + +def get_paypal_mode(db: Session) -> str: + """Get PayPal mode from database or environment variable""" + try: + setting = db.query(SystemSettings).filter( + SystemSettings.key == "paypal_mode" + ).first() + if setting and setting.value: + return setting.value + except Exception: + pass + # Fallback to environment variable + return settings.PAYPAL_MODE if settings.PAYPAL_MODE else "sandbox" + + +def get_paypal_client(db: Optional[Session] = None) -> PayPalHttpClient: + """ + Get PayPal HTTP client + + Args: + db: Optional database session to get credentials from database + + Returns: + PayPalHttpClient instance + """ + client_id = None + client_secret = None + mode = "sandbox" + + if db: + client_id = get_paypal_client_id(db) + client_secret = get_paypal_client_secret(db) + mode = get_paypal_mode(db) + + if not client_id: + client_id = settings.PAYPAL_CLIENT_ID + if not client_secret: + client_secret = settings.PAYPAL_CLIENT_SECRET + if not mode: + mode = settings.PAYPAL_MODE or "sandbox" + + if not client_id or not client_secret: + raise ValueError("PayPal credentials are not configured") + + # Create environment based on mode + if mode.lower() == "live": + environment = LiveEnvironment(client_id=client_id, client_secret=client_secret) + else: + environment = SandboxEnvironment(client_id=client_id, client_secret=client_secret) + + return PayPalHttpClient(environment) + + +class PayPalService: + """Service for handling PayPal payments""" + + @staticmethod + def create_order( + amount: float, + currency: str = "USD", + metadata: Optional[Dict[str, Any]] = None, + db: Optional[Session] = None + ) -> Dict[str, Any]: + """ + Create a PayPal order + + Args: + amount: Payment amount in currency units + currency: Currency code (default: USD) + metadata: Additional metadata to attach to the order + db: Optional database session to get credentials from database + + Returns: + Order object with approval URL and order ID + """ + client = get_paypal_client(db) + + # Validate amount + if amount <= 0: + raise ValueError("Amount must be greater than 0") + if amount > 100000: + raise ValueError(f"Amount ${amount:,.2f} exceeds PayPal's maximum of $100,000") + + # Create order request + request = OrdersCreateRequest() + request.prefer("return=representation") + + # Build order body + order_data = { + "intent": "CAPTURE", + "purchase_units": [ + { + "amount": { + "currency_code": currency.upper(), + "value": f"{amount:.2f}" + }, + "description": metadata.get("description", "Hotel Booking Payment") if metadata else "Hotel Booking Payment", + "custom_id": metadata.get("booking_id") if metadata else None, + } + ], + "application_context": { + "brand_name": "Hotel Booking", + "landing_page": "BILLING", + "user_action": "PAY_NOW", + "return_url": metadata.get("return_url") if metadata else None, + "cancel_url": metadata.get("cancel_url") if metadata else None, + } + } + + # Add metadata if provided + if metadata: + order_data["purchase_units"][0]["invoice_id"] = metadata.get("booking_number") + + request.request_body(order_data) + + try: + response = client.execute(request) + order = response.result + + # Extract approval URL + approval_url = None + for link in order.links: + if link.rel == "approve": + approval_url = link.href + break + + return { + "id": order.id, + "status": order.status, + "approval_url": approval_url, + "amount": amount, + "currency": currency.upper(), + } + except Exception as e: + error_msg = str(e) + # Try to extract more details from PayPal error + if hasattr(e, 'message'): + error_msg = e.message + elif hasattr(e, 'details') and e.details: + error_msg = json.dumps(e.details) + raise ValueError(f"PayPal error: {error_msg}") + + @staticmethod + def get_order( + order_id: str, + db: Optional[Session] = None + ) -> Dict[str, Any]: + """ + Retrieve an order by ID + + Args: + order_id: PayPal order ID + db: Optional database session to get credentials from database + + Returns: + Order object + """ + client = get_paypal_client(db) + + request = OrdersGetRequest(order_id) + + try: + response = client.execute(request) + order = response.result + + # Extract amount from purchase units + amount = 0.0 + currency = "USD" + if order.purchase_units and len(order.purchase_units) > 0: + amount_str = order.purchase_units[0].amount.value + currency = order.purchase_units[0].amount.currency_code + amount = float(amount_str) + + return { + "id": order.id, + "status": order.status, + "amount": amount, + "currency": currency, + "create_time": order.create_time, + "update_time": order.update_time, + } + except Exception as e: + error_msg = str(e) + if hasattr(e, 'message'): + error_msg = e.message + raise ValueError(f"PayPal error: {error_msg}") + + @staticmethod + def capture_order( + order_id: str, + db: Optional[Session] = None + ) -> Dict[str, Any]: + """ + Capture a PayPal order + + Args: + order_id: PayPal order ID + db: Optional database session to get credentials from database + + Returns: + Capture details + """ + client = get_paypal_client(db) + + request = OrdersCaptureRequest(order_id) + request.prefer("return=representation") + + try: + response = client.execute(request) + order = response.result + + # Extract capture details + capture_id = None + amount = 0.0 + currency = "USD" + status = order.status + + if order.purchase_units and len(order.purchase_units) > 0: + payments = order.purchase_units[0].payments + if payments and payments.captures and len(payments.captures) > 0: + capture = payments.captures[0] + capture_id = capture.id + amount_str = capture.amount.value + currency = capture.amount.currency_code + amount = float(amount_str) + status = capture.status + + return { + "order_id": order.id, + "capture_id": capture_id, + "status": status, + "amount": amount, + "currency": currency, + } + except Exception as e: + error_msg = str(e) + if hasattr(e, 'message'): + error_msg = e.message + raise ValueError(f"PayPal error: {error_msg}") + + @staticmethod + def confirm_payment( + order_id: str, + db: Session, + booking_id: Optional[int] = None + ) -> Dict[str, Any]: + """ + Confirm a payment and update database records + + Args: + order_id: PayPal order ID + db: Database session + booking_id: Optional booking ID for metadata lookup + + Returns: + Payment record dictionary + """ + try: + # First capture the order + capture_data = PayPalService.capture_order(order_id, db) + + # Get order details to extract booking_id from metadata if not provided + if not booking_id: + order_data = PayPalService.get_order(order_id, db) + # Try to get booking_id from custom_id in purchase_units + # Note: We'll need to store booking_id in the order metadata when creating + + # For now, we'll require booking_id to be passed + if not booking_id: + raise ValueError("Booking ID is required") + + booking = db.query(Booking).filter(Booking.id == booking_id).first() + if not booking: + raise ValueError("Booking not found") + + # Check capture status + capture_status = capture_data.get("status") + if capture_status not in ["COMPLETED", "PENDING"]: + raise ValueError(f"Payment capture not in a valid state. Status: {capture_status}") + + # Find existing payment or create new one + # First try to find by transaction_id (for already captured payments) + payment = db.query(Payment).filter( + Payment.booking_id == booking_id, + Payment.transaction_id == order_id, + Payment.payment_method == PaymentMethod.paypal + ).first() + + # If not found, try to find pending PayPal payment for this booking + if not payment: + payment = db.query(Payment).filter( + Payment.booking_id == booking_id, + Payment.payment_method == PaymentMethod.paypal, + Payment.payment_status == PaymentStatus.pending + ).order_by(Payment.created_at.desc()).first() + + amount = capture_data["amount"] + capture_id = capture_data.get("capture_id") + + if payment: + # Update existing payment + if capture_status == "COMPLETED": + payment.payment_status = PaymentStatus.completed + payment.payment_date = datetime.utcnow() + # If pending, keep as pending + payment.amount = amount + if capture_id: + payment.transaction_id = f"{order_id}|{capture_id}" + else: + # Create new payment record + payment_type = PaymentType.full + if booking.requires_deposit and not booking.deposit_paid: + payment_type = PaymentType.deposit + + payment_status_enum = PaymentStatus.completed if capture_status == "COMPLETED" else PaymentStatus.pending + payment_date = datetime.utcnow() if capture_status == "COMPLETED" else None + + transaction_id = f"{order_id}|{capture_id}" if capture_id else order_id + + payment = Payment( + booking_id=booking_id, + amount=amount, + payment_method=PaymentMethod.paypal, + payment_type=payment_type, + payment_status=payment_status_enum, + transaction_id=transaction_id, + payment_date=payment_date, + notes=f"PayPal payment - Order: {order_id}, Capture: {capture_id} (Status: {capture_status})", + ) + db.add(payment) + + # Commit payment first + db.commit() + db.refresh(payment) + + # Update booking status only if payment is completed + if payment.payment_status == PaymentStatus.completed: + db.refresh(booking) + + if payment.payment_type == PaymentType.deposit: + booking.deposit_paid = True + if booking.status == BookingStatus.pending: + booking.status = BookingStatus.confirmed + elif payment.payment_type == PaymentType.full: + total_paid = sum( + float(p.amount) for p in booking.payments + if p.payment_status == PaymentStatus.completed + ) + + if total_paid >= float(booking.total_price) or float(payment.amount) >= float(booking.total_price): + booking.status = BookingStatus.confirmed + + db.commit() + db.refresh(booking) + + # Safely get enum values + def get_enum_value(enum_obj): + if enum_obj is None: + return None + if isinstance(enum_obj, (PaymentMethod, PaymentType, PaymentStatus)): + return enum_obj.value + return enum_obj + + return { + "id": payment.id, + "booking_id": payment.booking_id, + "amount": float(payment.amount) if payment.amount else 0.0, + "payment_method": get_enum_value(payment.payment_method), + "payment_type": get_enum_value(payment.payment_type), + "payment_status": get_enum_value(payment.payment_status), + "transaction_id": payment.transaction_id, + "payment_date": payment.payment_date.isoformat() if payment.payment_date else None, + } + + except ValueError as e: + db.rollback() + raise + except Exception as e: + import traceback + error_details = traceback.format_exc() + error_msg = str(e) if str(e) else f"{type(e).__name__}: {repr(e)}" + print(f"Error in confirm_payment: {error_msg}") + print(f"Traceback: {error_details}") + db.rollback() + raise ValueError(f"Error confirming payment: {error_msg}") + diff --git a/Backend/src/utils/__pycache__/email_templates.cpython-312.pyc b/Backend/src/utils/__pycache__/email_templates.cpython-312.pyc index 6414cf35..b9f275d2 100644 Binary files a/Backend/src/utils/__pycache__/email_templates.cpython-312.pyc and b/Backend/src/utils/__pycache__/email_templates.cpython-312.pyc differ diff --git a/Backend/venv/bin/prichunkpng b/Backend/venv/bin/prichunkpng new file mode 100755 index 00000000..16fdc3c7 --- /dev/null +++ b/Backend/venv/bin/prichunkpng @@ -0,0 +1,266 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 +# prichunkpng +# Chunk editing tool. + +""" +Make a new PNG by adding, delete, or replacing particular chunks. +""" + +import argparse +import collections + +# https://docs.python.org/2.7/library/io.html +import io +import re +import string +import struct +import sys +import zlib + +# Local module. +import png + + +Chunk = collections.namedtuple("Chunk", "type content") + + +class ArgumentError(Exception): + """A user problem with the command arguments.""" + + +def process(out, args): + """Process the PNG file args.input to the output, chunk by chunk. + Chunks can be inserted, removed, replaced, or sometimes edited. + Chunks are specified by their 4 byte Chunk Type; + see https://www.w3.org/TR/2003/REC-PNG-20031110/#5Chunk-layout . + The chunks in args.delete will be removed from the stream. + The chunks in args.chunk will be inserted into the stream + with their contents taken from the named files. + + Other options on the args object will create particular + ancillary chunks. + + .gamma -> gAMA chunk + .sigbit -> sBIT chunk + + Chunk types need not be official PNG chunks at all. + Non-standard chunks can be created. + """ + + # Convert options to chunks in the args.chunk list + if args.gamma: + v = int(round(1e5 * args.gamma)) + bs = io.BytesIO(struct.pack(">I", v)) + args.chunk.insert(0, Chunk(b"gAMA", bs)) + if args.sigbit: + v = struct.pack("%dB" % len(args.sigbit), *args.sigbit) + bs = io.BytesIO(v) + args.chunk.insert(0, Chunk(b"sBIT", bs)) + if args.iccprofile: + # http://www.w3.org/TR/PNG/#11iCCP + v = b"a color profile\x00\x00" + zlib.compress(args.iccprofile.read()) + bs = io.BytesIO(v) + args.chunk.insert(0, Chunk(b"iCCP", bs)) + if args.transparent: + # https://www.w3.org/TR/2003/REC-PNG-20031110/#11tRNS + v = struct.pack(">%dH" % len(args.transparent), *args.transparent) + bs = io.BytesIO(v) + args.chunk.insert(0, Chunk(b"tRNS", bs)) + if args.background: + # https://www.w3.org/TR/2003/REC-PNG-20031110/#11bKGD + v = struct.pack(">%dH" % len(args.background), *args.background) + bs = io.BytesIO(v) + args.chunk.insert(0, Chunk(b"bKGD", bs)) + if args.physical: + # https://www.w3.org/TR/PNG/#11pHYs + numbers = re.findall(r"(\d+\.?\d*)", args.physical) + if len(numbers) not in {1, 2}: + raise ArgumentError("One or two numbers are required for --physical") + xppu = float(numbers[0]) + if len(numbers) == 1: + yppu = xppu + else: + yppu = float(numbers[1]) + + unit_spec = 0 + if args.physical.endswith("dpi"): + # Convert from DPI to Pixels Per Metre + # 1 inch is 0.0254 metres + l = 0.0254 + xppu /= l + yppu /= l + unit_spec = 1 + elif args.physical.endswith("ppm"): + unit_spec = 1 + + v = struct.pack("!LLB", round(xppu), round(yppu), unit_spec) + bs = io.BytesIO(v) + args.chunk.insert(0, Chunk(b"pHYs", bs)) + + # Create: + # - a set of chunks to delete + # - a dict of chunks to replace + # - a list of chunk to add + + delete = set(args.delete) + # The set of chunks to replace are those where the specification says + # that there should be at most one of them. + replacing = set([b"gAMA", b"pHYs", b"sBIT", b"PLTE", b"tRNS", b"sPLT", b"IHDR"]) + replace = dict() + add = [] + + for chunk in args.chunk: + if chunk.type in replacing: + replace[chunk.type] = chunk + else: + add.append(chunk) + + input = png.Reader(file=args.input) + + return png.write_chunks(out, edit_chunks(input.chunks(), delete, replace, add)) + + +def edit_chunks(chunks, delete, replace, add): + """ + Iterate over chunks, yielding edited chunks. + Subtle: the new chunks have to have their contents .read(). + """ + for type, v in chunks: + if type in delete: + continue + if type in replace: + yield type, replace[type].content.read() + del replace[type] + continue + + if b"IDAT" <= type <= b"IDAT" and replace: + # If there are any chunks on the replace list by + # the time we reach IDAT, add then all now. + # put them all on the add list. + for chunk in replace.values(): + yield chunk.type, chunk.content.read() + replace = dict() + + if b"IDAT" <= type <= b"IDAT" and add: + # We reached IDAT; add all remaining chunks now. + for chunk in add: + yield chunk.type, chunk.content.read() + add = [] + + yield type, v + + +def chunk_name(s): + """ + Type check a chunk name option value. + """ + + # See https://www.w3.org/TR/2003/REC-PNG-20031110/#table51 + valid = len(s) == 4 and set(s) <= set(string.ascii_letters) + if not valid: + raise ValueError("Chunk name must be 4 ASCII letters") + return s.encode("ascii") + + +def comma_list(s): + """ + Convert s, a command separated list of whole numbers, + into a sequence of int. + """ + + return tuple(int(v) for v in s.split(",")) + + +def hex_color(s): + """ + Type check and convert a hex color. + """ + + if s.startswith("#"): + s = s[1:] + valid = len(s) in [1, 2, 3, 4, 6, 12] and set(s) <= set(string.hexdigits) + if not valid: + raise ValueError("colour must be 1,2,3,4,6, or 12 hex-digits") + + # For the 4-bit RGB, expand to 8-bit, by repeating digits. + if len(s) == 3: + s = "".join(c + c for c in s) + + if len(s) in [1, 2, 4]: + # Single grey value. + return (int(s, 16),) + + if len(s) in [6, 12]: + w = len(s) // 3 + return tuple(int(s[i : i + w], 16) for i in range(0, len(s), w)) + + +def main(argv=None): + if argv is None: + argv = sys.argv + + argv = argv[1:] + + parser = argparse.ArgumentParser() + parser.add_argument("--gamma", type=float, help="Gamma value for gAMA chunk") + parser.add_argument( + "--physical", + type=str, + metavar="x[,y][dpi|ppm]", + help="specify intended pixel size or aspect ratio", + ) + parser.add_argument( + "--sigbit", + type=comma_list, + metavar="D[,D[,D[,D]]]", + help="Number of significant bits in each channel", + ) + parser.add_argument( + "--iccprofile", + metavar="file.iccp", + type=argparse.FileType("rb"), + help="add an ICC Profile from a file", + ) + parser.add_argument( + "--transparent", + type=hex_color, + metavar="#RRGGBB", + help="Specify the colour that is transparent (tRNS chunk)", + ) + parser.add_argument( + "--background", + type=hex_color, + metavar="#RRGGBB", + help="background colour for bKGD chunk", + ) + parser.add_argument( + "--delete", + action="append", + default=[], + type=chunk_name, + help="delete the chunk", + ) + parser.add_argument( + "--chunk", + action="append", + nargs=2, + default=[], + type=str, + help="insert chunk, taking contents from file", + ) + parser.add_argument( + "input", nargs="?", default="-", type=png.cli_open, metavar="PNG" + ) + + args = parser.parse_args(argv) + + # Reprocess the chunk arguments, converting each pair into a Chunk. + args.chunk = [ + Chunk(chunk_name(type), open(path, "rb")) for type, path in args.chunk + ] + + return process(png.binary_stdout(), args) + + +if __name__ == "__main__": + main() diff --git a/Backend/venv/bin/pricolpng b/Backend/venv/bin/pricolpng new file mode 100755 index 00000000..f79cbc29 --- /dev/null +++ b/Backend/venv/bin/pricolpng @@ -0,0 +1,81 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# http://www.python.org/doc/2.4.4/lib/module-itertools.html +import itertools +import sys + +import png + +Description = """Join PNG images in a column top-to-bottom.""" + + +class FormatError(Exception): + """ + Some problem with the image format. + """ + + +def join_col(out, l): + """ + Join the list of images. + All input images must be same width and + have the same number of channels. + They are joined top-to-bottom. + `out` is the (open file) destination for the output image. + `l` should be a list of open files (the input image files). + """ + + image = 0 + stream = 0 + + # When the first image is read, this will be the reference width, + # which must be the same for all images. + width = None + # Total height (accumulated as images are read). + height = 0 + # Accumulated rows. + rows = [] + + for f in l: + stream += 1 + while True: + im = png.Reader(file=f) + try: + im.preamble() + except EOFError: + break + image += 1 + + if not width: + width = im.width + elif width != im.width: + raise FormatError('Image %d in stream %d has width %d; does not match %d.' % + (image, stream, im.width, width)) + + height += im.height + # Various bugs here because different numbers of channels and depths go wrong. + w, h, p, info = im.asDirect() + rows.extend(p) + + # Alarmingly re-use the last info object. + tinfo = dict(info) + del tinfo['size'] + w = png.Writer(width, height, **tinfo) + + w.write(out, rows) + + +def main(argv): + import argparse + + parser = argparse.ArgumentParser(description=Description) + parser.add_argument( + "input", nargs="*", default="-", type=png.cli_open, metavar="PNG" + ) + + args = parser.parse_args() + + return join_col(png.binary_stdout(), args.input) + +if __name__ == '__main__': + main(sys.argv) diff --git a/Backend/venv/bin/priditherpng b/Backend/venv/bin/priditherpng new file mode 100755 index 00000000..e3e5cb57 --- /dev/null +++ b/Backend/venv/bin/priditherpng @@ -0,0 +1,254 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# pipdither +# Error Diffusing image dithering. +# Now with serpentine scanning. + +# See http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT + +# http://www.python.org/doc/2.4.4/lib/module-bisect.html +from bisect import bisect_left + + +import png + + +def dither( + out, + input, + bitdepth=1, + linear=False, + defaultgamma=1.0, + targetgamma=None, + cutoff=0.5, # see :cutoff:default +): + """Dither the input PNG `inp` into an image with a smaller bit depth + and write the result image onto `out`. `bitdepth` specifies the bit + depth of the new image. + + Normally the source image gamma is honoured (the image is + converted into a linear light space before being dithered), but + if the `linear` argument is true then the image is treated as + being linear already: no gamma conversion is done (this is + quicker, and if you don't care much about accuracy, it won't + matter much). + + Images with no gamma indication (no ``gAMA`` chunk) are normally + treated as linear (gamma = 1.0), but often it can be better + to assume a different gamma value: For example continuous tone + photographs intended for presentation on the web often carry + an implicit assumption of being encoded with a gamma of about + 0.45 (because that's what you get if you just "blat the pixels" + onto a PC framebuffer), so ``defaultgamma=0.45`` might be a + good idea. `defaultgamma` does not override a gamma value + specified in the file itself: It is only used when the file + does not specify a gamma. + + If you (pointlessly) specify both `linear` and `defaultgamma`, + `linear` wins. + + The gamma of the output image is, by default, the same as the input + image. The `targetgamma` argument can be used to specify a + different gamma for the output image. This effectively recodes the + image to a different gamma, dithering as we go. The gamma specified + is the exponent used to encode the output file (and appears in the + output PNG's ``gAMA`` chunk); it is usually less than 1. + + """ + + # Encoding is what happened when the PNG was made (and also what + # happens when we output the PNG). Decoding is what we do to the + # source PNG in order to process it. + + # The dithering algorithm is not completely general; it + # can only do bit depth reduction, not arbitrary palette changes. + import operator + + maxval = 2 ** bitdepth - 1 + r = png.Reader(file=input) + + _, _, pixels, info = r.asDirect() + planes = info["planes"] + # :todo: make an Exception + assert planes == 1 + width = info["size"][0] + sourcemaxval = 2 ** info["bitdepth"] - 1 + + if linear: + gamma = 1 + else: + gamma = info.get("gamma") or defaultgamma + + # Calculate an effective gamma for input and output; + # then build tables using those. + + # `gamma` (whether it was obtained from the input file or an + # assumed value) is the encoding gamma. + # We need the decoding gamma, which is the reciprocal. + decode = 1.0 / gamma + + # `targetdecode` is the assumed gamma that is going to be used + # to decoding the target PNG. + # Note that even though we will _encode_ the target PNG we + # still need the decoding gamma, because + # the table we use maps from PNG pixel value to linear light level. + if targetgamma is None: + targetdecode = decode + else: + targetdecode = 1.0 / targetgamma + + incode = build_decode_table(sourcemaxval, decode) + + # For encoding, we still build a decode table, because we + # use it inverted (searching with bisect). + outcode = build_decode_table(maxval, targetdecode) + + # The table used for choosing output codes. These values represent + # the cutoff points between two adjacent output codes. + # The cutoff parameter can be varied between 0 and 1 to + # preferentially choose lighter (when cutoff > 0.5) or + # darker (when cutoff < 0.5) values. + # :cutoff:default: The default for this used to be 0.75, but + # testing by drj on 2021-07-30 showed that this produces + # banding when dithering left-to-right gradients; + # test with: + # priforgepng grl | priditherpng | kitty icat + choosecode = list(zip(outcode[1:], outcode)) + p = cutoff + choosecode = [x[0] * p + x[1] * (1.0 - p) for x in choosecode] + + rows = repeat_header(pixels) + dithered_rows = run_dither(incode, choosecode, outcode, width, rows) + dithered_rows = remove_header(dithered_rows) + + info["bitdepth"] = bitdepth + info["gamma"] = 1.0 / targetdecode + w = png.Writer(**info) + w.write(out, dithered_rows) + + +def build_decode_table(maxval, gamma): + """Build a lookup table for decoding; + table converts from pixel values to linear space. + """ + + assert maxval == int(maxval) + assert maxval > 0 + + f = 1.0 / maxval + table = [f * v for v in range(maxval + 1)] + if gamma != 1.0: + table = [v ** gamma for v in table] + return table + + +def run_dither(incode, choosecode, outcode, width, rows): + """ + Run an serpentine dither. + Using the incode and choosecode tables. + """ + + # Errors diffused downwards (into next row) + ed = [0.0] * width + flipped = False + for row in rows: + # Convert to linear... + row = [incode[v] for v in row] + # Add errors... + row = [e + v for e, v in zip(ed, row)] + + if flipped: + row = row[::-1] + targetrow = [0] * width + + for i, v in enumerate(row): + # `it` will be the index of the chosen target colour; + it = bisect_left(choosecode, v) + targetrow[i] = it + t = outcode[it] + # err is the error that needs distributing. + err = v - t + + # Sierra "Filter Lite" distributes * 2 + # as per this diagram. 1 1 + ef = err * 0.5 + # :todo: consider making rows one wider at each end and + # removing "if"s + if i + 1 < width: + row[i + 1] += ef + ef *= 0.5 + ed[i] = ef + if i: + ed[i - 1] += ef + + if flipped: + ed = ed[::-1] + targetrow = targetrow[::-1] + yield targetrow + flipped = not flipped + + +WARMUP_ROWS = 32 + + +def repeat_header(rows): + """Repeat the first row, to "warm up" the error register.""" + for row in rows: + yield row + for _ in range(WARMUP_ROWS): + yield row + break + yield from rows + + +def remove_header(rows): + """Remove the same number of rows that repeat_header added.""" + + for _ in range(WARMUP_ROWS): + next(rows) + yield from rows + + +def main(argv=None): + import sys + + # https://docs.python.org/3.5/library/argparse.html + import argparse + + parser = argparse.ArgumentParser() + + if argv is None: + argv = sys.argv + + progname, *args = argv + + parser.add_argument("--bitdepth", type=int, default=1, help="bitdepth of output") + parser.add_argument( + "--cutoff", + type=float, + default=0.5, + help="cutoff to select adjacent output values", + ) + parser.add_argument( + "--defaultgamma", + type=float, + default=1.0, + help="gamma value to use when no gamma in input", + ) + parser.add_argument("--linear", action="store_true", help="force linear input") + parser.add_argument( + "--targetgamma", + type=float, + help="gamma to use in output (target), defaults to input gamma", + ) + parser.add_argument( + "input", nargs="?", default="-", type=png.cli_open, metavar="PNG" + ) + + ns = parser.parse_args(args) + + return dither(png.binary_stdout(), **vars(ns)) + + +if __name__ == "__main__": + main() diff --git a/Backend/venv/bin/priforgepng b/Backend/venv/bin/priforgepng new file mode 100755 index 00000000..519558ec --- /dev/null +++ b/Backend/venv/bin/priforgepng @@ -0,0 +1,275 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 +# priforgepng + +"""Forge PNG image from raw computation.""" + +from array import array +from fractions import Fraction + +import argparse +import re +import sys + +import png + + +def gen_glr(x): + """Gradient Left to Right""" + return x + + +def gen_grl(x): + """Gradient Right to Left""" + return 1 - x + + +def gen_gtb(x, y): + """Gradient Top to Bottom""" + return y + + +def gen_gbt(x, y): + """Gradient Bottom to Top""" + return 1.0 - y + + +def gen_rtl(x, y): + """Radial gradient, centred at Top-Left""" + return max(1 - (float(x) ** 2 + float(y) ** 2) ** 0.5, 0.0) + + +def gen_rctr(x, y): + """Radial gradient, centred at Centre""" + return gen_rtl(float(x) - 0.5, float(y) - 0.5) + + +def gen_rtr(x, y): + """Radial gradient, centred at Top-Right""" + return gen_rtl(1.0 - float(x), y) + + +def gen_rbl(x, y): + """Radial gradient, centred at Bottom-Left""" + return gen_rtl(x, 1.0 - float(y)) + + +def gen_rbr(x, y): + """Radial gradient, centred at Bottom-Right""" + return gen_rtl(1.0 - float(x), 1.0 - float(y)) + + +def stripe(x, n): + return int(x * n) & 1 + + +def gen_vs2(x): + """2 Vertical Stripes""" + return stripe(x, 2) + + +def gen_vs4(x): + """4 Vertical Stripes""" + return stripe(x, 4) + + +def gen_vs10(x): + """10 Vertical Stripes""" + return stripe(x, 10) + + +def gen_hs2(x, y): + """2 Horizontal Stripes""" + return stripe(float(y), 2) + + +def gen_hs4(x, y): + """4 Horizontal Stripes""" + return stripe(float(y), 4) + + +def gen_hs10(x, y): + """10 Horizontal Stripes""" + return stripe(float(y), 10) + + +def gen_slr(x, y): + """10 diagonal stripes, rising from Left to Right""" + return stripe(x + y, 10) + + +def gen_srl(x, y): + """10 diagonal stripes, rising from Right to Left""" + return stripe(1 + x - y, 10) + + +def checker(x, y, n): + return stripe(x, n) ^ stripe(y, n) + + +def gen_ck8(x, y): + """8 by 8 checkerboard""" + return checker(x, y, 8) + + +def gen_ck15(x, y): + """15 by 15 checkerboard""" + return checker(x, y, 15) + + +def gen_zero(x): + """All zero (black)""" + return 0 + + +def gen_one(x): + """All one (white)""" + return 1 + + +def yield_fun_rows(size, bitdepth, pattern): + """ + Create a single channel (monochrome) test pattern. + Yield each row in turn. + """ + + width, height = size + + maxval = 2 ** bitdepth - 1 + if maxval > 255: + typecode = "H" + else: + typecode = "B" + pfun = pattern_function(pattern) + + # The coordinates are an integer + 0.5, + # effectively sampling each pixel at its centre. + # This is morally better, and produces all 256 sample values + # in a 256-pixel wide gradient. + + # We make a list of x coordinates here and re-use it, + # because Fraction instances are slow to allocate. + xs = [Fraction(x, 2 * width) for x in range(1, 2 * width, 2)] + + # The general case is a function in x and y, + # but if the function only takes an x argument, + # it's handled in a special case that is a lot faster. + if n_args(pfun) == 2: + for y in range(height): + a = array(typecode) + fy = Fraction(Fraction(y + 0.5), height) + for fx in xs: + a.append(int(round(maxval * pfun(fx, fy)))) + yield a + return + + # For functions in x only, it's a _lot_ faster + # to generate a single row and repeatedly yield it + a = array(typecode) + for fx in xs: + a.append(int(round(maxval * pfun(x=fx)))) + for y in range(height): + yield a + return + + +def generate(args): + """ + Create a PNG test image and write the file to stdout. + + `args` should be an argparse Namespace instance or similar. + """ + + size = args.size + bitdepth = args.depth + + out = png.binary_stdout() + + for pattern in args.pattern: + rows = yield_fun_rows(size, bitdepth, pattern) + writer = png.Writer( + size[0], size[1], bitdepth=bitdepth, greyscale=True, alpha=False + ) + writer.write(out, rows) + + +def n_args(fun): + """Number of arguments in fun's argument list.""" + return fun.__code__.co_argcount + + +def pattern_function(pattern): + """From `pattern`, a string, + return the function for that pattern. + """ + + lpat = pattern.lower() + for name, fun in globals().items(): + parts = name.split("_") + if parts[0] != "gen": + continue + if parts[1] == lpat: + return fun + + +def patterns(): + """ + List the patterns. + """ + + for name, fun in globals().items(): + parts = name.split("_") + if parts[0] == "gen": + yield parts[1], fun.__doc__ + + +def dimensions(s): + """ + Typecheck the --size option, which should be + one or two comma separated numbers. + Example: "64,40". + """ + + tupl = re.findall(r"\d+", s) + if len(tupl) not in (1, 2): + raise ValueError("%r should be width or width,height" % s) + if len(tupl) == 1: + tupl *= 2 + assert len(tupl) == 2 + return list(map(int, tupl)) + + +def main(argv=None): + if argv is None: + argv = sys.argv + parser = argparse.ArgumentParser(description="Forge greyscale PNG patterns") + + parser.add_argument( + "-l", "--list", action="store_true", help="print list of patterns and exit" + ) + parser.add_argument( + "-d", "--depth", default=8, type=int, metavar="N", help="N bits per pixel" + ) + parser.add_argument( + "-s", + "--size", + default=[256, 256], + type=dimensions, + metavar="w[,h]", + help="width and height of the image in pixels", + ) + parser.add_argument("pattern", nargs="*", help="name of pattern") + + args = parser.parse_args(argv[1:]) + + if args.list: + for name, doc in sorted(patterns()): + print(name, doc, sep="\t") + return + + if not args.pattern: + parser.error("--list or pattern is required") + return generate(args) + + +if __name__ == "__main__": + main() diff --git a/Backend/venv/bin/prigreypng b/Backend/venv/bin/prigreypng new file mode 100755 index 00000000..0cb6cfc4 --- /dev/null +++ b/Backend/venv/bin/prigreypng @@ -0,0 +1,72 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# prigreypng + +# Convert image to grey (L, or LA), but only if that involves no colour change. + +import argparse +import array + + +import png + + +def as_grey(out, inp): + """ + Convert image to greyscale, but only when no colour change. + This works by using the input G channel (green) as + the output L channel (luminance) and + checking that every pixel is grey as we go. + A non-grey pixel will raise an error. + """ + + r = png.Reader(file=inp) + _, _, rows, info = r.asDirect() + if info["greyscale"]: + w = png.Writer(**info) + return w.write(out, rows) + + planes = info["planes"] + targetplanes = planes - 2 + alpha = info["alpha"] + width, height = info["size"] + typecode = "BH"[info["bitdepth"] > 8] + + # Values per target row + vpr = width * targetplanes + + def iterasgrey(): + for i, row in enumerate(rows): + row = array.array(typecode, row) + targetrow = array.array(typecode, [0] * vpr) + # Copy G (and possibly A) channel. + green = row[0::planes] + if alpha: + targetrow[0::2] = green + targetrow[1::2] = row[3::4] + else: + targetrow = green + # Check R and B channel match. + if green != row[0::planes] or green != row[2::planes]: + raise ValueError("Row %i contains non-grey pixel." % i) + yield targetrow + + info["greyscale"] = True + del info["planes"] + w = png.Writer(**info) + return w.write(out, iterasgrey()) + + +def main(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument( + "input", nargs="?", default="-", type=png.cli_open, metavar="PNG" + ) + args = parser.parse_args() + return as_grey(png.binary_stdout(), args.input) + + +if __name__ == "__main__": + import sys + + sys.exit(main()) diff --git a/Backend/venv/bin/pripalpng b/Backend/venv/bin/pripalpng new file mode 100755 index 00000000..362eb584 --- /dev/null +++ b/Backend/venv/bin/pripalpng @@ -0,0 +1,111 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 +# pripalpng + + +"""Convert to Palette PNG (without changing colours)""" + +import argparse +import collections + +# https://docs.python.org/2.7/library/io.html +import io +import string +import zlib + +# Local module. +import png + + +def make_inverse_palette(rows, channels): + """ + The inverse palette maps from tuple to palette index. + """ + + palette = {} + + for row in rows: + for pixel in png.group(row, channels): + if pixel in palette: + continue + palette[pixel] = len(palette) + return palette + + +def palette_convert(out, inp, palette_file): + """ + Convert PNG image in `inp` to use a palette, colour type 3, + and write converted image to `out`. + + `palette_file` is a file descriptor for the palette to use. + + If `palette_file` is None, then `inp` is used as the palette. + """ + + if palette_file is None: + inp, palette_file = palette_file, inp + + reader = png.Reader(file=palette_file) + w, h, rows, info = asRGBorA8(reader) + channels = info["planes"] + if not inp: + rows = list(rows) + + palette_map = make_inverse_palette(rows, channels) + + if inp: + reader = png.Reader(file=inp) + w, h, rows, info = asRGBorA8(reader) + channels = info["planes"] + + # Default for colours not in palette is to use last entry. + last = len(palette_map) - 1 + + def map_pixel(p): + return palette_map.get(p, last) + + def convert_rows(): + for row in rows: + yield [map_pixel(p) for p in png.group(row, channels)] + + # Make a palette by sorting the pixels according to their index. + palette = sorted(palette_map.keys(), key=palette_map.get) + pal_info = dict(size=info["size"], palette=palette) + + w = png.Writer(**pal_info) + w.write(out, convert_rows()) + + +def asRGBorA8(reader): + """ + Return (width, height, rows, info) converting to RGB, + or RGBA if original has an alpha channel. + """ + _, _, _, info = reader.read() + if info["alpha"]: + return reader.asRGBA8() + else: + return reader.asRGB8() + + +def main(argv=None): + import sys + import re + + if argv is None: + argv = sys.argv + + argv = argv[1:] + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--palette", type=png.cli_open) + parser.add_argument( + "input", nargs="?", default="-", type=png.cli_open, metavar="PNG" + ) + + args = parser.parse_args(argv) + + palette_convert(png.binary_stdout(), args.input, args.palette) + + +if __name__ == "__main__": + main() diff --git a/Backend/venv/bin/pripamtopng b/Backend/venv/bin/pripamtopng new file mode 100755 index 00000000..a068adab --- /dev/null +++ b/Backend/venv/bin/pripamtopng @@ -0,0 +1,355 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# pripamtopng +# +# Python Raster Image PAM to PNG + +import array +import struct +import sys + +import png + +Description = """Convert NetPBM PAM/PNM format files to PNG.""" + + +def read_pam_header(infile): + """ + Read (the rest of a) PAM header. + `infile` should be positioned immediately after the initial 'P7' line + (at the beginning of the second line). + Returns are as for `read_pnm_header`. + """ + + # Unlike PBM, PGM, and PPM, we can read the header a line at a time. + header = dict() + while True: + line = infile.readline().strip() + if line == b"ENDHDR": + break + if not line: + raise EOFError("PAM ended prematurely") + if line[0] == b"#": + continue + line = line.split(None, 1) + key = line[0] + if key not in header: + header[key] = line[1] + else: + header[key] += b" " + line[1] + + required = [b"WIDTH", b"HEIGHT", b"DEPTH", b"MAXVAL"] + required_str = b", ".join(required).decode("ascii") + result = [] + for token in required: + if token not in header: + raise png.Error("PAM file must specify " + required_str) + try: + x = int(header[token]) + except ValueError: + raise png.Error(required_str + " must all be valid integers") + if x <= 0: + raise png.Error(required_str + " must all be positive integers") + result.append(x) + + return (b"P7",) + tuple(result) + + +def read_pnm_header(infile): + """ + Read a PNM header, returning (format,width,height,depth,maxval). + Also reads a PAM header (by using a helper function). + `width` and `height` are in pixels. + `depth` is the number of channels in the image; + for PBM and PGM it is synthesized as 1, for PPM as 3; + for PAM images it is read from the header. + `maxval` is synthesized (as 1) for PBM images. + """ + + # Generally, see http://netpbm.sourceforge.net/doc/ppm.html + # and http://netpbm.sourceforge.net/doc/pam.html + + # Technically 'P7' must be followed by a newline, + # so by using rstrip() we are being liberal in what we accept. + # I think this is acceptable. + magic = infile.read(3).rstrip() + if magic == b"P7": + # PAM header parsing is completely different. + return read_pam_header(infile) + + # Expected number of tokens in header (3 for P4, 4 for P6) + expected = 4 + pbm = (b"P1", b"P4") + if magic in pbm: + expected = 3 + header = [magic] + + # We must read the rest of the header byte by byte because + # the final whitespace character may not be a newline. + # Of course all PNM files in the wild use a newline at this point, + # but we are strong and so we avoid + # the temptation to use readline. + bs = bytearray() + backs = bytearray() + + def next(): + if backs: + c = bytes(backs[0:1]) + del backs[0] + else: + c = infile.read(1) + if not c: + raise png.Error("premature EOF reading PNM header") + bs.extend(c) + return c + + def backup(): + """Push last byte of token onto front of backs.""" + backs.insert(0, bs[-1]) + del bs[-1] + + def ignore(): + del bs[:] + + def tokens(): + ls = lexInit + while True: + token, ls = ls() + if token: + yield token + + def lexInit(): + c = next() + # Skip comments + if b"#" <= c <= b"#": + while c not in b"\n\r": + c = next() + ignore() + return None, lexInit + # Skip whitespace (that precedes a token) + if c.isspace(): + ignore() + return None, lexInit + if not c.isdigit(): + raise png.Error("unexpected byte %r found in header" % c) + return None, lexNumber + + def lexNumber(): + # According to the specification it is legal to have comments + # that appear in the middle of a token. + # I've never seen it; and, + # it's a bit awkward to code good lexers in Python (no goto). + # So we break on such cases. + c = next() + while c.isdigit(): + c = next() + backup() + token = bs[:] + ignore() + return token, lexInit + + for token in tokens(): + # All "tokens" are decimal integers, so convert them here. + header.append(int(token)) + if len(header) == expected: + break + + final = next() + if not final.isspace(): + raise png.Error("expected header to end with whitespace, not %r" % final) + + if magic in pbm: + # synthesize a MAXVAL + header.append(1) + depth = (1, 3)[magic == b"P6"] + return header[0], header[1], header[2], depth, header[3] + + +def convert_pnm_plain(w, infile, outfile): + """ + Convert a plain PNM file containing raw pixel data into + a PNG file with the parameters set in the writer object. + Works for plain PGM formats. + """ + + # See convert_pnm_binary for the corresponding function for + # binary PNM formats. + + rows = scan_rows_from_file_plain(infile, w.width, w.height, w.planes) + w.write(outfile, rows) + + +def scan_rows_from_file_plain(infile, width, height, planes): + """ + Generate a sequence of rows from the input file `infile`. + The input file should be in a "Netpbm-like" plain format. + The input file should be positioned at the beginning of the + first value (that is, immediately after the header). + The number of pixels to read is taken from + the image dimensions (`width`, `height`, `planes`). + + Each row is yielded as a single sequence of values. + """ + + # Values per row + vpr = width * planes + + values = [] + rows_output = 0 + + # The core problem is that input lines (text lines) may not + # correspond with pixel rows. We use two nested loops. + # The outer loop reads the input one text line at a time; + # this will contain a whole number of values, which are + # added to the `values` list. + # The inner loop strips the first `vpr` values from the + # list, until there aren't enough. + # Note we can't tell how many iterations the inner loop will + # run for, it could be 0 (if not enough values were read to + # make a whole pixel row) or many (if the entire image were + # on one input line), or somewhere in between. + # In PNM there is in general no requirement to have + # correspondence between text lines and pixel rows. + + for inp in infile: + values.extend(map(int, inp.split())) + while len(values) >= vpr: + yield values[:vpr] + del values[:vpr] + rows_output += 1 + if rows_output >= height: + # Diagnostic here if there are spare values? + return + # Diagnostic here for early EOF? + + +def convert_pnm_binary(w, infile, outfile): + """ + Convert a PNM file containing raw pixel data into + a PNG file with the parameters set in the writer object. + Works for (binary) PGM, PPM, and PAM formats. + """ + + rows = scan_rows_from_file(infile, w.width, w.height, w.planes, w.bitdepth) + w.write(outfile, rows) + + +def scan_rows_from_file(infile, width, height, planes, bitdepth): + """ + Generate a sequence of rows from the input file `infile`. + The input file should be in a "Netpbm-like" binary format. + The input file should be positioned at the beginning of the first pixel. + The number of pixels to read is taken from + the image dimensions (`width`, `height`, `planes`); + the number of bytes per value is implied by `bitdepth`. + Each row is yielded as a single sequence of values. + """ + + # Values per row + vpr = width * planes + # Bytes per row + bpr = vpr + if bitdepth > 8: + assert bitdepth == 16 + bpr *= 2 + fmt = ">%dH" % vpr + + def line(): + return array.array("H", struct.unpack(fmt, infile.read(bpr))) + + else: + + def line(): + return array.array("B", infile.read(bpr)) + + for y in range(height): + yield line() + + +def parse_args(args): + """ + Create a parser and parse the command line arguments. + """ + from argparse import ArgumentParser + + parser = ArgumentParser(description=Description) + version = "%(prog)s " + png.__version__ + parser.add_argument("--version", action="version", version=version) + parser.add_argument( + "-c", + "--compression", + type=int, + metavar="level", + help="zlib compression level (0-9)", + ) + parser.add_argument( + "input", + nargs="?", + default="-", + type=png.cli_open, + metavar="PAM/PNM", + help="input PAM/PNM file to convert", + ) + args = parser.parse_args(args) + return args + + +def main(argv=None): + if argv is None: + argv = sys.argv + + args = parse_args(argv[1:]) + + # Prepare input and output files + infile = args.input + + # Call after parsing, so that --version and --help work. + outfile = png.binary_stdout() + + # Encode PNM to PNG + format, width, height, depth, maxval = read_pnm_header(infile) + + ok_formats = (b"P2", b"P5", b"P6", b"P7") + if format not in ok_formats: + raise NotImplementedError("file format %s not supported" % format) + + # The NetPBM depth (number of channels) completely + # determines the PNG format. + # Observe: + # - L, LA, RGB, RGBA are the 4 modes supported by PNG; + # - they correspond to 1, 2, 3, 4 channels respectively. + # We use the number of channels in the source image to + # determine which one we have. + # We ignore the NetPBM image type and the PAM TUPLTYPE. + greyscale = depth <= 2 + pamalpha = depth in (2, 4) + supported = [2 ** x - 1 for x in range(1, 17)] + try: + mi = supported.index(maxval) + except ValueError: + raise NotImplementedError( + "input maxval (%s) not in supported list %s" % (maxval, str(supported)) + ) + bitdepth = mi + 1 + writer = png.Writer( + width, + height, + greyscale=greyscale, + bitdepth=bitdepth, + alpha=pamalpha, + compression=args.compression, + ) + + plain = format in (b"P1", b"P2", b"P3") + if plain: + convert_pnm_plain(writer, infile, outfile) + else: + convert_pnm_binary(writer, infile, outfile) + + +if __name__ == "__main__": + try: + sys.exit(main()) + except png.Error as e: + print(e, file=sys.stderr) + sys.exit(99) diff --git a/Backend/venv/bin/priplan9topng b/Backend/venv/bin/priplan9topng new file mode 100755 index 00000000..3bc2577e --- /dev/null +++ b/Backend/venv/bin/priplan9topng @@ -0,0 +1,540 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# Imported from //depot/prj/plan9topam/master/code/plan9topam.py#4 on +# 2009-06-15. + +"""Command line tool to convert from Plan 9 image format to PNG format. + +Plan 9 image format description: +https://plan9.io/magic/man2html/6/image + +Where possible this tool will use unbuffered read() calls, +so that when finished the file offset is exactly at the end of +the image data. +This is useful for Plan9 subfont files which place font metric +data immediately after the image. +""" + +# Test materials + +# asset/left.bit is a Plan 9 image file, a leftwards facing Glenda. +# Other materials have to be scrounged from the internet. +# https://plan9.io/sources/plan9/sys/games/lib/sokoban/images/cargo.bit + +import array +import collections +import io + +# http://www.python.org/doc/2.3.5/lib/module-itertools.html +import itertools +import os + +# http://www.python.org/doc/2.3.5/lib/module-re.html +import re +import struct + +# http://www.python.org/doc/2.3.5/lib/module-sys.html +import sys + +# https://docs.python.org/3/library/tarfile.html +import tarfile + + +# https://pypi.org/project/pypng/ +import png + +# internal +import prix + + +class Error(Exception): + """Some sort of Plan 9 image error.""" + + +def block(s, n): + return zip(*[iter(s)] * n) + + +def plan9_as_image(inp): + """Represent a Plan 9 image file as a png.Image instance, so + that it can be written as a PNG file. + Works with compressed input files and may work with uncompressed files. + """ + + # Use inp.raw if available. + # This avoids buffering and means that when the image is processed, + # the resulting input stream is cued up exactly at the end + # of the image. + inp = getattr(inp, "raw", inp) + + info, blocks = plan9_open_image(inp) + + rows, infodict = plan9_image_rows(blocks, info) + + return png.Image(rows, infodict) + + +def plan9_open_image(inp): + """Open a Plan9 image file (`inp` should be an already open + file object), and return (`info`, `blocks`) pair. + `info` should be a Plan9 5-tuple; + `blocks` is the input, and it should yield (`row`, `data`) + pairs (see :meth:`pixmeta`). + """ + + r = inp.read(11) + if r == b"compressed\n": + info, blocks = decompress(inp) + else: + # Since Python 3, there is a good chance that this path + # doesn't work. + info, blocks = glue(inp, r) + + return info, blocks + + +def glue(f, r): + """Return (info, stream) pair, given `r` the initial portion of + the metadata that has already been read from the stream `f`. + """ + + r = r + f.read(60 - len(r)) + return (meta(r), f) + + +def meta(r): + """Convert 60 byte bytestring `r`, the metadata from an image file. + Returns a 5-tuple (*chan*,*minx*,*miny*,*limx*,*limy*). + 5-tuples may settle into lists in transit. + + As per https://plan9.io/magic/man2html/6/image the metadata + comprises 5 words separated by blanks. + As it happens each word starts at an index that is a multiple of 12, + but this routine does not care about that. + """ + + r = r.split() + # :todo: raise FormatError + if 5 != len(r): + raise Error("Expected 5 space-separated words in metadata") + r = [r[0]] + [int(x) for x in r[1:]] + return r + + +def bitdepthof(chan): + """Return the bitdepth for a Plan9 pixel format string.""" + + maxd = 0 + for c in re.findall(rb"[a-z]\d*", chan): + if c[0] != "x": + maxd = max(maxd, int(c[1:])) + return maxd + + +def maxvalof(chan): + """Return the netpbm MAXVAL for a Plan9 pixel format string.""" + + bitdepth = bitdepthof(chan) + return (2 ** bitdepth) - 1 + + +def plan9_image_rows(blocks, metadata): + """ + Convert (uncompressed) Plan 9 image file to pair of (*rows*, *info*). + This is intended to be used by PyPNG format. + *info* is the image info (metadata) returned in a dictionary, + *rows* is an iterator that yields each row in + boxed row flat pixel format. + + `blocks`, should be an iterator of (`row`, `data`) pairs. + """ + + chan, minx, miny, limx, limy = metadata + rows = limy - miny + width = limx - minx + nchans = len(re.findall(b"[a-wyz]", chan)) + alpha = b"a" in chan + # Iverson's convention for the win! + ncolour = nchans - alpha + greyscale = ncolour == 1 + bitdepth = bitdepthof(chan) + maxval = maxvalof(chan) + + # PNG style info dict. + meta = dict( + size=(width, rows), + bitdepth=bitdepth, + greyscale=greyscale, + alpha=alpha, + planes=nchans, + ) + + arraycode = "BH"[bitdepth > 8] + + return ( + map( + lambda x: array.array(arraycode, itertools.chain(*x)), + block(unpack(blocks, rows, width, chan, maxval), width), + ), + meta, + ) + + +def unpack(f, rows, width, chan, maxval): + """Unpack `f` into pixels. + `chan` describes the pixel format using + the Plan9 syntax ("k8", "r8g8b8", and so on). + Assumes the pixel format has a total channel bit depth + that is either a multiple or a divisor of 8 + (the Plan9 image specification requires this). + `f` should be an iterator that returns blocks of input such that + each block contains a whole number of pixels. + The return value is an iterator that yields each pixel as an n-tuple. + """ + + def mask(w): + """An integer, to be used as a mask, with bottom `w` bits set to 1.""" + + return (1 << w) - 1 + + def deblock(f, depth, width): + """A "packer" used to convert multiple bytes into single pixels. + `depth` is the pixel depth in bits (>= 8), `width` is the row width in + pixels. + """ + + w = depth // 8 + i = 0 + for block in f: + for i in range(len(block) // w): + p = block[w * i : w * (i + 1)] + i += w + # Convert little-endian p to integer x + x = 0 + s = 1 # scale + for j in p: + x += s * j + s <<= 8 + yield x + + def bitfunge(f, depth, width): + """A "packer" used to convert single bytes into multiple pixels. + Depth is the pixel depth (< 8), width is the row width in pixels. + """ + + assert 8 / depth == 8 // depth + + for block in f: + col = 0 + for x in block: + for j in range(8 // depth): + yield x >> (8 - depth) + col += 1 + if col == width: + # A row-end forces a new byte even if + # we haven't consumed all of the current byte. + # Effectively rows are bit-padded to make + # a whole number of bytes. + col = 0 + break + x <<= depth + + # number of bits in each channel + bits = [int(d) for d in re.findall(rb"\d+", chan)] + # colr of each channel + # (r, g, b, k for actual colours, and + # a, m, x for alpha, map-index, and unused) + colr = re.findall(b"[a-z]", chan) + + depth = sum(bits) + + # Select a "packer" that either: + # - gathers multiple bytes into a single pixel (for depth >= 8); or, + # - splits bytes into several pixels (for depth < 8). + if depth >= 8: + assert depth % 8 == 0 + packer = deblock + else: + assert 8 % depth == 0 + packer = bitfunge + + for x in packer(f, depth, width): + # x is the pixel as an unsigned integer + o = [] + # This is a bit yucky. + # Extract each channel from the _most_ significant part of x. + for b, col in zip(bits, colr): + v = (x >> (depth - b)) & mask(b) + x <<= b + if col != "x": + # scale to maxval + v = v * float(maxval) / mask(b) + v = int(v + 0.5) + o.append(v) + yield o + + +def decompress(f): + """Decompress a Plan 9 image file. + The input `f` should be a binary file object that + is already cued past the initial 'compressed\n' string. + The return result is (`info`, `blocks`); + `info` is a 5-tuple of the Plan 9 image metadata; + `blocks` is an iterator that yields a (row, data) pair + for each block of data. + """ + + r = meta(f.read(60)) + return r, decomprest(f, r[4]) + + +def decomprest(f, rows): + """Iterator that decompresses the rest of a file once the metadata + have been consumed.""" + + row = 0 + while row < rows: + row, o = deblock(f) + yield o + + +def deblock(f): + """Decompress a single block from a compressed Plan 9 image file. + Each block starts with 2 decimal strings of 12 bytes each. + Yields a sequence of (row, data) pairs where + `row` is the total number of rows processed + (according to the file format) and + `data` is the decompressed data for this block. + """ + + row = int(f.read(12)) + size = int(f.read(12)) + if not (0 <= size <= 6000): + raise Error("block has invalid size; not a Plan 9 image file?") + + # Since each block is at most 6000 bytes we may as well read it all in + # one go. + d = f.read(size) + i = 0 + o = [] + + while i < size: + x = d[i] + i += 1 + if x & 0x80: + x = (x & 0x7F) + 1 + lit = d[i : i + x] + i += x + o.extend(lit) + continue + # x's high-order bit is 0 + length = (x >> 2) + 3 + # Offset is made from bottom 2 bits of x and 8 bits of next byte. + # MSByte LSByte + # +---------------------+-------------------------+ + # | - - - - - - | x1 x0 | d7 d6 d5 d4 d3 d2 d1 d0 | + # +-----------------------------------------------+ + # Had to discover by inspection which way round the bits go, + # because https://plan9.io/magic/man2html/6/image doesn't say. + # that x's 2 bits are most significant. + offset = (x & 3) << 8 + offset |= d[i] + i += 1 + # Note: complement operator neatly maps (0 to 1023) to (-1 to + # -1024). Adding len(o) gives a (non-negative) offset into o from + # which to start indexing. + offset = ~offset + len(o) + if offset < 0: + raise Error( + "byte offset indexes off the begininning of " + "the output buffer; not a Plan 9 image file?" + ) + for j in range(length): + o.append(o[offset + j]) + return row, bytes(o) + + +FontChar = collections.namedtuple("FontChar", "x top bottom left width") + + +def font_copy(inp, image, out, control): + """ + Convert a Plan 9 font (`inp`, `image`) to a series of PNG images, + and write them out as a tar file to the file object `out`. + Write a text control file out to the file object `control`. + + Each valid glyph in the font becomes a single PNG image; + the output is a tar file of all the images. + + A Plan 9 font consists of a Plan 9 image immediately + followed by font data. + The image for the font should be the `image` argument, + the file containing the rest of the font data should be the + file object `inp` which should be cued up to the start of + the font data that immediately follows the image. + + https://plan9.io/magic/man2html/6/font + """ + + # The format is a little unusual, and isn't completely + # clearly documented. + # Each 6-byte structure (see FontChar above) defines + # a rectangular region of the image that is used for each + # glyph. + # The source image region that is used may be strictly + # smaller than the rectangle for the target glyph. + # This seems like a micro-optimisation. + # For each glyph, + # rows above `top` and below `bottom` will not be copied + # from the source (they can be assumed to be blank). + # No space is saved in the source image, since the rows must + # be present. + # `x` is always non-decreasing, so the glyphs appear strictly + # left-to-image in the source image. + # The x of the next glyph is used to + # infer the width of the source rectangle. + # `top` and `bottom` give the y-coordinate of the top- and + # bottom- sides of the rectangle in both source and targets. + # `left` is the x-coordinate of the left-side of the + # rectangle in the target glyph. (equivalently, the amount + # of padding that should be added on the left). + # `width` is the advance-width of the glyph; by convention + # it is 0 for an undefined glyph. + + name = getattr(inp, "name", "*subfont*name*not*supplied*") + + header = inp.read(36) + n, height, ascent = [int(x) for x in header.split()] + print("baseline", name, ascent, file=control, sep=",") + + chs = [] + for i in range(n + 1): + bs = inp.read(6) + ch = FontChar(*struct.unpack(" 0xFF: + fmt = fmt + "H" + else: + fmt = fmt + "B" + for row in rows: + file.write(struct.pack(fmt, *row)) + + file.flush() + + +def main(argv=None): + import argparse + + parser = argparse.ArgumentParser(description="Convert PNG to PAM") + parser.add_argument("--plain", action="store_true") + parser.add_argument( + "input", nargs="?", default="-", type=png.cli_open, metavar="PNG" + ) + + args = parser.parse_args() + + # Encode PNG to PNM (or PAM) + image = png.Reader(file=args.input) + _, _, rows, info = image.asDirect() + write_pnm(png.binary_stdout(), args.plain, rows, info) + + +if __name__ == "__main__": + import sys + + sys.exit(main()) diff --git a/Backend/venv/bin/prirowpng b/Backend/venv/bin/prirowpng new file mode 100755 index 00000000..8c243775 --- /dev/null +++ b/Backend/venv/bin/prirowpng @@ -0,0 +1,71 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# http://www.python.org/doc/2.4.4/lib/module-itertools.html +import itertools +import sys + +import png + +Description = """Join PNG images in a row left-to-right.""" + + +class FormatError(Exception): + """ + Some problem with the image format. + """ + + +def join_row(out, l): + """ + Concatenate the list of images. + All input images must be same height and + have the same number of channels. + They are concatenated left-to-right. + `out` is the (open file) destination for the output image. + `l` should be a list of open files (the input image files). + """ + + l = [png.Reader(file=f) for f in l] + + # Ewgh, side effects. + for r in l: + r.preamble() + + # The reference height; from the first image. + height = l[0].height + # The total target width + width = 0 + for i,r in enumerate(l): + if r.height != height: + raise FormatError('Image %d, height %d, does not match %d.' % + (i, r.height, height)) + width += r.width + + # Various bugs here because different numbers of channels and depths go wrong. + pixel, info = zip(*[r.asDirect()[2:4] for r in l]) + tinfo = dict(info[0]) + del tinfo['size'] + w = png.Writer(width, height, **tinfo) + + def iter_all_rows(): + for row in zip(*pixel): + # `row` is a sequence that has one row from each input image. + # list() is required here to hasten the lazy row building; + # not sure if that's a bug in PyPNG or not. + yield list(itertools.chain(*row)) + w.write(out, iter_all_rows()) + +def main(argv): + import argparse + + parser = argparse.ArgumentParser(description=Description) + parser.add_argument( + "input", nargs="*", default="-", type=png.cli_open, metavar="PNG" + ) + + args = parser.parse_args() + + return join_row(png.binary_stdout(), args.input) + +if __name__ == '__main__': + main(sys.argv) diff --git a/Backend/venv/bin/priweavepng b/Backend/venv/bin/priweavepng new file mode 100755 index 00000000..f9614655 --- /dev/null +++ b/Backend/venv/bin/priweavepng @@ -0,0 +1,215 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 + +# priweavepng +# Weave selected channels from input PNG files into +# a multi-channel output PNG. + +import collections +import re + +from array import array + +import png + +""" +priweavepng file1.png [file2.png ...] + +The `priweavepng` tool combines channels from the input images and +weaves a selection of those channels into an output image. + +Conceptually an intermediate image is formed consisting of +all channels of all input images in the order given on the command line +and in the order of each channel in its image. +Then from 1 to 4 channels are selected and +an image is output with those channels. +The limit on the number of selected channels is +imposed by the PNG image format. + +The `-c n` option selects channel `n`. +Further channels can be selected either by repeating the `-c` option, +or using a comma separated list. +For example `-c 3,2,1` will select channels 3, 2, and 1 in that order; +if the input is an RGB PNG, this will swop the Red and Blue channels. +The order is significant, the order in which the options are given is +the order of the output channels. +It is permissible, and sometimes useful +(for example, grey to colour expansion, see below), +to repeat the same channel. + +If no `-c` option is used the default is +to select all of the input channels, up to the first 4. + +`priweavepng` does not care about the meaning of the channels +and treats them as a matrix of values. + +The numer of output channels determines the colour mode of the PNG file: +L (1-channel, Grey), LA (2-channel, Grey+Alpha), +RGB (3-channel, Red+Green+Blue), RGBA (4-channel, Red+Green+Blue+Alpha). + +The `priweavepng` tool can be used for a variety of +channel building, swopping, and extraction effects: + +Combine 3 grayscale images into RGB colour: + priweavepng grey1.png grey2.png grey3.png + +Swop Red and Blue channels in colour image: + priweavepng -c 3 -c 2 -c 1 rgb.png + +Extract Green channel as a greyscale image: + priweavepng -c 2 rgb.png + +Convert a greyscale image to a colour image (all grey): + priweavepng -c 1 -c 1 -c 1 grey.png + +Add alpha mask from a separate (greyscale) image: + priweavepng rgb.png grey.png + +Extract alpha mask into a separate (greyscale) image: + priweavepng -c 4 rgba.png + +Steal alpha mask from second file and add to first. +Note that the intermediate image in this example has 7 channels: + priweavepng -c 1 -c 2 -c 3 -c 7 rgb.png rgba.png + +Take Green channel from 3 successive colour images to make a new RGB image: + priweavepng -c 2 -c 5 -c 8 rgb1.png rgb2.png rgb3.png + +""" + +Image = collections.namedtuple("Image", "rows info") + +# For each channel in the intermediate raster, +# model: +# - image: the input image (0-based); +# - i: the channel index within that image (0-based); +# - bitdepth: the bitdepth of this channel. +Channel = collections.namedtuple("Channel", "image i bitdepth") + + +class Error(Exception): + pass + + +def weave(out, args): + """Stack the input PNG files and extract channels + into a single output PNG. + """ + + paths = args.input + + if len(paths) < 1: + raise Error("Required input is missing.") + + # List of Image instances + images = [] + # Channel map. Maps from channel number (starting from 1) + # to an (image_index, channel_index) pair. + channel_map = dict() + channel = 1 + + for image_index, path in enumerate(paths): + inp = png.cli_open(path) + rows, info = png.Reader(file=inp).asDirect()[2:] + rows = list(rows) + image = Image(rows, info) + images.append(image) + # A later version of PyPNG may intelligently support + # PNG files with heterogenous bitdepths. + # For now, assumes bitdepth of all channels in image + # is the same. + channel_bitdepth = (image.info["bitdepth"],) * image.info["planes"] + for i in range(image.info["planes"]): + channel_map[channel + i] = Channel(image_index, i, channel_bitdepth[i]) + channel += image.info["planes"] + + assert channel - 1 == sum(image.info["planes"] for image in images) + + # If no channels, select up to first 4 as default. + if not args.channel: + args.channel = range(1, channel)[:4] + + out_channels = len(args.channel) + if not (0 < out_channels <= 4): + raise Error("Too many channels selected (must be 1 to 4)") + alpha = out_channels in (2, 4) + greyscale = out_channels in (1, 2) + + bitdepth = tuple(image.info["bitdepth"] for image in images) + arraytype = "BH"[max(bitdepth) > 8] + + size = [image.info["size"] for image in images] + # Currently, fail unless all images same size. + if len(set(size)) > 1: + raise NotImplementedError("Cannot cope when sizes differ - sorry!") + size = size[0] + + # Values per row, of output image + vpr = out_channels * size[0] + + def weave_row_iter(): + """ + Yield each woven row in turn. + """ + # The zip call creates an iterator that yields + # a tuple with each element containing the next row + # for each of the input images. + for row_tuple in zip(*(image.rows for image in images)): + # output row + row = array(arraytype, [0] * vpr) + # for each output channel select correct input channel + for out_channel_i, selection in enumerate(args.channel): + channel = channel_map[selection] + # incoming row (make it an array) + irow = array(arraytype, row_tuple[channel.image]) + n = images[channel.image].info["planes"] + row[out_channel_i::out_channels] = irow[channel.i :: n] + yield row + + w = png.Writer( + size[0], + size[1], + greyscale=greyscale, + alpha=alpha, + bitdepth=bitdepth, + interlace=args.interlace, + ) + w.write(out, weave_row_iter()) + + +def comma_list(s): + """ + Type and return a list of integers. + """ + + return [int(c) for c in re.findall(r"\d+", s)] + + +def main(argv=None): + import argparse + import itertools + import sys + + if argv is None: + argv = sys.argv + argv = argv[1:] + + parser = argparse.ArgumentParser() + parser.add_argument( + "-c", + "--channel", + action="append", + type=comma_list, + help="list of channels to extract", + ) + parser.add_argument("--interlace", action="store_true", help="write interlaced PNG") + parser.add_argument("input", nargs="+") + args = parser.parse_args(argv) + + if args.channel: + args.channel = list(itertools.chain(*args.channel)) + + return weave(png.binary_stdout(), args) + + +if __name__ == "__main__": + main() diff --git a/Backend/venv/bin/qr b/Backend/venv/bin/qr new file mode 100755 index 00000000..d72fc3be --- /dev/null +++ b/Backend/venv/bin/qr @@ -0,0 +1,7 @@ +#!/home/gnx/Desktop/Hotel-Booking/Backend/venv/bin/python3 +import sys +from qrcode.console_scripts import main +if __name__ == '__main__': + if sys.argv[0].endswith('.exe'): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/SSL.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/SSL.py new file mode 100644 index 00000000..51c60d55 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/SSL.py @@ -0,0 +1,3239 @@ +from __future__ import annotations + +import os +import socket +import typing +import warnings +from collections.abc import Sequence +from errno import errorcode +from functools import partial, wraps +from itertools import chain, count +from sys import platform +from typing import Any, Callable, Optional, TypeVar +from weakref import WeakValueDictionary + +from cryptography import x509 +from cryptography.hazmat.primitives.asymmetric import ec + +from OpenSSL._util import ( + StrOrBytesPath as _StrOrBytesPath, +) +from OpenSSL._util import ( + exception_from_error_queue as _exception_from_error_queue, +) +from OpenSSL._util import ( + ffi as _ffi, +) +from OpenSSL._util import ( + lib as _lib, +) +from OpenSSL._util import ( + make_assert as _make_assert, +) +from OpenSSL._util import ( + no_zero_allocator as _no_zero_allocator, +) +from OpenSSL._util import ( + path_bytes as _path_bytes, +) +from OpenSSL._util import ( + text_to_bytes_and_warn as _text_to_bytes_and_warn, +) +from OpenSSL.crypto import ( + FILETYPE_PEM, + X509, + PKey, + X509Name, + X509Store, + _EllipticCurve, + _PassphraseHelper, + _PrivateKey, +) + +__all__ = [ + "DTLS_CLIENT_METHOD", + "DTLS_METHOD", + "DTLS_SERVER_METHOD", + "MODE_RELEASE_BUFFERS", + "NO_OVERLAPPING_PROTOCOLS", + "OPENSSL_BUILT_ON", + "OPENSSL_CFLAGS", + "OPENSSL_DIR", + "OPENSSL_PLATFORM", + "OPENSSL_VERSION", + "OPENSSL_VERSION_NUMBER", + "OP_ALL", + "OP_CIPHER_SERVER_PREFERENCE", + "OP_COOKIE_EXCHANGE", + "OP_DONT_INSERT_EMPTY_FRAGMENTS", + "OP_EPHEMERAL_RSA", + "OP_MICROSOFT_BIG_SSLV3_BUFFER", + "OP_MICROSOFT_SESS_ID_BUG", + "OP_MSIE_SSLV2_RSA_PADDING", + "OP_NETSCAPE_CA_DN_BUG", + "OP_NETSCAPE_CHALLENGE_BUG", + "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", + "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", + "OP_NO_COMPRESSION", + "OP_NO_QUERY_MTU", + "OP_NO_TICKET", + "OP_PKCS1_CHECK_1", + "OP_PKCS1_CHECK_2", + "OP_SINGLE_DH_USE", + "OP_SINGLE_ECDH_USE", + "OP_SSLEAY_080_CLIENT_DH_BUG", + "OP_SSLREF2_REUSE_CERT_TYPE_BUG", + "OP_TLS_BLOCK_PADDING_BUG", + "OP_TLS_D5_BUG", + "OP_TLS_ROLLBACK_BUG", + "RECEIVED_SHUTDOWN", + "SENT_SHUTDOWN", + "SESS_CACHE_BOTH", + "SESS_CACHE_CLIENT", + "SESS_CACHE_NO_AUTO_CLEAR", + "SESS_CACHE_NO_INTERNAL", + "SESS_CACHE_NO_INTERNAL_LOOKUP", + "SESS_CACHE_NO_INTERNAL_STORE", + "SESS_CACHE_OFF", + "SESS_CACHE_SERVER", + "SSL3_VERSION", + "SSLEAY_BUILT_ON", + "SSLEAY_CFLAGS", + "SSLEAY_DIR", + "SSLEAY_PLATFORM", + "SSLEAY_VERSION", + "SSL_CB_ACCEPT_EXIT", + "SSL_CB_ACCEPT_LOOP", + "SSL_CB_ALERT", + "SSL_CB_CONNECT_EXIT", + "SSL_CB_CONNECT_LOOP", + "SSL_CB_EXIT", + "SSL_CB_HANDSHAKE_DONE", + "SSL_CB_HANDSHAKE_START", + "SSL_CB_LOOP", + "SSL_CB_READ", + "SSL_CB_READ_ALERT", + "SSL_CB_WRITE", + "SSL_CB_WRITE_ALERT", + "SSL_ST_ACCEPT", + "SSL_ST_CONNECT", + "SSL_ST_MASK", + "TLS1_1_VERSION", + "TLS1_2_VERSION", + "TLS1_3_VERSION", + "TLS1_VERSION", + "TLS_CLIENT_METHOD", + "TLS_METHOD", + "TLS_SERVER_METHOD", + "VERIFY_CLIENT_ONCE", + "VERIFY_FAIL_IF_NO_PEER_CERT", + "VERIFY_NONE", + "VERIFY_PEER", + "Connection", + "Context", + "Error", + "OP_NO_SSLv2", + "OP_NO_SSLv3", + "OP_NO_TLSv1", + "OP_NO_TLSv1_1", + "OP_NO_TLSv1_2", + "OP_NO_TLSv1_3", + "SSLeay_version", + "SSLv23_METHOD", + "Session", + "SysCallError", + "TLSv1_1_METHOD", + "TLSv1_2_METHOD", + "TLSv1_METHOD", + "WantReadError", + "WantWriteError", + "WantX509LookupError", + "X509VerificationCodes", + "ZeroReturnError", +] + + +OPENSSL_VERSION_NUMBER: int = _lib.OPENSSL_VERSION_NUMBER +OPENSSL_VERSION: int = _lib.OPENSSL_VERSION +OPENSSL_CFLAGS: int = _lib.OPENSSL_CFLAGS +OPENSSL_PLATFORM: int = _lib.OPENSSL_PLATFORM +OPENSSL_DIR: int = _lib.OPENSSL_DIR +OPENSSL_BUILT_ON: int = _lib.OPENSSL_BUILT_ON + +SSLEAY_VERSION = OPENSSL_VERSION +SSLEAY_CFLAGS = OPENSSL_CFLAGS +SSLEAY_PLATFORM = OPENSSL_PLATFORM +SSLEAY_DIR = OPENSSL_DIR +SSLEAY_BUILT_ON = OPENSSL_BUILT_ON + +SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN +RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN + +SSLv23_METHOD = 3 +TLSv1_METHOD = 4 +TLSv1_1_METHOD = 5 +TLSv1_2_METHOD = 6 +TLS_METHOD = 7 +TLS_SERVER_METHOD = 8 +TLS_CLIENT_METHOD = 9 +DTLS_METHOD = 10 +DTLS_SERVER_METHOD = 11 +DTLS_CLIENT_METHOD = 12 + +SSL3_VERSION: int = _lib.SSL3_VERSION +TLS1_VERSION: int = _lib.TLS1_VERSION +TLS1_1_VERSION: int = _lib.TLS1_1_VERSION +TLS1_2_VERSION: int = _lib.TLS1_2_VERSION +TLS1_3_VERSION: int = _lib.TLS1_3_VERSION + +OP_NO_SSLv2: int = _lib.SSL_OP_NO_SSLv2 +OP_NO_SSLv3: int = _lib.SSL_OP_NO_SSLv3 +OP_NO_TLSv1: int = _lib.SSL_OP_NO_TLSv1 +OP_NO_TLSv1_1: int = _lib.SSL_OP_NO_TLSv1_1 +OP_NO_TLSv1_2: int = _lib.SSL_OP_NO_TLSv1_2 +OP_NO_TLSv1_3: int = _lib.SSL_OP_NO_TLSv1_3 + +MODE_RELEASE_BUFFERS: int = _lib.SSL_MODE_RELEASE_BUFFERS + +OP_SINGLE_DH_USE: int = _lib.SSL_OP_SINGLE_DH_USE +OP_SINGLE_ECDH_USE: int = _lib.SSL_OP_SINGLE_ECDH_USE +OP_EPHEMERAL_RSA: int = _lib.SSL_OP_EPHEMERAL_RSA +OP_MICROSOFT_SESS_ID_BUG: int = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG +OP_NETSCAPE_CHALLENGE_BUG: int = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG +OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: int = ( + _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG +) +OP_SSLREF2_REUSE_CERT_TYPE_BUG: int = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG +OP_MICROSOFT_BIG_SSLV3_BUFFER: int = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER +OP_MSIE_SSLV2_RSA_PADDING: int = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING +OP_SSLEAY_080_CLIENT_DH_BUG: int = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG +OP_TLS_D5_BUG: int = _lib.SSL_OP_TLS_D5_BUG +OP_TLS_BLOCK_PADDING_BUG: int = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG +OP_DONT_INSERT_EMPTY_FRAGMENTS: int = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS +OP_CIPHER_SERVER_PREFERENCE: int = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE +OP_TLS_ROLLBACK_BUG: int = _lib.SSL_OP_TLS_ROLLBACK_BUG +OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 +OP_PKCS1_CHECK_2: int = _lib.SSL_OP_PKCS1_CHECK_2 +OP_NETSCAPE_CA_DN_BUG: int = _lib.SSL_OP_NETSCAPE_CA_DN_BUG +OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: int = ( + _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG +) +OP_NO_COMPRESSION: int = _lib.SSL_OP_NO_COMPRESSION + +OP_NO_QUERY_MTU: int = _lib.SSL_OP_NO_QUERY_MTU +OP_COOKIE_EXCHANGE: int = _lib.SSL_OP_COOKIE_EXCHANGE +OP_NO_TICKET: int = _lib.SSL_OP_NO_TICKET + +try: + OP_NO_RENEGOTIATION: int = _lib.SSL_OP_NO_RENEGOTIATION + __all__.append("OP_NO_RENEGOTIATION") +except AttributeError: + pass + +try: + OP_IGNORE_UNEXPECTED_EOF: int = _lib.SSL_OP_IGNORE_UNEXPECTED_EOF + __all__.append("OP_IGNORE_UNEXPECTED_EOF") +except AttributeError: + pass + +try: + OP_LEGACY_SERVER_CONNECT: int = _lib.SSL_OP_LEGACY_SERVER_CONNECT + __all__.append("OP_LEGACY_SERVER_CONNECT") +except AttributeError: + pass + +OP_ALL: int = _lib.SSL_OP_ALL + +VERIFY_PEER: int = _lib.SSL_VERIFY_PEER +VERIFY_FAIL_IF_NO_PEER_CERT: int = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT +VERIFY_CLIENT_ONCE: int = _lib.SSL_VERIFY_CLIENT_ONCE +VERIFY_NONE: int = _lib.SSL_VERIFY_NONE + +SESS_CACHE_OFF: int = _lib.SSL_SESS_CACHE_OFF +SESS_CACHE_CLIENT: int = _lib.SSL_SESS_CACHE_CLIENT +SESS_CACHE_SERVER: int = _lib.SSL_SESS_CACHE_SERVER +SESS_CACHE_BOTH: int = _lib.SSL_SESS_CACHE_BOTH +SESS_CACHE_NO_AUTO_CLEAR: int = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR +SESS_CACHE_NO_INTERNAL_LOOKUP: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP +SESS_CACHE_NO_INTERNAL_STORE: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE +SESS_CACHE_NO_INTERNAL: int = _lib.SSL_SESS_CACHE_NO_INTERNAL + +SSL_ST_CONNECT: int = _lib.SSL_ST_CONNECT +SSL_ST_ACCEPT: int = _lib.SSL_ST_ACCEPT +SSL_ST_MASK: int = _lib.SSL_ST_MASK + +SSL_CB_LOOP: int = _lib.SSL_CB_LOOP +SSL_CB_EXIT: int = _lib.SSL_CB_EXIT +SSL_CB_READ: int = _lib.SSL_CB_READ +SSL_CB_WRITE: int = _lib.SSL_CB_WRITE +SSL_CB_ALERT: int = _lib.SSL_CB_ALERT +SSL_CB_READ_ALERT: int = _lib.SSL_CB_READ_ALERT +SSL_CB_WRITE_ALERT: int = _lib.SSL_CB_WRITE_ALERT +SSL_CB_ACCEPT_LOOP: int = _lib.SSL_CB_ACCEPT_LOOP +SSL_CB_ACCEPT_EXIT: int = _lib.SSL_CB_ACCEPT_EXIT +SSL_CB_CONNECT_LOOP: int = _lib.SSL_CB_CONNECT_LOOP +SSL_CB_CONNECT_EXIT: int = _lib.SSL_CB_CONNECT_EXIT +SSL_CB_HANDSHAKE_START: int = _lib.SSL_CB_HANDSHAKE_START +SSL_CB_HANDSHAKE_DONE: int = _lib.SSL_CB_HANDSHAKE_DONE + +_Buffer = typing.Union[bytes, bytearray, memoryview] +_T = TypeVar("_T") + + +class _NoOverlappingProtocols: + pass + + +NO_OVERLAPPING_PROTOCOLS = _NoOverlappingProtocols() + +# Callback types. +_ALPNSelectCallback = Callable[ + [ + "Connection", + typing.List[bytes], + ], + typing.Union[bytes, _NoOverlappingProtocols], +] +_CookieGenerateCallback = Callable[["Connection"], bytes] +_CookieVerifyCallback = Callable[["Connection", bytes], bool] +_OCSPClientCallback = Callable[["Connection", bytes, Optional[_T]], bool] +_OCSPServerCallback = Callable[["Connection", Optional[_T]], bytes] +_PassphraseCallback = Callable[[int, bool, Optional[_T]], bytes] +_VerifyCallback = Callable[["Connection", X509, int, int, int], bool] + + +class X509VerificationCodes: + """ + Success and error codes for X509 verification, as returned by the + underlying ``X509_STORE_CTX_get_error()`` function and passed by pyOpenSSL + to verification callback functions. + + See `OpenSSL Verification Errors + `_ + for details. + """ + + OK = _lib.X509_V_OK + ERR_UNABLE_TO_GET_ISSUER_CERT = _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT + ERR_UNABLE_TO_GET_CRL = _lib.X509_V_ERR_UNABLE_TO_GET_CRL + ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = ( + _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE + ) + ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = ( + _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE + ) + ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = ( + _lib.X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY + ) + ERR_CERT_SIGNATURE_FAILURE = _lib.X509_V_ERR_CERT_SIGNATURE_FAILURE + ERR_CRL_SIGNATURE_FAILURE = _lib.X509_V_ERR_CRL_SIGNATURE_FAILURE + ERR_CERT_NOT_YET_VALID = _lib.X509_V_ERR_CERT_NOT_YET_VALID + ERR_CERT_HAS_EXPIRED = _lib.X509_V_ERR_CERT_HAS_EXPIRED + ERR_CRL_NOT_YET_VALID = _lib.X509_V_ERR_CRL_NOT_YET_VALID + ERR_CRL_HAS_EXPIRED = _lib.X509_V_ERR_CRL_HAS_EXPIRED + ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = ( + _lib.X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD + ) + ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = ( + _lib.X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD + ) + ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = ( + _lib.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD + ) + ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = ( + _lib.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD + ) + ERR_OUT_OF_MEM = _lib.X509_V_ERR_OUT_OF_MEM + ERR_DEPTH_ZERO_SELF_SIGNED_CERT = ( + _lib.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT + ) + ERR_SELF_SIGNED_CERT_IN_CHAIN = _lib.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN + ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = ( + _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY + ) + ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = ( + _lib.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE + ) + ERR_CERT_CHAIN_TOO_LONG = _lib.X509_V_ERR_CERT_CHAIN_TOO_LONG + ERR_CERT_REVOKED = _lib.X509_V_ERR_CERT_REVOKED + ERR_INVALID_CA = _lib.X509_V_ERR_INVALID_CA + ERR_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PATH_LENGTH_EXCEEDED + ERR_INVALID_PURPOSE = _lib.X509_V_ERR_INVALID_PURPOSE + ERR_CERT_UNTRUSTED = _lib.X509_V_ERR_CERT_UNTRUSTED + ERR_CERT_REJECTED = _lib.X509_V_ERR_CERT_REJECTED + ERR_SUBJECT_ISSUER_MISMATCH = _lib.X509_V_ERR_SUBJECT_ISSUER_MISMATCH + ERR_AKID_SKID_MISMATCH = _lib.X509_V_ERR_AKID_SKID_MISMATCH + ERR_AKID_ISSUER_SERIAL_MISMATCH = ( + _lib.X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH + ) + ERR_KEYUSAGE_NO_CERTSIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CERTSIGN + ERR_UNABLE_TO_GET_CRL_ISSUER = _lib.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER + ERR_UNHANDLED_CRITICAL_EXTENSION = ( + _lib.X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION + ) + ERR_KEYUSAGE_NO_CRL_SIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN + ERR_UNHANDLED_CRITICAL_CRL_EXTENSION = ( + _lib.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION + ) + ERR_INVALID_NON_CA = _lib.X509_V_ERR_INVALID_NON_CA + ERR_PROXY_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED + ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE = ( + _lib.X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE + ) + ERR_PROXY_CERTIFICATES_NOT_ALLOWED = ( + _lib.X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED + ) + ERR_INVALID_EXTENSION = _lib.X509_V_ERR_INVALID_EXTENSION + ERR_INVALID_POLICY_EXTENSION = _lib.X509_V_ERR_INVALID_POLICY_EXTENSION + ERR_NO_EXPLICIT_POLICY = _lib.X509_V_ERR_NO_EXPLICIT_POLICY + ERR_DIFFERENT_CRL_SCOPE = _lib.X509_V_ERR_DIFFERENT_CRL_SCOPE + ERR_UNSUPPORTED_EXTENSION_FEATURE = ( + _lib.X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE + ) + ERR_UNNESTED_RESOURCE = _lib.X509_V_ERR_UNNESTED_RESOURCE + ERR_PERMITTED_VIOLATION = _lib.X509_V_ERR_PERMITTED_VIOLATION + ERR_EXCLUDED_VIOLATION = _lib.X509_V_ERR_EXCLUDED_VIOLATION + ERR_SUBTREE_MINMAX = _lib.X509_V_ERR_SUBTREE_MINMAX + ERR_UNSUPPORTED_CONSTRAINT_TYPE = ( + _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE + ) + ERR_UNSUPPORTED_CONSTRAINT_SYNTAX = ( + _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX + ) + ERR_UNSUPPORTED_NAME_SYNTAX = _lib.X509_V_ERR_UNSUPPORTED_NAME_SYNTAX + ERR_CRL_PATH_VALIDATION_ERROR = _lib.X509_V_ERR_CRL_PATH_VALIDATION_ERROR + ERR_HOSTNAME_MISMATCH = _lib.X509_V_ERR_HOSTNAME_MISMATCH + ERR_EMAIL_MISMATCH = _lib.X509_V_ERR_EMAIL_MISMATCH + ERR_IP_ADDRESS_MISMATCH = _lib.X509_V_ERR_IP_ADDRESS_MISMATCH + ERR_APPLICATION_VERIFICATION = _lib.X509_V_ERR_APPLICATION_VERIFICATION + + +# Taken from https://golang.org/src/crypto/x509/root_linux.go +_CERTIFICATE_FILE_LOCATIONS = [ + "/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", # OpenSUSE + "/etc/pki/tls/cacert.pem", # OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7 +] + +_CERTIFICATE_PATH_LOCATIONS = [ + "/etc/ssl/certs", # SLES10/SLES11 +] + +# These values are compared to output from cffi's ffi.string so they must be +# byte strings. +_CRYPTOGRAPHY_MANYLINUX_CA_DIR = b"/opt/pyca/cryptography/openssl/certs" +_CRYPTOGRAPHY_MANYLINUX_CA_FILE = b"/opt/pyca/cryptography/openssl/cert.pem" + + +class Error(Exception): + """ + An error occurred in an `OpenSSL.SSL` API. + """ + + +_raise_current_error = partial(_exception_from_error_queue, Error) +_openssl_assert = _make_assert(Error) + + +class WantReadError(Error): + pass + + +class WantWriteError(Error): + pass + + +class WantX509LookupError(Error): + pass + + +class ZeroReturnError(Error): + pass + + +class SysCallError(Error): + pass + + +class _CallbackExceptionHelper: + """ + A base class for wrapper classes that allow for intelligent exception + handling in OpenSSL callbacks. + + :ivar list _problems: Any exceptions that occurred while executing in a + context where they could not be raised in the normal way. Typically + this is because OpenSSL has called into some Python code and requires a + return value. The exceptions are saved to be raised later when it is + possible to do so. + """ + + def __init__(self) -> None: + self._problems: list[Exception] = [] + + def raise_if_problem(self) -> None: + """ + Raise an exception from the OpenSSL error queue or that was previously + captured whe running a callback. + """ + if self._problems: + try: + _raise_current_error() + except Error: + pass + raise self._problems.pop(0) + + +class _VerifyHelper(_CallbackExceptionHelper): + """ + Wrap a callback such that it can be used as a certificate verification + callback. + """ + + def __init__(self, callback: _VerifyCallback) -> None: + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ok, store_ctx): # type: ignore[no-untyped-def] + x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) + _lib.X509_up_ref(x509) + cert = X509._from_raw_x509_ptr(x509) + error_number = _lib.X509_STORE_CTX_get_error(store_ctx) + error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) + + index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx() + ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index) + connection = Connection._reverse_mapping[ssl] + + try: + result = callback( + connection, cert, error_number, error_depth, ok + ) + except Exception as e: + self._problems.append(e) + return 0 + else: + if result: + _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) + return 1 + else: + return 0 + + self.callback = _ffi.callback( + "int (*)(int, X509_STORE_CTX *)", wrapper + ) + + +class _ALPNSelectHelper(_CallbackExceptionHelper): + """ + Wrap a callback such that it can be used as an ALPN selection callback. + """ + + def __init__(self, callback: _ALPNSelectCallback) -> None: + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, out, outlen, in_, inlen, arg): # type: ignore[no-untyped-def] + try: + conn = Connection._reverse_mapping[ssl] + + # The string passed to us is made up of multiple + # length-prefixed bytestrings. We need to split that into a + # list. + instr = _ffi.buffer(in_, inlen)[:] + protolist = [] + while instr: + encoded_len = instr[0] + proto = instr[1 : encoded_len + 1] + protolist.append(proto) + instr = instr[encoded_len + 1 :] + + # Call the callback + outbytes = callback(conn, protolist) + any_accepted = True + if outbytes is NO_OVERLAPPING_PROTOCOLS: + outbytes = b"" + any_accepted = False + elif not isinstance(outbytes, bytes): + raise TypeError( + "ALPN callback must return a bytestring or the " + "special NO_OVERLAPPING_PROTOCOLS sentinel value." + ) + + # Save our callback arguments on the connection object to make + # sure that they don't get freed before OpenSSL can use them. + # Then, return them in the appropriate output parameters. + conn._alpn_select_callback_args = [ + _ffi.new("unsigned char *", len(outbytes)), + _ffi.new("unsigned char[]", outbytes), + ] + outlen[0] = conn._alpn_select_callback_args[0][0] + out[0] = conn._alpn_select_callback_args[1] + if not any_accepted: + return _lib.SSL_TLSEXT_ERR_NOACK + return _lib.SSL_TLSEXT_ERR_OK + except Exception as e: + self._problems.append(e) + return _lib.SSL_TLSEXT_ERR_ALERT_FATAL + + self.callback = _ffi.callback( + ( + "int (*)(SSL *, unsigned char **, unsigned char *, " + "const unsigned char *, unsigned int, void *)" + ), + wrapper, + ) + + +class _OCSPServerCallbackHelper(_CallbackExceptionHelper): + """ + Wrap a callback such that it can be used as an OCSP callback for the server + side. + + Annoyingly, OpenSSL defines one OCSP callback but uses it in two different + ways. For servers, that callback is expected to retrieve some OCSP data and + hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, + SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback + is expected to check the OCSP data, and returns a negative value on error, + 0 if the response is not acceptable, or positive if it is. These are + mutually exclusive return code behaviours, and they mean that we need two + helpers so that we always return an appropriate error code if the user's + code throws an exception. + + Given that we have to have two helpers anyway, these helpers are a bit more + helpery than most: specifically, they hide a few more of the OpenSSL + functions so that the user has an easier time writing these callbacks. + + This helper implements the server side. + """ + + def __init__(self, callback: _OCSPServerCallback[Any]) -> None: + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, cdata): # type: ignore[no-untyped-def] + try: + conn = Connection._reverse_mapping[ssl] + + # Extract the data if any was provided. + if cdata != _ffi.NULL: + data = _ffi.from_handle(cdata) + else: + data = None + + # Call the callback. + ocsp_data = callback(conn, data) + + if not isinstance(ocsp_data, bytes): + raise TypeError("OCSP callback must return a bytestring.") + + # If the OCSP data was provided, we will pass it to OpenSSL. + # However, we have an early exit here: if no OCSP data was + # provided we will just exit out and tell OpenSSL that there + # is nothing to do. + if not ocsp_data: + return 3 # SSL_TLSEXT_ERR_NOACK + + # OpenSSL takes ownership of this data and expects it to have + # been allocated by OPENSSL_malloc. + ocsp_data_length = len(ocsp_data) + data_ptr = _lib.OPENSSL_malloc(ocsp_data_length) + _ffi.buffer(data_ptr, ocsp_data_length)[:] = ocsp_data + + _lib.SSL_set_tlsext_status_ocsp_resp( + ssl, data_ptr, ocsp_data_length + ) + + return 0 + except Exception as e: + self._problems.append(e) + return 2 # SSL_TLSEXT_ERR_ALERT_FATAL + + self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) + + +class _OCSPClientCallbackHelper(_CallbackExceptionHelper): + """ + Wrap a callback such that it can be used as an OCSP callback for the client + side. + + Annoyingly, OpenSSL defines one OCSP callback but uses it in two different + ways. For servers, that callback is expected to retrieve some OCSP data and + hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, + SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback + is expected to check the OCSP data, and returns a negative value on error, + 0 if the response is not acceptable, or positive if it is. These are + mutually exclusive return code behaviours, and they mean that we need two + helpers so that we always return an appropriate error code if the user's + code throws an exception. + + Given that we have to have two helpers anyway, these helpers are a bit more + helpery than most: specifically, they hide a few more of the OpenSSL + functions so that the user has an easier time writing these callbacks. + + This helper implements the client side. + """ + + def __init__(self, callback: _OCSPClientCallback[Any]) -> None: + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, cdata): # type: ignore[no-untyped-def] + try: + conn = Connection._reverse_mapping[ssl] + + # Extract the data if any was provided. + if cdata != _ffi.NULL: + data = _ffi.from_handle(cdata) + else: + data = None + + # Get the OCSP data. + ocsp_ptr = _ffi.new("unsigned char **") + ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) + if ocsp_len < 0: + # No OCSP data. + ocsp_data = b"" + else: + # Copy the OCSP data, then pass it to the callback. + ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] + + valid = callback(conn, ocsp_data, data) + + # Return 1 on success or 0 on error. + return int(bool(valid)) + + except Exception as e: + self._problems.append(e) + # Return negative value if an exception is hit. + return -1 + + self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) + + +class _CookieGenerateCallbackHelper(_CallbackExceptionHelper): + def __init__(self, callback: _CookieGenerateCallback) -> None: + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, out, outlen): # type: ignore[no-untyped-def] + try: + conn = Connection._reverse_mapping[ssl] + cookie = callback(conn) + out[0 : len(cookie)] = cookie + outlen[0] = len(cookie) + return 1 + except Exception as e: + self._problems.append(e) + # "a zero return value can be used to abort the handshake" + return 0 + + self.callback = _ffi.callback( + "int (*)(SSL *, unsigned char *, unsigned int *)", + wrapper, + ) + + +class _CookieVerifyCallbackHelper(_CallbackExceptionHelper): + def __init__(self, callback: _CookieVerifyCallback) -> None: + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, c_cookie, cookie_len): # type: ignore[no-untyped-def] + try: + conn = Connection._reverse_mapping[ssl] + return callback(conn, bytes(c_cookie[0:cookie_len])) + except Exception as e: + self._problems.append(e) + return 0 + + self.callback = _ffi.callback( + "int (*)(SSL *, unsigned char *, unsigned int)", + wrapper, + ) + + +def _asFileDescriptor(obj: Any) -> int: + fd = None + if not isinstance(obj, int): + meth = getattr(obj, "fileno", None) + if meth is not None: + obj = meth() + + if isinstance(obj, int): + fd = obj + + if not isinstance(fd, int): + raise TypeError("argument must be an int, or have a fileno() method.") + elif fd < 0: + raise ValueError( + f"file descriptor cannot be a negative integer ({fd:i})" + ) + + return fd + + +def OpenSSL_version(type: int) -> bytes: + """ + Return a string describing the version of OpenSSL in use. + + :param type: One of the :const:`OPENSSL_` constants defined in this module. + """ + return _ffi.string(_lib.OpenSSL_version(type)) + + +SSLeay_version = OpenSSL_version + + +def _make_requires(flag: int, error: str) -> Callable[[_T], _T]: + """ + Builds a decorator that ensures that functions that rely on OpenSSL + functions that are not present in this build raise NotImplementedError, + rather than AttributeError coming out of cryptography. + + :param flag: A cryptography flag that guards the functions, e.g. + ``Cryptography_HAS_NEXTPROTONEG``. + :param error: The string to be used in the exception if the flag is false. + """ + + def _requires_decorator(func): # type: ignore[no-untyped-def] + if not flag: + + @wraps(func) + def explode(*args, **kwargs): # type: ignore[no-untyped-def] + raise NotImplementedError(error) + + return explode + else: + return func + + return _requires_decorator + + +_requires_keylog = _make_requires( + getattr(_lib, "Cryptography_HAS_KEYLOG", 0), "Key logging not available" +) + + +class Session: + """ + A class representing an SSL session. A session defines certain connection + parameters which may be re-used to speed up the setup of subsequent + connections. + + .. versionadded:: 0.14 + """ + + _session: Any + + +F = TypeVar("F", bound=Callable[..., Any]) + + +def _require_not_used(f: F) -> F: + @wraps(f) + def inner(self: Context, *args: Any, **kwargs: Any) -> Any: + if self._used: + warnings.warn( + ( + "Attempting to mutate a Context after a Connection was " + "created. In the future, this will raise an exception" + ), + DeprecationWarning, + stacklevel=2, + ) + return f(self, *args, **kwargs) + + return typing.cast(F, inner) + + +class Context: + """ + :class:`OpenSSL.SSL.Context` instances define the parameters for setting + up new SSL connections. + + :param method: One of TLS_METHOD, TLS_CLIENT_METHOD, TLS_SERVER_METHOD, + DTLS_METHOD, DTLS_CLIENT_METHOD, or DTLS_SERVER_METHOD. + SSLv23_METHOD, TLSv1_METHOD, etc. are deprecated and should + not be used. + """ + + _methods: typing.ClassVar[ + dict[int, tuple[Callable[[], Any], int | None]] + ] = { + SSLv23_METHOD: (_lib.TLS_method, None), + TLSv1_METHOD: (_lib.TLS_method, TLS1_VERSION), + TLSv1_1_METHOD: (_lib.TLS_method, TLS1_1_VERSION), + TLSv1_2_METHOD: (_lib.TLS_method, TLS1_2_VERSION), + TLS_METHOD: (_lib.TLS_method, None), + TLS_SERVER_METHOD: (_lib.TLS_server_method, None), + TLS_CLIENT_METHOD: (_lib.TLS_client_method, None), + DTLS_METHOD: (_lib.DTLS_method, None), + DTLS_SERVER_METHOD: (_lib.DTLS_server_method, None), + DTLS_CLIENT_METHOD: (_lib.DTLS_client_method, None), + } + + def __init__(self, method: int) -> None: + if not isinstance(method, int): + raise TypeError("method must be an integer") + + try: + method_func, version = self._methods[method] + except KeyError: + raise ValueError("No such protocol") + + method_obj = method_func() + _openssl_assert(method_obj != _ffi.NULL) + + context = _lib.SSL_CTX_new(method_obj) + _openssl_assert(context != _ffi.NULL) + context = _ffi.gc(context, _lib.SSL_CTX_free) + + self._context = context + self._used = False + self._passphrase_helper: _PassphraseHelper | None = None + self._passphrase_callback: _PassphraseCallback[Any] | None = None + self._passphrase_userdata: Any | None = None + self._verify_helper: _VerifyHelper | None = None + self._verify_callback: _VerifyCallback | None = None + self._info_callback = None + self._keylog_callback = None + self._tlsext_servername_callback = None + self._app_data = None + self._alpn_select_helper: _ALPNSelectHelper | None = None + self._alpn_select_callback: _ALPNSelectCallback | None = None + self._ocsp_helper: ( + _OCSPClientCallbackHelper | _OCSPServerCallbackHelper | None + ) = None + self._ocsp_callback: ( + _OCSPClientCallback[Any] | _OCSPServerCallback[Any] | None + ) = None + self._ocsp_data: Any | None = None + self._cookie_generate_helper: _CookieGenerateCallbackHelper | None = ( + None + ) + self._cookie_verify_helper: _CookieVerifyCallbackHelper | None = None + + self.set_mode( + _lib.SSL_MODE_ENABLE_PARTIAL_WRITE + | _lib.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER + ) + if version is not None: + self.set_min_proto_version(version) + self.set_max_proto_version(version) + + @_require_not_used + def set_min_proto_version(self, version: int) -> None: + """ + Set the minimum supported protocol version. Setting the minimum + version to 0 will enable protocol versions down to the lowest version + supported by the library. + + If the underlying OpenSSL build is missing support for the selected + version, this method will raise an exception. + """ + _openssl_assert( + _lib.SSL_CTX_set_min_proto_version(self._context, version) == 1 + ) + + @_require_not_used + def set_max_proto_version(self, version: int) -> None: + """ + Set the maximum supported protocol version. Setting the maximum + version to 0 will enable protocol versions up to the highest version + supported by the library. + + If the underlying OpenSSL build is missing support for the selected + version, this method will raise an exception. + """ + _openssl_assert( + _lib.SSL_CTX_set_max_proto_version(self._context, version) == 1 + ) + + @_require_not_used + def load_verify_locations( + self, + cafile: _StrOrBytesPath | None, + capath: _StrOrBytesPath | None = None, + ) -> None: + """ + Let SSL know where we can find trusted certificates for the certificate + chain. Note that the certificates have to be in PEM format. + + If capath is passed, it must be a directory prepared using the + ``c_rehash`` tool included with OpenSSL. Either, but not both, of + *pemfile* or *capath* may be :data:`None`. + + :param cafile: In which file we can find the certificates (``bytes`` or + ``str``). + :param capath: In which directory we can find the certificates + (``bytes`` or ``str``). + + :return: None + """ + if cafile is None: + cafile = _ffi.NULL + else: + cafile = _path_bytes(cafile) + + if capath is None: + capath = _ffi.NULL + else: + capath = _path_bytes(capath) + + load_result = _lib.SSL_CTX_load_verify_locations( + self._context, cafile, capath + ) + if not load_result: + _raise_current_error() + + def _wrap_callback( + self, callback: _PassphraseCallback[_T] + ) -> _PassphraseHelper: + @wraps(callback) + def wrapper(size: int, verify: bool, userdata: Any) -> bytes: + return callback(size, verify, self._passphrase_userdata) + + return _PassphraseHelper( + FILETYPE_PEM, wrapper, more_args=True, truncate=True + ) + + @_require_not_used + def set_passwd_cb( + self, + callback: _PassphraseCallback[_T], + userdata: _T | None = None, + ) -> None: + """ + Set the passphrase callback. This function will be called + when a private key with a passphrase is loaded. + + :param callback: The Python callback to use. This must accept three + positional arguments. First, an integer giving the maximum length + of the passphrase it may return. If the returned passphrase is + longer than this, it will be truncated. Second, a boolean value + which will be true if the user should be prompted for the + passphrase twice and the callback should verify that the two values + supplied are equal. Third, the value given as the *userdata* + parameter to :meth:`set_passwd_cb`. The *callback* must return + a byte string. If an error occurs, *callback* should return a false + value (e.g. an empty string). + :param userdata: (optional) A Python object which will be given as + argument to the callback + :return: None + """ + if not callable(callback): + raise TypeError("callback must be callable") + + self._passphrase_helper = self._wrap_callback(callback) + self._passphrase_callback = self._passphrase_helper.callback + _lib.SSL_CTX_set_default_passwd_cb( + self._context, self._passphrase_callback + ) + self._passphrase_userdata = userdata + + @_require_not_used + def set_default_verify_paths(self) -> None: + """ + Specify that the platform provided CA certificates are to be used for + verification purposes. This method has some caveats related to the + binary wheels that cryptography (pyOpenSSL's primary dependency) ships: + + * macOS will only load certificates using this method if the user has + the ``openssl@1.1`` `Homebrew `_ formula installed + in the default location. + * Windows will not work. + * manylinux cryptography wheels will work on most common Linux + distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the + manylinux wheel and attempts to load roots via a fallback path. + + :return: None + """ + # SSL_CTX_set_default_verify_paths will attempt to load certs from + # both a cafile and capath that are set at compile time. However, + # it will first check environment variables and, if present, load + # those paths instead + set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) + _openssl_assert(set_result == 1) + # After attempting to set default_verify_paths we need to know whether + # to go down the fallback path. + # First we'll check to see if any env vars have been set. If so, + # we won't try to do anything else because the user has set the path + # themselves. + if not self._check_env_vars_set("SSL_CERT_DIR", "SSL_CERT_FILE"): + default_dir = _ffi.string(_lib.X509_get_default_cert_dir()) + default_file = _ffi.string(_lib.X509_get_default_cert_file()) + # Now we check to see if the default_dir and default_file are set + # to the exact values we use in our manylinux builds. If they are + # then we know to load the fallbacks + if ( + default_dir == _CRYPTOGRAPHY_MANYLINUX_CA_DIR + and default_file == _CRYPTOGRAPHY_MANYLINUX_CA_FILE + ): + # This is manylinux, let's load our fallback paths + self._fallback_default_verify_paths( + _CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS + ) + + def _check_env_vars_set(self, dir_env_var: str, file_env_var: str) -> bool: + """ + Check to see if the default cert dir/file environment vars are present. + + :return: bool + """ + return ( + os.environ.get(file_env_var) is not None + or os.environ.get(dir_env_var) is not None + ) + + def _fallback_default_verify_paths( + self, file_path: list[str], dir_path: list[str] + ) -> None: + """ + Default verify paths are based on the compiled version of OpenSSL. + However, when pyca/cryptography is compiled as a manylinux wheel + that compiled location can potentially be wrong. So, like Go, we + will try a predefined set of paths and attempt to load roots + from there. + + :return: None + """ + for cafile in file_path: + if os.path.isfile(cafile): + self.load_verify_locations(cafile) + break + + for capath in dir_path: + if os.path.isdir(capath): + self.load_verify_locations(None, capath) + break + + @_require_not_used + def use_certificate_chain_file(self, certfile: _StrOrBytesPath) -> None: + """ + Load a certificate chain from a file. + + :param certfile: The name of the certificate chain file (``bytes`` or + ``str``). Must be PEM encoded. + + :return: None + """ + certfile = _path_bytes(certfile) + + result = _lib.SSL_CTX_use_certificate_chain_file( + self._context, certfile + ) + if not result: + _raise_current_error() + + @_require_not_used + def use_certificate_file( + self, certfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM + ) -> None: + """ + Load a certificate from a file + + :param certfile: The name of the certificate file (``bytes`` or + ``str``). + :param filetype: (optional) The encoding of the file, which is either + :const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is + :const:`FILETYPE_PEM`. + + :return: None + """ + certfile = _path_bytes(certfile) + if not isinstance(filetype, int): + raise TypeError("filetype must be an integer") + + use_result = _lib.SSL_CTX_use_certificate_file( + self._context, certfile, filetype + ) + if not use_result: + _raise_current_error() + + @_require_not_used + def use_certificate(self, cert: X509 | x509.Certificate) -> None: + """ + Load a certificate from a X509 object + + :param cert: The X509 object + :return: None + """ + # Mirrored at Connection.use_certificate + if not isinstance(cert, X509): + cert = X509.from_cryptography(cert) + else: + warnings.warn( + ( + "Passing pyOpenSSL X509 objects is deprecated. You " + "should use a cryptography.x509.Certificate instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) + if not use_result: + _raise_current_error() + + @_require_not_used + def add_extra_chain_cert(self, certobj: X509 | x509.Certificate) -> None: + """ + Add certificate to chain + + :param certobj: The X509 certificate object to add to the chain + :return: None + """ + if not isinstance(certobj, X509): + certobj = X509.from_cryptography(certobj) + else: + warnings.warn( + ( + "Passing pyOpenSSL X509 objects is deprecated. You " + "should use a cryptography.x509.Certificate instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + copy = _lib.X509_dup(certobj._x509) + add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) + if not add_result: + # TODO: This is untested. + _lib.X509_free(copy) + _raise_current_error() + + def _raise_passphrase_exception(self) -> None: + if self._passphrase_helper is not None: + self._passphrase_helper.raise_if_problem(Error) + + _raise_current_error() + + @_require_not_used + def use_privatekey_file( + self, keyfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM + ) -> None: + """ + Load a private key from a file + + :param keyfile: The name of the key file (``bytes`` or ``str``) + :param filetype: (optional) The encoding of the file, which is either + :const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is + :const:`FILETYPE_PEM`. + + :return: None + """ + keyfile = _path_bytes(keyfile) + + if not isinstance(filetype, int): + raise TypeError("filetype must be an integer") + + use_result = _lib.SSL_CTX_use_PrivateKey_file( + self._context, keyfile, filetype + ) + if not use_result: + self._raise_passphrase_exception() + + @_require_not_used + def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: + """ + Load a private key from a PKey object + + :param pkey: The PKey object + :return: None + """ + # Mirrored at Connection.use_privatekey + if not isinstance(pkey, PKey): + pkey = PKey.from_cryptography_key(pkey) + else: + warnings.warn( + ( + "Passing pyOpenSSL PKey objects is deprecated. You " + "should use a cryptography private key instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) + if not use_result: + self._raise_passphrase_exception() + + def check_privatekey(self) -> None: + """ + Check if the private key (loaded with :meth:`use_privatekey`) matches + the certificate (loaded with :meth:`use_certificate`) + + :return: :data:`None` (raises :exc:`Error` if something's wrong) + """ + if not _lib.SSL_CTX_check_private_key(self._context): + _raise_current_error() + + @_require_not_used + def load_client_ca(self, cafile: bytes) -> None: + """ + Load the trusted certificates that will be sent to the client. Does + not actually imply any of the certificates are trusted; that must be + configured separately. + + :param bytes cafile: The path to a certificates file in PEM format. + :return: None + """ + ca_list = _lib.SSL_load_client_CA_file( + _text_to_bytes_and_warn("cafile", cafile) + ) + _openssl_assert(ca_list != _ffi.NULL) + _lib.SSL_CTX_set_client_CA_list(self._context, ca_list) + + @_require_not_used + def set_session_id(self, buf: bytes) -> None: + """ + Set the session id to *buf* within which a session can be reused for + this Context object. This is needed when doing session resumption, + because there is no way for a stored session to know which Context + object it is associated with. + + :param bytes buf: The session id. + + :returns: None + """ + buf = _text_to_bytes_and_warn("buf", buf) + _openssl_assert( + _lib.SSL_CTX_set_session_id_context(self._context, buf, len(buf)) + == 1 + ) + + @_require_not_used + def set_session_cache_mode(self, mode: int) -> int: + """ + Set the behavior of the session cache used by all connections using + this Context. The previously set mode is returned. See + :const:`SESS_CACHE_*` for details about particular modes. + + :param mode: One or more of the SESS_CACHE_* flags (combine using + bitwise or) + :returns: The previously set caching mode. + + .. versionadded:: 0.14 + """ + if not isinstance(mode, int): + raise TypeError("mode must be an integer") + + return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) + + def get_session_cache_mode(self) -> int: + """ + Get the current session cache mode. + + :returns: The currently used cache mode. + + .. versionadded:: 0.14 + """ + return _lib.SSL_CTX_get_session_cache_mode(self._context) + + @_require_not_used + def set_verify( + self, mode: int, callback: _VerifyCallback | None = None + ) -> None: + """ + Set the verification flags for this Context object to *mode* and + specify that *callback* should be used for verification callbacks. + + :param mode: The verify mode, this should be one of + :const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If + :const:`VERIFY_PEER` is used, *mode* can be OR:ed with + :const:`VERIFY_FAIL_IF_NO_PEER_CERT` and + :const:`VERIFY_CLIENT_ONCE` to further control the behaviour. + :param callback: The optional Python verification callback to use. + This should take five arguments: A Connection object, an X509 + object, and three integer variables, which are in turn potential + error number, error depth and return code. *callback* should + return True if verification passes and False otherwise. + If omitted, OpenSSL's default verification is used. + :return: None + + See SSL_CTX_set_verify(3SSL) for further details. + """ + if not isinstance(mode, int): + raise TypeError("mode must be an integer") + + if callback is None: + self._verify_helper = None + self._verify_callback = None + _lib.SSL_CTX_set_verify(self._context, mode, _ffi.NULL) + else: + if not callable(callback): + raise TypeError("callback must be callable") + + self._verify_helper = _VerifyHelper(callback) + self._verify_callback = self._verify_helper.callback + _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) + + @_require_not_used + def set_verify_depth(self, depth: int) -> None: + """ + Set the maximum depth for the certificate chain verification that shall + be allowed for this Context object. + + :param depth: An integer specifying the verify depth + :return: None + """ + if not isinstance(depth, int): + raise TypeError("depth must be an integer") + + _lib.SSL_CTX_set_verify_depth(self._context, depth) + + def get_verify_mode(self) -> int: + """ + Retrieve the Context object's verify mode, as set by + :meth:`set_verify`. + + :return: The verify mode + """ + return _lib.SSL_CTX_get_verify_mode(self._context) + + def get_verify_depth(self) -> int: + """ + Retrieve the Context object's verify depth, as set by + :meth:`set_verify_depth`. + + :return: The verify depth + """ + return _lib.SSL_CTX_get_verify_depth(self._context) + + @_require_not_used + def load_tmp_dh(self, dhfile: _StrOrBytesPath) -> None: + """ + Load parameters for Ephemeral Diffie-Hellman + + :param dhfile: The file to load EDH parameters from (``bytes`` or + ``str``). + + :return: None + """ + dhfile = _path_bytes(dhfile) + + bio = _lib.BIO_new_file(dhfile, b"r") + if bio == _ffi.NULL: + _raise_current_error() + bio = _ffi.gc(bio, _lib.BIO_free) + + dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) + dh = _ffi.gc(dh, _lib.DH_free) + res = _lib.SSL_CTX_set_tmp_dh(self._context, dh) + _openssl_assert(res == 1) + + @_require_not_used + def set_tmp_ecdh(self, curve: _EllipticCurve | ec.EllipticCurve) -> None: + """ + Select a curve to use for ECDHE key exchange. + + :param curve: A curve instance from cryptography + (:class:`~cryptogragraphy.hazmat.primitives.asymmetric.ec.EllipticCurve`). + Alternatively (deprecated) a curve object from either + :meth:`OpenSSL.crypto.get_elliptic_curve` or + :meth:`OpenSSL.crypto.get_elliptic_curves`. + + :return: None + """ + + if isinstance(curve, _EllipticCurve): + warnings.warn( + ( + "Passing pyOpenSSL elliptic curves to set_tmp_ecdh is " + "deprecated. You should use cryptography's elliptic curve " + "types instead." + ), + DeprecationWarning, + stacklevel=2, + ) + _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) + else: + name = curve.name + if name == "secp192r1": + name = "prime192v1" + elif name == "secp256r1": + name = "prime256v1" + nid = _lib.OBJ_txt2nid(name.encode()) + if nid == _lib.NID_undef: + _raise_current_error() + + ec = _lib.EC_KEY_new_by_curve_name(nid) + _openssl_assert(ec != _ffi.NULL) + ec = _ffi.gc(ec, _lib.EC_KEY_free) + _lib.SSL_CTX_set_tmp_ecdh(self._context, ec) + + @_require_not_used + def set_cipher_list(self, cipher_list: bytes) -> None: + """ + Set the list of ciphers to be used in this context. + + See the OpenSSL manual for more information (e.g. + :manpage:`ciphers(1)`). + + Note this API does not change the cipher suites used in TLS 1.3 + Use `set_tls13_ciphersuites` for that. + + :param bytes cipher_list: An OpenSSL cipher string. + :return: None + """ + cipher_list = _text_to_bytes_and_warn("cipher_list", cipher_list) + + if not isinstance(cipher_list, bytes): + raise TypeError("cipher_list must be a byte string.") + + _openssl_assert( + _lib.SSL_CTX_set_cipher_list(self._context, cipher_list) == 1 + ) + + @_require_not_used + def set_tls13_ciphersuites(self, ciphersuites: bytes) -> None: + """ + Set the list of TLS 1.3 ciphers to be used in this context. + OpenSSL maintains a separate list of TLS 1.3+ ciphers to + ciphers for TLS 1.2 and lowers. + + See the OpenSSL manual for more information (e.g. + :manpage:`ciphers(1)`). + + :param bytes ciphersuites: An OpenSSL cipher string containing + TLS 1.3+ ciphersuites. + :return: None + + .. versionadded:: 25.2.0 + """ + if not isinstance(ciphersuites, bytes): + raise TypeError("ciphersuites must be a byte string.") + + _openssl_assert( + _lib.SSL_CTX_set_ciphersuites(self._context, ciphersuites) == 1 + ) + + @_require_not_used + def set_client_ca_list( + self, certificate_authorities: Sequence[X509Name] + ) -> None: + """ + Set the list of preferred client certificate signers for this server + context. + + This list of certificate authorities will be sent to the client when + the server requests a client certificate. + + :param certificate_authorities: a sequence of X509Names. + :return: None + + .. versionadded:: 0.10 + """ + name_stack = _lib.sk_X509_NAME_new_null() + _openssl_assert(name_stack != _ffi.NULL) + + try: + for ca_name in certificate_authorities: + if not isinstance(ca_name, X509Name): + raise TypeError( + f"client CAs must be X509Name objects, not " + f"{type(ca_name).__name__} objects" + ) + copy = _lib.X509_NAME_dup(ca_name._name) + _openssl_assert(copy != _ffi.NULL) + push_result = _lib.sk_X509_NAME_push(name_stack, copy) + if not push_result: + _lib.X509_NAME_free(copy) + _raise_current_error() + except Exception: + _lib.sk_X509_NAME_free(name_stack) + raise + + _lib.SSL_CTX_set_client_CA_list(self._context, name_stack) + + @_require_not_used + def add_client_ca( + self, certificate_authority: X509 | x509.Certificate + ) -> None: + """ + Add the CA certificate to the list of preferred signers for this + context. + + The list of certificate authorities will be sent to the client when the + server requests a client certificate. + + :param certificate_authority: certificate authority's X509 certificate. + :return: None + + .. versionadded:: 0.10 + """ + if not isinstance(certificate_authority, X509): + certificate_authority = X509.from_cryptography( + certificate_authority + ) + else: + warnings.warn( + ( + "Passing pyOpenSSL X509 objects is deprecated. You " + "should use a cryptography.x509.Certificate instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + add_result = _lib.SSL_CTX_add_client_CA( + self._context, certificate_authority._x509 + ) + _openssl_assert(add_result == 1) + + @_require_not_used + def set_timeout(self, timeout: int) -> None: + """ + Set the timeout for newly created sessions for this Context object to + *timeout*. The default value is 300 seconds. See the OpenSSL manual + for more information (e.g. :manpage:`SSL_CTX_set_timeout(3)`). + + :param timeout: The timeout in (whole) seconds + :return: The previous session timeout + """ + if not isinstance(timeout, int): + raise TypeError("timeout must be an integer") + + return _lib.SSL_CTX_set_timeout(self._context, timeout) + + def get_timeout(self) -> int: + """ + Retrieve session timeout, as set by :meth:`set_timeout`. The default + is 300 seconds. + + :return: The session timeout + """ + return _lib.SSL_CTX_get_timeout(self._context) + + @_require_not_used + def set_info_callback( + self, callback: Callable[[Connection, int, int], None] + ) -> None: + """ + Set the information callback to *callback*. This function will be + called from time to time during SSL handshakes. + + :param callback: The Python callback to use. This should take three + arguments: a Connection object and two integers. The first integer + specifies where in the SSL handshake the function was called, and + the other the return code from a (possibly failed) internal + function call. + :return: None + """ + + @wraps(callback) + def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def] + callback(Connection._reverse_mapping[ssl], where, return_code) + + self._info_callback = _ffi.callback( + "void (*)(const SSL *, int, int)", wrapper + ) + _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) + + @_requires_keylog + @_require_not_used + def set_keylog_callback( + self, callback: Callable[[Connection, bytes], None] + ) -> None: + """ + Set the TLS key logging callback to *callback*. This function will be + called whenever TLS key material is generated or received, in order + to allow applications to store this keying material for debugging + purposes. + + :param callback: The Python callback to use. This should take two + arguments: a Connection object and a bytestring that contains + the key material in the format used by NSS for its SSLKEYLOGFILE + debugging output. + :return: None + """ + + @wraps(callback) + def wrapper(ssl, line): # type: ignore[no-untyped-def] + line = _ffi.string(line) + callback(Connection._reverse_mapping[ssl], line) + + self._keylog_callback = _ffi.callback( + "void (*)(const SSL *, const char *)", wrapper + ) + _lib.SSL_CTX_set_keylog_callback(self._context, self._keylog_callback) + + def get_app_data(self) -> Any: + """ + Get the application data (supplied via :meth:`set_app_data()`) + + :return: The application data + """ + return self._app_data + + @_require_not_used + def set_app_data(self, data: Any) -> None: + """ + Set the application data (will be returned from get_app_data()) + + :param data: Any Python object + :return: None + """ + self._app_data = data + + def get_cert_store(self) -> X509Store | None: + """ + Get the certificate store for the context. This can be used to add + "trusted" certificates without using the + :meth:`load_verify_locations` method. + + :return: A X509Store object or None if it does not have one. + """ + store = _lib.SSL_CTX_get_cert_store(self._context) + if store == _ffi.NULL: + # TODO: This is untested. + return None + + pystore = X509Store.__new__(X509Store) + pystore._store = store + return pystore + + @_require_not_used + def set_options(self, options: int) -> int: + """ + Add options. Options set before are not cleared! + This method should be used with the :const:`OP_*` constants. + + :param options: The options to add. + :return: The new option bitmask. + """ + if not isinstance(options, int): + raise TypeError("options must be an integer") + + return _lib.SSL_CTX_set_options(self._context, options) + + @_require_not_used + def set_mode(self, mode: int) -> int: + """ + Add modes via bitmask. Modes set before are not cleared! This method + should be used with the :const:`MODE_*` constants. + + :param mode: The mode to add. + :return: The new mode bitmask. + """ + if not isinstance(mode, int): + raise TypeError("mode must be an integer") + + return _lib.SSL_CTX_set_mode(self._context, mode) + + @_require_not_used + def clear_mode(self, mode_to_clear: int) -> int: + """ + Modes previously set cannot be overwritten without being + cleared first. This method should be used to clear existing modes. + """ + return _lib.SSL_CTX_clear_mode(self._context, mode_to_clear) + + @_require_not_used + def set_tlsext_servername_callback( + self, callback: Callable[[Connection], None] + ) -> None: + """ + Specify a callback function to be called when clients specify a server + name. + + :param callback: The callback function. It will be invoked with one + argument, the Connection instance. + + .. versionadded:: 0.13 + """ + + @wraps(callback) + def wrapper(ssl, alert, arg): # type: ignore[no-untyped-def] + callback(Connection._reverse_mapping[ssl]) + return 0 + + self._tlsext_servername_callback = _ffi.callback( + "int (*)(SSL *, int *, void *)", wrapper + ) + _lib.SSL_CTX_set_tlsext_servername_callback( + self._context, self._tlsext_servername_callback + ) + + @_require_not_used + def set_tlsext_use_srtp(self, profiles: bytes) -> None: + """ + Enable support for negotiating SRTP keying material. + + :param bytes profiles: A colon delimited list of protection profile + names, like ``b'SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32'``. + :return: None + """ + if not isinstance(profiles, bytes): + raise TypeError("profiles must be a byte string.") + + _openssl_assert( + _lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0 + ) + + @_require_not_used + def set_alpn_protos(self, protos: list[bytes]) -> None: + """ + Specify the protocols that the client is prepared to speak after the + TLS connection has been negotiated using Application Layer Protocol + Negotiation. + + :param protos: A list of the protocols to be offered to the server. + This list should be a Python list of bytestrings representing the + protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. + """ + # Different versions of OpenSSL are inconsistent about how they handle + # empty proto lists (see #1043), so we avoid the problem entirely by + # rejecting them ourselves. + if not protos: + raise ValueError("at least one protocol must be specified") + + # Take the list of protocols and join them together, prefixing them + # with their lengths. + protostr = b"".join( + chain.from_iterable((bytes((len(p),)), p) for p in protos) + ) + + # Build a C string from the list. We don't need to save this off + # because OpenSSL immediately copies the data out. + input_str = _ffi.new("unsigned char[]", protostr) + + # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: + # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() + # return 0 on success, and non-0 on failure. + # WARNING: these functions reverse the return value convention. + _openssl_assert( + _lib.SSL_CTX_set_alpn_protos( + self._context, input_str, len(protostr) + ) + == 0 + ) + + @_require_not_used + def set_alpn_select_callback(self, callback: _ALPNSelectCallback) -> None: + """ + Specify a callback function that will be called on the server when a + client offers protocols using ALPN. + + :param callback: The callback function. It will be invoked with two + arguments: the Connection, and a list of offered protocols as + bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It can return + one of those bytestrings to indicate the chosen protocol, the + empty bytestring to terminate the TLS connection, or the + :py:obj:`NO_OVERLAPPING_PROTOCOLS` to indicate that no offered + protocol was selected, but that the connection should not be + aborted. + """ + self._alpn_select_helper = _ALPNSelectHelper(callback) + self._alpn_select_callback = self._alpn_select_helper.callback + _lib.SSL_CTX_set_alpn_select_cb( + self._context, self._alpn_select_callback, _ffi.NULL + ) + + def _set_ocsp_callback( + self, + helper: _OCSPClientCallbackHelper | _OCSPServerCallbackHelper, + data: Any | None, + ) -> None: + """ + This internal helper does the common work for + ``set_ocsp_server_callback`` and ``set_ocsp_client_callback``, which is + almost all of it. + """ + self._ocsp_helper = helper + self._ocsp_callback = helper.callback + if data is None: + self._ocsp_data = _ffi.NULL + else: + self._ocsp_data = _ffi.new_handle(data) + + rc = _lib.SSL_CTX_set_tlsext_status_cb( + self._context, self._ocsp_callback + ) + _openssl_assert(rc == 1) + rc = _lib.SSL_CTX_set_tlsext_status_arg(self._context, self._ocsp_data) + _openssl_assert(rc == 1) + + @_require_not_used + def set_ocsp_server_callback( + self, + callback: _OCSPServerCallback[_T], + data: _T | None = None, + ) -> None: + """ + Set a callback to provide OCSP data to be stapled to the TLS handshake + on the server side. + + :param callback: The callback function. It will be invoked with two + arguments: the Connection, and the optional arbitrary data you have + provided. The callback must return a bytestring that contains the + OCSP data to staple to the handshake. If no OCSP data is available + for this connection, return the empty bytestring. + :param data: Some opaque data that will be passed into the callback + function when called. This can be used to avoid needing to do + complex data lookups or to keep track of what context is being + used. This parameter is optional. + """ + helper = _OCSPServerCallbackHelper(callback) + self._set_ocsp_callback(helper, data) + + @_require_not_used + def set_ocsp_client_callback( + self, + callback: _OCSPClientCallback[_T], + data: _T | None = None, + ) -> None: + """ + Set a callback to validate OCSP data stapled to the TLS handshake on + the client side. + + :param callback: The callback function. It will be invoked with three + arguments: the Connection, a bytestring containing the stapled OCSP + assertion, and the optional arbitrary data you have provided. The + callback must return a boolean that indicates the result of + validating the OCSP data: ``True`` if the OCSP data is valid and + the certificate can be trusted, or ``False`` if either the OCSP + data is invalid or the certificate has been revoked. + :param data: Some opaque data that will be passed into the callback + function when called. This can be used to avoid needing to do + complex data lookups or to keep track of what context is being + used. This parameter is optional. + """ + helper = _OCSPClientCallbackHelper(callback) + self._set_ocsp_callback(helper, data) + + @_require_not_used + def set_cookie_generate_callback( + self, callback: _CookieGenerateCallback + ) -> None: + self._cookie_generate_helper = _CookieGenerateCallbackHelper(callback) + _lib.SSL_CTX_set_cookie_generate_cb( + self._context, + self._cookie_generate_helper.callback, + ) + + @_require_not_used + def set_cookie_verify_callback( + self, callback: _CookieVerifyCallback + ) -> None: + self._cookie_verify_helper = _CookieVerifyCallbackHelper(callback) + _lib.SSL_CTX_set_cookie_verify_cb( + self._context, + self._cookie_verify_helper.callback, + ) + + +class Connection: + _reverse_mapping: typing.MutableMapping[Any, Connection] = ( + WeakValueDictionary() + ) + + def __init__( + self, context: Context, socket: socket.socket | None = None + ) -> None: + """ + Create a new Connection object, using the given OpenSSL.SSL.Context + instance and socket. + + :param context: An SSL Context to use for this connection + :param socket: The socket to use for transport layer + """ + if not isinstance(context, Context): + raise TypeError("context must be a Context instance") + + context._used = True + + ssl = _lib.SSL_new(context._context) + self._ssl = _ffi.gc(ssl, _lib.SSL_free) + # We set SSL_MODE_AUTO_RETRY to handle situations where OpenSSL returns + # an SSL_ERROR_WANT_READ when processing a non-application data packet + # even though there is still data on the underlying transport. + # See https://github.com/openssl/openssl/issues/6234 for more details. + _lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY) + self._context = context + self._app_data = None + + # References to strings used for Application Layer Protocol + # Negotiation. These strings get copied at some point but it's well + # after the callback returns, so we have to hang them somewhere to + # avoid them getting freed. + self._alpn_select_callback_args: Any = None + + # Reference the verify_callback of the Context. This ensures that if + # set_verify is called again after the SSL object has been created we + # do not point to a dangling reference + self._verify_helper = context._verify_helper + self._verify_callback = context._verify_callback + + # And likewise for the cookie callbacks + self._cookie_generate_helper = context._cookie_generate_helper + self._cookie_verify_helper = context._cookie_verify_helper + + self._reverse_mapping[self._ssl] = self + + if socket is None: + self._socket = None + # Don't set up any gc for these, SSL_free will take care of them. + self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) + _openssl_assert(self._into_ssl != _ffi.NULL) + + self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) + _openssl_assert(self._from_ssl != _ffi.NULL) + + _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) + else: + self._into_ssl = None + self._from_ssl = None + self._socket = socket + set_result = _lib.SSL_set_fd( + self._ssl, _asFileDescriptor(self._socket) + ) + _openssl_assert(set_result == 1) + + def __getattr__(self, name: str) -> Any: + """ + Look up attributes on the wrapped socket object if they are not found + on the Connection object. + """ + if self._socket is None: + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{name}'" + ) + else: + return getattr(self._socket, name) + + def _raise_ssl_error(self, ssl: Any, result: int) -> None: + if self._context._verify_helper is not None: + self._context._verify_helper.raise_if_problem() + if self._context._alpn_select_helper is not None: + self._context._alpn_select_helper.raise_if_problem() + if self._context._ocsp_helper is not None: + self._context._ocsp_helper.raise_if_problem() + + error = _lib.SSL_get_error(ssl, result) + if error == _lib.SSL_ERROR_WANT_READ: + raise WantReadError() + elif error == _lib.SSL_ERROR_WANT_WRITE: + raise WantWriteError() + elif error == _lib.SSL_ERROR_ZERO_RETURN: + raise ZeroReturnError() + elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: + # TODO: This is untested. + raise WantX509LookupError() + elif error == _lib.SSL_ERROR_SYSCALL: + if platform == "win32": + errno = _ffi.getwinerror()[0] + else: + errno = _ffi.errno + if _lib.ERR_peek_error() == 0 or errno != 0: + if result < 0 and errno != 0: + raise SysCallError(errno, errorcode.get(errno)) + raise SysCallError(-1, "Unexpected EOF") + else: + # TODO: This is untested, but I think twisted hits it? + _raise_current_error() + elif error == _lib.SSL_ERROR_SSL and _lib.ERR_peek_error() != 0: + # In 3.0.x an unexpected EOF no longer triggers syscall error + # but we want to maintain compatibility so we check here and + # raise syscall if it is an EOF. Since we're not actually sure + # what else could raise SSL_ERROR_SSL we check for the presence + # of the OpenSSL 3 constant SSL_R_UNEXPECTED_EOF_WHILE_READING + # and if it's not present we just raise an error, which matches + # the behavior before we added this elif section + peeked_error = _lib.ERR_peek_error() + reason = _lib.ERR_GET_REASON(peeked_error) + if _lib.Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING: + _openssl_assert( + reason == _lib.SSL_R_UNEXPECTED_EOF_WHILE_READING + ) + _lib.ERR_clear_error() + raise SysCallError(-1, "Unexpected EOF") + else: + _raise_current_error() + elif error == _lib.SSL_ERROR_NONE: + pass + else: + _raise_current_error() + + def get_context(self) -> Context: + """ + Retrieve the :class:`Context` object associated with this + :class:`Connection`. + """ + return self._context + + def set_context(self, context: Context) -> None: + """ + Switch this connection to a new session context. + + :param context: A :class:`Context` instance giving the new session + context to use. + """ + if not isinstance(context, Context): + raise TypeError("context must be a Context instance") + + _lib.SSL_set_SSL_CTX(self._ssl, context._context) + self._context = context + self._context._used = True + + def get_servername(self) -> bytes | None: + """ + Retrieve the servername extension value if provided in the client hello + message, or None if there wasn't one. + + :return: A byte string giving the server name or :data:`None`. + + .. versionadded:: 0.13 + """ + name = _lib.SSL_get_servername( + self._ssl, _lib.TLSEXT_NAMETYPE_host_name + ) + if name == _ffi.NULL: + return None + + return _ffi.string(name) + + def set_verify( + self, mode: int, callback: _VerifyCallback | None = None + ) -> None: + """ + Override the Context object's verification flags for this specific + connection. See :py:meth:`Context.set_verify` for details. + """ + if not isinstance(mode, int): + raise TypeError("mode must be an integer") + + if callback is None: + self._verify_helper = None + self._verify_callback = None + _lib.SSL_set_verify(self._ssl, mode, _ffi.NULL) + else: + if not callable(callback): + raise TypeError("callback must be callable") + + self._verify_helper = _VerifyHelper(callback) + self._verify_callback = self._verify_helper.callback + _lib.SSL_set_verify(self._ssl, mode, self._verify_callback) + + def get_verify_mode(self) -> int: + """ + Retrieve the Connection object's verify mode, as set by + :meth:`set_verify`. + + :return: The verify mode + """ + return _lib.SSL_get_verify_mode(self._ssl) + + def use_certificate(self, cert: X509 | x509.Certificate) -> None: + """ + Load a certificate from a X509 object + + :param cert: The X509 object + :return: None + """ + # Mirrored from Context.use_certificate + if not isinstance(cert, X509): + cert = X509.from_cryptography(cert) + else: + warnings.warn( + ( + "Passing pyOpenSSL X509 objects is deprecated. You " + "should use a cryptography.x509.Certificate instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + use_result = _lib.SSL_use_certificate(self._ssl, cert._x509) + if not use_result: + _raise_current_error() + + def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: + """ + Load a private key from a PKey object + + :param pkey: The PKey object + :return: None + """ + # Mirrored from Context.use_privatekey + if not isinstance(pkey, PKey): + pkey = PKey.from_cryptography_key(pkey) + else: + warnings.warn( + ( + "Passing pyOpenSSL PKey objects is deprecated. You " + "should use a cryptography private key instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + use_result = _lib.SSL_use_PrivateKey(self._ssl, pkey._pkey) + if not use_result: + self._context._raise_passphrase_exception() + + def set_ciphertext_mtu(self, mtu: int) -> None: + """ + For DTLS, set the maximum UDP payload size (*not* including IP/UDP + overhead). + + Note that you might have to set :data:`OP_NO_QUERY_MTU` to prevent + OpenSSL from spontaneously clearing this. + + :param mtu: An integer giving the maximum transmission unit. + + .. versionadded:: 21.1 + """ + _lib.SSL_set_mtu(self._ssl, mtu) + + def get_cleartext_mtu(self) -> int: + """ + For DTLS, get the maximum size of unencrypted data you can pass to + :meth:`write` without exceeding the MTU (as passed to + :meth:`set_ciphertext_mtu`). + + :return: The effective MTU as an integer. + + .. versionadded:: 21.1 + """ + + if not hasattr(_lib, "DTLS_get_data_mtu"): + raise NotImplementedError("requires OpenSSL 1.1.1 or better") + return _lib.DTLS_get_data_mtu(self._ssl) + + def set_tlsext_host_name(self, name: bytes) -> None: + """ + Set the value of the servername extension to send in the client hello. + + :param name: A byte string giving the name. + + .. versionadded:: 0.13 + """ + if not isinstance(name, bytes): + raise TypeError("name must be a byte string") + elif b"\0" in name: + raise TypeError("name must not contain NUL byte") + + # XXX I guess this can fail sometimes? + _lib.SSL_set_tlsext_host_name(self._ssl, name) + + def pending(self) -> int: + """ + Get the number of bytes that can be safely read from the SSL buffer + (**not** the underlying transport buffer). + + :return: The number of bytes available in the receive buffer. + """ + return _lib.SSL_pending(self._ssl) + + def send(self, buf: _Buffer, flags: int = 0) -> int: + """ + Send data on the connection. NOTE: If you get one of the WantRead, + WantWrite or WantX509Lookup exceptions on this, you have to call the + method again with the SAME buffer. + + :param buf: The string, buffer or memoryview to send + :param flags: (optional) Included for compatibility with the socket + API, the value is ignored + :return: The number of bytes written + """ + # Backward compatibility + buf = _text_to_bytes_and_warn("buf", buf) + + with _ffi.from_buffer(buf) as data: + # check len(buf) instead of len(data) for testability + if len(buf) > 2147483647: + raise ValueError( + "Cannot send more than 2**31-1 bytes at once." + ) + + result = _lib.SSL_write(self._ssl, data, len(data)) + self._raise_ssl_error(self._ssl, result) + + return result + + write = send + + def sendall(self, buf: _Buffer, flags: int = 0) -> int: + """ + Send "all" data on the connection. This calls send() repeatedly until + all data is sent. If an error occurs, it's impossible to tell how much + data has been sent. + + :param buf: The string, buffer or memoryview to send + :param flags: (optional) Included for compatibility with the socket + API, the value is ignored + :return: The number of bytes written + """ + buf = _text_to_bytes_and_warn("buf", buf) + + with _ffi.from_buffer(buf) as data: + left_to_send = len(buf) + total_sent = 0 + + while left_to_send: + # SSL_write's num arg is an int, + # so we cannot send more than 2**31-1 bytes at once. + result = _lib.SSL_write( + self._ssl, data + total_sent, min(left_to_send, 2147483647) + ) + self._raise_ssl_error(self._ssl, result) + total_sent += result + left_to_send -= result + + return total_sent + + def recv(self, bufsiz: int, flags: int | None = None) -> bytes: + """ + Receive data on the connection. + + :param bufsiz: The maximum number of bytes to read + :param flags: (optional) The only supported flag is ``MSG_PEEK``, + all other flags are ignored. + :return: The string read from the Connection + """ + buf = _no_zero_allocator("char[]", bufsiz) + if flags is not None and flags & socket.MSG_PEEK: + result = _lib.SSL_peek(self._ssl, buf, bufsiz) + else: + result = _lib.SSL_read(self._ssl, buf, bufsiz) + self._raise_ssl_error(self._ssl, result) + return _ffi.buffer(buf, result)[:] + + read = recv + + def recv_into( + self, + buffer: Any, # collections.abc.Buffer once we use Python 3.12+ + nbytes: int | None = None, + flags: int | None = None, + ) -> int: + """ + Receive data on the connection and copy it directly into the provided + buffer, rather than creating a new string. + + :param buffer: The buffer to copy into. + :param nbytes: (optional) The maximum number of bytes to read into the + buffer. If not present, defaults to the size of the buffer. If + larger than the size of the buffer, is reduced to the size of the + buffer. + :param flags: (optional) The only supported flag is ``MSG_PEEK``, + all other flags are ignored. + :return: The number of bytes read into the buffer. + """ + if nbytes is None: + nbytes = len(buffer) + else: + nbytes = min(nbytes, len(buffer)) + + # We need to create a temporary buffer. This is annoying, it would be + # better if we could pass memoryviews straight into the SSL_read call, + # but right now we can't. Revisit this if CFFI gets that ability. + buf = _no_zero_allocator("char[]", nbytes) + if flags is not None and flags & socket.MSG_PEEK: + result = _lib.SSL_peek(self._ssl, buf, nbytes) + else: + result = _lib.SSL_read(self._ssl, buf, nbytes) + self._raise_ssl_error(self._ssl, result) + + # This strange line is all to avoid a memory copy. The buffer protocol + # should allow us to assign a CFFI buffer to the LHS of this line, but + # on CPython 3.3+ that segfaults. As a workaround, we can temporarily + # wrap it in a memoryview. + buffer[:result] = memoryview(_ffi.buffer(buf, result)) + + return result + + def _handle_bio_errors(self, bio: Any, result: int) -> typing.NoReturn: + if _lib.BIO_should_retry(bio): + if _lib.BIO_should_read(bio): + raise WantReadError() + elif _lib.BIO_should_write(bio): + # TODO: This is untested. + raise WantWriteError() + elif _lib.BIO_should_io_special(bio): + # TODO: This is untested. I think io_special means the socket + # BIO has a not-yet connected socket. + raise ValueError("BIO_should_io_special") + else: + # TODO: This is untested. + raise ValueError("unknown bio failure") + else: + # TODO: This is untested. + _raise_current_error() + + def bio_read(self, bufsiz: int) -> bytes: + """ + If the Connection was created with a memory BIO, this method can be + used to read bytes from the write end of that memory BIO. Many + Connection methods will add bytes which must be read in this manner or + the buffer will eventually fill up and the Connection will be able to + take no further actions. + + :param bufsiz: The maximum number of bytes to read + :return: The string read. + """ + if self._from_ssl is None: + raise TypeError("Connection sock was not None") + + if not isinstance(bufsiz, int): + raise TypeError("bufsiz must be an integer") + + buf = _no_zero_allocator("char[]", bufsiz) + result = _lib.BIO_read(self._from_ssl, buf, bufsiz) + if result <= 0: + self._handle_bio_errors(self._from_ssl, result) + + return _ffi.buffer(buf, result)[:] + + def bio_write(self, buf: _Buffer) -> int: + """ + If the Connection was created with a memory BIO, this method can be + used to add bytes to the read end of that memory BIO. The Connection + can then read the bytes (for example, in response to a call to + :meth:`recv`). + + :param buf: The string to put into the memory BIO. + :return: The number of bytes written + """ + buf = _text_to_bytes_and_warn("buf", buf) + + if self._into_ssl is None: + raise TypeError("Connection sock was not None") + + with _ffi.from_buffer(buf) as data: + result = _lib.BIO_write(self._into_ssl, data, len(data)) + if result <= 0: + self._handle_bio_errors(self._into_ssl, result) + return result + + def renegotiate(self) -> bool: + """ + Renegotiate the session. + + :return: True if the renegotiation can be started, False otherwise + """ + if not self.renegotiate_pending(): + _openssl_assert(_lib.SSL_renegotiate(self._ssl) == 1) + return True + return False + + def do_handshake(self) -> None: + """ + Perform an SSL handshake (usually called after :meth:`renegotiate` or + one of :meth:`set_accept_state` or :meth:`set_connect_state`). This can + raise the same exceptions as :meth:`send` and :meth:`recv`. + + :return: None. + """ + result = _lib.SSL_do_handshake(self._ssl) + self._raise_ssl_error(self._ssl, result) + + def renegotiate_pending(self) -> bool: + """ + Check if there's a renegotiation in progress, it will return False once + a renegotiation is finished. + + :return: Whether there's a renegotiation in progress + """ + return _lib.SSL_renegotiate_pending(self._ssl) == 1 + + def total_renegotiations(self) -> int: + """ + Find out the total number of renegotiations. + + :return: The number of renegotiations. + """ + return _lib.SSL_total_renegotiations(self._ssl) + + def connect(self, addr: Any) -> None: + """ + Call the :meth:`connect` method of the underlying socket and set up SSL + on the socket, using the :class:`Context` object supplied to this + :class:`Connection` object at creation. + + :param addr: A remote address + :return: What the socket's connect method returns + """ + _lib.SSL_set_connect_state(self._ssl) + return self._socket.connect(addr) # type: ignore[return-value, union-attr] + + def connect_ex(self, addr: Any) -> int: + """ + Call the :meth:`connect_ex` method of the underlying socket and set up + SSL on the socket, using the Context object supplied to this Connection + object at creation. Note that if the :meth:`connect_ex` method of the + socket doesn't return 0, SSL won't be initialized. + + :param addr: A remove address + :return: What the socket's connect_ex method returns + """ + connect_ex = self._socket.connect_ex # type: ignore[union-attr] + self.set_connect_state() + return connect_ex(addr) + + def accept(self) -> tuple[Connection, Any]: + """ + Call the :meth:`accept` method of the underlying socket and set up SSL + on the returned socket, using the Context object supplied to this + :class:`Connection` object at creation. + + :return: A *(conn, addr)* pair where *conn* is the new + :class:`Connection` object created, and *address* is as returned by + the socket's :meth:`accept`. + """ + client, addr = self._socket.accept() # type: ignore[union-attr] + conn = Connection(self._context, client) + conn.set_accept_state() + return (conn, addr) + + def DTLSv1_listen(self) -> None: + """ + Call the OpenSSL function DTLSv1_listen on this connection. See the + OpenSSL manual for more details. + + :return: None + """ + # Possible future extension: return the BIO_ADDR in some form. + bio_addr = _lib.BIO_ADDR_new() + try: + result = _lib.DTLSv1_listen(self._ssl, bio_addr) + finally: + _lib.BIO_ADDR_free(bio_addr) + # DTLSv1_listen is weird. A zero return value means 'didn't find a + # ClientHello with valid cookie, but keep trying'. So basically + # WantReadError. But it doesn't work correctly with _raise_ssl_error. + # So we raise it manually instead. + if self._cookie_generate_helper is not None: + self._cookie_generate_helper.raise_if_problem() + if self._cookie_verify_helper is not None: + self._cookie_verify_helper.raise_if_problem() + if result == 0: + raise WantReadError() + if result < 0: + self._raise_ssl_error(self._ssl, result) + + def DTLSv1_get_timeout(self) -> int | None: + """ + Determine when the DTLS SSL object next needs to perform internal + processing due to the passage of time. + + When the returned number of seconds have passed, the + :meth:`DTLSv1_handle_timeout` method needs to be called. + + :return: The time left in seconds before the next timeout or `None` + if no timeout is currently active. + """ + ptv_sec = _ffi.new("time_t *") + ptv_usec = _ffi.new("long *") + if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec): + return ptv_sec[0] + (ptv_usec[0] / 1000000) + else: + return None + + def DTLSv1_handle_timeout(self) -> bool: + """ + Handles any timeout events which have become pending on a DTLS SSL + object. + + :return: `True` if there was a pending timeout, `False` otherwise. + """ + result = _lib.DTLSv1_handle_timeout(self._ssl) + if result < 0: + self._raise_ssl_error(self._ssl, result) + assert False, "unreachable" + else: + return bool(result) + + def bio_shutdown(self) -> None: + """ + If the Connection was created with a memory BIO, this method can be + used to indicate that *end of file* has been reached on the read end of + that memory BIO. + + :return: None + """ + if self._from_ssl is None: + raise TypeError("Connection sock was not None") + + _lib.BIO_set_mem_eof_return(self._into_ssl, 0) + + def shutdown(self) -> bool: + """ + Send the shutdown message to the Connection. + + :return: True if the shutdown completed successfully (i.e. both sides + have sent closure alerts), False otherwise (in which case you + call :meth:`recv` or :meth:`send` when the connection becomes + readable/writeable). + """ + result = _lib.SSL_shutdown(self._ssl) + if result < 0: + self._raise_ssl_error(self._ssl, result) + assert False, "unreachable" + elif result > 0: + return True + else: + return False + + def get_cipher_list(self) -> list[str]: + """ + Retrieve the list of ciphers used by the Connection object. + + :return: A list of native cipher strings. + """ + ciphers = [] + for i in count(): + result = _lib.SSL_get_cipher_list(self._ssl, i) + if result == _ffi.NULL: + break + ciphers.append(_ffi.string(result).decode("utf-8")) + return ciphers + + def get_client_ca_list(self) -> list[X509Name]: + """ + Get CAs whose certificates are suggested for client authentication. + + :return: If this is a server connection, the list of certificate + authorities that will be sent or has been sent to the client, as + controlled by this :class:`Connection`'s :class:`Context`. + + If this is a client connection, the list will be empty until the + connection with the server is established. + + .. versionadded:: 0.10 + """ + ca_names = _lib.SSL_get_client_CA_list(self._ssl) + if ca_names == _ffi.NULL: + # TODO: This is untested. + return [] + + result = [] + for i in range(_lib.sk_X509_NAME_num(ca_names)): + name = _lib.sk_X509_NAME_value(ca_names, i) + copy = _lib.X509_NAME_dup(name) + _openssl_assert(copy != _ffi.NULL) + + pyname = X509Name.__new__(X509Name) + pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) + result.append(pyname) + return result + + def makefile(self, *args: Any, **kwargs: Any) -> typing.NoReturn: + """ + The makefile() method is not implemented, since there is no dup + semantics for SSL connections + + :raise: NotImplementedError + """ + raise NotImplementedError( + "Cannot make file object of OpenSSL.SSL.Connection" + ) + + def get_app_data(self) -> Any: + """ + Retrieve application data as set by :meth:`set_app_data`. + + :return: The application data + """ + return self._app_data + + def set_app_data(self, data: Any) -> None: + """ + Set application data + + :param data: The application data + :return: None + """ + self._app_data = data + + def get_shutdown(self) -> int: + """ + Get the shutdown state of the Connection. + + :return: The shutdown state, a bitvector of SENT_SHUTDOWN, + RECEIVED_SHUTDOWN. + """ + return _lib.SSL_get_shutdown(self._ssl) + + def set_shutdown(self, state: int) -> None: + """ + Set the shutdown state of the Connection. + + :param state: bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. + :return: None + """ + if not isinstance(state, int): + raise TypeError("state must be an integer") + + _lib.SSL_set_shutdown(self._ssl, state) + + def get_state_string(self) -> bytes: + """ + Retrieve a verbose string detailing the state of the Connection. + + :return: A string representing the state + """ + return _ffi.string(_lib.SSL_state_string_long(self._ssl)) + + def server_random(self) -> bytes | None: + """ + Retrieve the random value used with the server hello message. + + :return: A string representing the state + """ + session = _lib.SSL_get_session(self._ssl) + if session == _ffi.NULL: + return None + length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) + _openssl_assert(length > 0) + outp = _no_zero_allocator("unsigned char[]", length) + _lib.SSL_get_server_random(self._ssl, outp, length) + return _ffi.buffer(outp, length)[:] + + def client_random(self) -> bytes | None: + """ + Retrieve the random value used with the client hello message. + + :return: A string representing the state + """ + session = _lib.SSL_get_session(self._ssl) + if session == _ffi.NULL: + return None + + length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) + _openssl_assert(length > 0) + outp = _no_zero_allocator("unsigned char[]", length) + _lib.SSL_get_client_random(self._ssl, outp, length) + return _ffi.buffer(outp, length)[:] + + def master_key(self) -> bytes | None: + """ + Retrieve the value of the master key for this session. + + :return: A string representing the state + """ + session = _lib.SSL_get_session(self._ssl) + if session == _ffi.NULL: + return None + + length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) + _openssl_assert(length > 0) + outp = _no_zero_allocator("unsigned char[]", length) + _lib.SSL_SESSION_get_master_key(session, outp, length) + return _ffi.buffer(outp, length)[:] + + def export_keying_material( + self, label: bytes, olen: int, context: bytes | None = None + ) -> bytes: + """ + Obtain keying material for application use. + + :param: label - a disambiguating label string as described in RFC 5705 + :param: olen - the length of the exported key material in bytes + :param: context - a per-association context value + :return: the exported key material bytes or None + """ + outp = _no_zero_allocator("unsigned char[]", olen) + context_buf = _ffi.NULL + context_len = 0 + use_context = 0 + if context is not None: + context_buf = context + context_len = len(context) + use_context = 1 + success = _lib.SSL_export_keying_material( + self._ssl, + outp, + olen, + label, + len(label), + context_buf, + context_len, + use_context, + ) + _openssl_assert(success == 1) + return _ffi.buffer(outp, olen)[:] + + def sock_shutdown(self, *args: Any, **kwargs: Any) -> None: + """ + Call the :meth:`shutdown` method of the underlying socket. + See :manpage:`shutdown(2)`. + + :return: What the socket's shutdown() method returns + """ + return self._socket.shutdown(*args, **kwargs) # type: ignore[return-value, union-attr] + + @typing.overload + def get_certificate( + self, *, as_cryptography: typing.Literal[True] + ) -> x509.Certificate | None: + pass + + @typing.overload + def get_certificate( + self, *, as_cryptography: typing.Literal[False] = False + ) -> X509 | None: + pass + + def get_certificate( + self, + *, + as_cryptography: typing.Literal[True] | typing.Literal[False] = False, + ) -> X509 | x509.Certificate | None: + """ + Retrieve the local certificate (if any) + + :param bool as_cryptography: Controls whether a + ``cryptography.x509.Certificate`` or an ``OpenSSL.crypto.X509`` + object should be returned. + + :return: The local certificate + """ + cert = _lib.SSL_get_certificate(self._ssl) + if cert != _ffi.NULL: + _lib.X509_up_ref(cert) + pycert = X509._from_raw_x509_ptr(cert) + if as_cryptography: + return pycert.to_cryptography() + return pycert + return None + + @typing.overload + def get_peer_certificate( + self, *, as_cryptography: typing.Literal[True] + ) -> x509.Certificate | None: + pass + + @typing.overload + def get_peer_certificate( + self, *, as_cryptography: typing.Literal[False] = False + ) -> X509 | None: + pass + + def get_peer_certificate( + self, + *, + as_cryptography: typing.Literal[True] | typing.Literal[False] = False, + ) -> X509 | x509.Certificate | None: + """ + Retrieve the other side's certificate (if any) + + :param bool as_cryptography: Controls whether a + ``cryptography.x509.Certificate`` or an ``OpenSSL.crypto.X509`` + object should be returned. + + :return: The peer's certificate + """ + cert = _lib.SSL_get_peer_certificate(self._ssl) + if cert != _ffi.NULL: + pycert = X509._from_raw_x509_ptr(cert) + if as_cryptography: + return pycert.to_cryptography() + return pycert + return None + + @staticmethod + def _cert_stack_to_list(cert_stack: Any) -> list[X509]: + """ + Internal helper to convert a STACK_OF(X509) to a list of X509 + instances. + """ + result = [] + for i in range(_lib.sk_X509_num(cert_stack)): + cert = _lib.sk_X509_value(cert_stack, i) + _openssl_assert(cert != _ffi.NULL) + res = _lib.X509_up_ref(cert) + _openssl_assert(res >= 1) + pycert = X509._from_raw_x509_ptr(cert) + result.append(pycert) + return result + + @staticmethod + def _cert_stack_to_cryptography_list( + cert_stack: Any, + ) -> list[x509.Certificate]: + """ + Internal helper to convert a STACK_OF(X509) to a list of X509 + instances. + """ + result = [] + for i in range(_lib.sk_X509_num(cert_stack)): + cert = _lib.sk_X509_value(cert_stack, i) + _openssl_assert(cert != _ffi.NULL) + res = _lib.X509_up_ref(cert) + _openssl_assert(res >= 1) + pycert = X509._from_raw_x509_ptr(cert) + result.append(pycert.to_cryptography()) + return result + + @typing.overload + def get_peer_cert_chain( + self, *, as_cryptography: typing.Literal[True] + ) -> list[x509.Certificate] | None: + pass + + @typing.overload + def get_peer_cert_chain( + self, *, as_cryptography: typing.Literal[False] = False + ) -> list[X509] | None: + pass + + def get_peer_cert_chain( + self, + *, + as_cryptography: typing.Literal[True] | typing.Literal[False] = False, + ) -> list[X509] | list[x509.Certificate] | None: + """ + Retrieve the other side's certificate (if any) + + :param bool as_cryptography: Controls whether a list of + ``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509`` + object should be returned. + + :return: A list of X509 instances giving the peer's certificate chain, + or None if it does not have one. + """ + cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) + if cert_stack == _ffi.NULL: + return None + + if as_cryptography: + return self._cert_stack_to_cryptography_list(cert_stack) + return self._cert_stack_to_list(cert_stack) + + @typing.overload + def get_verified_chain( + self, *, as_cryptography: typing.Literal[True] + ) -> list[x509.Certificate] | None: + pass + + @typing.overload + def get_verified_chain( + self, *, as_cryptography: typing.Literal[False] = False + ) -> list[X509] | None: + pass + + def get_verified_chain( + self, + *, + as_cryptography: typing.Literal[True] | typing.Literal[False] = False, + ) -> list[X509] | list[x509.Certificate] | None: + """ + Retrieve the verified certificate chain of the peer including the + peer's end entity certificate. It must be called after a session has + been successfully established. If peer verification was not successful + the chain may be incomplete, invalid, or None. + + :param bool as_cryptography: Controls whether a list of + ``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509`` + object should be returned. + + :return: A list of X509 instances giving the peer's verified + certificate chain, or None if it does not have one. + + .. versionadded:: 20.0 + """ + # OpenSSL 1.1+ + cert_stack = _lib.SSL_get0_verified_chain(self._ssl) + if cert_stack == _ffi.NULL: + return None + + if as_cryptography: + return self._cert_stack_to_cryptography_list(cert_stack) + return self._cert_stack_to_list(cert_stack) + + def want_read(self) -> bool: + """ + Checks if more data has to be read from the transport layer to complete + an operation. + + :return: True iff more data has to be read + """ + return _lib.SSL_want_read(self._ssl) + + def want_write(self) -> bool: + """ + Checks if there is data to write to the transport layer to complete an + operation. + + :return: True iff there is data to write + """ + return _lib.SSL_want_write(self._ssl) + + def set_accept_state(self) -> None: + """ + Set the connection to work in server mode. The handshake will be + handled automatically by read/write. + + :return: None + """ + _lib.SSL_set_accept_state(self._ssl) + + def set_connect_state(self) -> None: + """ + Set the connection to work in client mode. The handshake will be + handled automatically by read/write. + + :return: None + """ + _lib.SSL_set_connect_state(self._ssl) + + def get_session(self) -> Session | None: + """ + Returns the Session currently used. + + :return: An instance of :class:`OpenSSL.SSL.Session` or + :obj:`None` if no session exists. + + .. versionadded:: 0.14 + """ + session = _lib.SSL_get1_session(self._ssl) + if session == _ffi.NULL: + return None + + pysession = Session.__new__(Session) + pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) + return pysession + + def set_session(self, session: Session) -> None: + """ + Set the session to be used when the TLS/SSL connection is established. + + :param session: A Session instance representing the session to use. + :returns: None + + .. versionadded:: 0.14 + """ + if not isinstance(session, Session): + raise TypeError("session must be a Session instance") + + result = _lib.SSL_set_session(self._ssl, session._session) + _openssl_assert(result == 1) + + def _get_finished_message( + self, function: Callable[[Any, Any, int], int] + ) -> bytes | None: + """ + Helper to implement :meth:`get_finished` and + :meth:`get_peer_finished`. + + :param function: Either :data:`SSL_get_finished`: or + :data:`SSL_get_peer_finished`. + + :return: :data:`None` if the desired message has not yet been + received, otherwise the contents of the message. + """ + # The OpenSSL documentation says nothing about what might happen if the + # count argument given is zero. Specifically, it doesn't say whether + # the output buffer may be NULL in that case or not. Inspection of the + # implementation reveals that it calls memcpy() unconditionally. + # Section 7.1.4, paragraph 1 of the C standard suggests that + # memcpy(NULL, source, 0) is not guaranteed to produce defined (let + # alone desirable) behavior (though it probably does on just about + # every implementation...) + # + # Allocate a tiny buffer to pass in (instead of just passing NULL as + # one might expect) for the initial call so as to be safe against this + # potentially undefined behavior. + empty = _ffi.new("char[]", 0) + size = function(self._ssl, empty, 0) + if size == 0: + # No Finished message so far. + return None + + buf = _no_zero_allocator("char[]", size) + function(self._ssl, buf, size) + return _ffi.buffer(buf, size)[:] + + def get_finished(self) -> bytes | None: + """ + Obtain the latest TLS Finished message that we sent. + + :return: The contents of the message or :obj:`None` if the TLS + handshake has not yet completed. + + .. versionadded:: 0.15 + """ + return self._get_finished_message(_lib.SSL_get_finished) + + def get_peer_finished(self) -> bytes | None: + """ + Obtain the latest TLS Finished message that we received from the peer. + + :return: The contents of the message or :obj:`None` if the TLS + handshake has not yet completed. + + .. versionadded:: 0.15 + """ + return self._get_finished_message(_lib.SSL_get_peer_finished) + + def get_cipher_name(self) -> str | None: + """ + Obtain the name of the currently used cipher. + + :returns: The name of the currently used cipher or :obj:`None` + if no connection has been established. + + .. versionadded:: 0.15 + """ + cipher = _lib.SSL_get_current_cipher(self._ssl) + if cipher == _ffi.NULL: + return None + else: + name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher)) + return name.decode("utf-8") + + def get_cipher_bits(self) -> int | None: + """ + Obtain the number of secret bits of the currently used cipher. + + :returns: The number of secret bits of the currently used cipher + or :obj:`None` if no connection has been established. + + .. versionadded:: 0.15 + """ + cipher = _lib.SSL_get_current_cipher(self._ssl) + if cipher == _ffi.NULL: + return None + else: + return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL) + + def get_cipher_version(self) -> str | None: + """ + Obtain the protocol version of the currently used cipher. + + :returns: The protocol name of the currently used cipher + or :obj:`None` if no connection has been established. + + .. versionadded:: 0.15 + """ + cipher = _lib.SSL_get_current_cipher(self._ssl) + if cipher == _ffi.NULL: + return None + else: + version = _ffi.string(_lib.SSL_CIPHER_get_version(cipher)) + return version.decode("utf-8") + + def get_protocol_version_name(self) -> str: + """ + Retrieve the protocol version of the current connection. + + :returns: The TLS version of the current connection, for example + the value for TLS 1.2 would be ``TLSv1.2``or ``Unknown`` + for connections that were not successfully established. + """ + version = _ffi.string(_lib.SSL_get_version(self._ssl)) + return version.decode("utf-8") + + def get_protocol_version(self) -> int: + """ + Retrieve the SSL or TLS protocol version of the current connection. + + :returns: The TLS version of the current connection. For example, + it will return ``0x769`` for connections made over TLS version 1. + """ + version = _lib.SSL_version(self._ssl) + return version + + def set_alpn_protos(self, protos: list[bytes]) -> None: + """ + Specify the client's ALPN protocol list. + + These protocols are offered to the server during protocol negotiation. + + :param protos: A list of the protocols to be offered to the server. + This list should be a Python list of bytestrings representing the + protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. + """ + # Different versions of OpenSSL are inconsistent about how they handle + # empty proto lists (see #1043), so we avoid the problem entirely by + # rejecting them ourselves. + if not protos: + raise ValueError("at least one protocol must be specified") + + # Take the list of protocols and join them together, prefixing them + # with their lengths. + protostr = b"".join( + chain.from_iterable((bytes((len(p),)), p) for p in protos) + ) + + # Build a C string from the list. We don't need to save this off + # because OpenSSL immediately copies the data out. + input_str = _ffi.new("unsigned char[]", protostr) + + # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: + # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() + # return 0 on success, and non-0 on failure. + # WARNING: these functions reverse the return value convention. + _openssl_assert( + _lib.SSL_set_alpn_protos(self._ssl, input_str, len(protostr)) == 0 + ) + + def get_alpn_proto_negotiated(self) -> bytes: + """ + Get the protocol that was negotiated by ALPN. + + :returns: A bytestring of the protocol name. If no protocol has been + negotiated yet, returns an empty bytestring. + """ + data = _ffi.new("unsigned char **") + data_len = _ffi.new("unsigned int *") + + _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) + + if not data_len: + return b"" + + return _ffi.buffer(data[0], data_len[0])[:] + + def get_selected_srtp_profile(self) -> bytes: + """ + Get the SRTP protocol which was negotiated. + + :returns: A bytestring of the SRTP profile name. If no profile has been + negotiated yet, returns an empty bytestring. + """ + profile = _lib.SSL_get_selected_srtp_profile(self._ssl) + if not profile: + return b"" + + return _ffi.string(profile.name) + + def request_ocsp(self) -> None: + """ + Called to request that the server sends stapled OCSP data, if + available. If this is not called on the client side then the server + will not send OCSP data. Should be used in conjunction with + :meth:`Context.set_ocsp_client_callback`. + """ + rc = _lib.SSL_set_tlsext_status_type( + self._ssl, _lib.TLSEXT_STATUSTYPE_ocsp + ) + _openssl_assert(rc == 1) + + def set_info_callback( + self, callback: Callable[[Connection, int, int], None] + ) -> None: + """ + Set the information callback to *callback*. This function will be + called from time to time during SSL handshakes. + + :param callback: The Python callback to use. This should take three + arguments: a Connection object and two integers. The first integer + specifies where in the SSL handshake the function was called, and + the other the return code from a (possibly failed) internal + function call. + :return: None + """ + + @wraps(callback) + def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def] + callback(Connection._reverse_mapping[ssl], where, return_code) + + self._info_callback = _ffi.callback( + "void (*)(const SSL *, int, int)", wrapper + ) + _lib.SSL_set_info_callback(self._ssl, self._info_callback) diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__init__.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__init__.py new file mode 100644 index 00000000..7b077cf7 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__init__.py @@ -0,0 +1,31 @@ +# Copyright (C) AB Strakt +# See LICENSE for details. + +""" +pyOpenSSL - A simple wrapper around the OpenSSL library +""" + +from OpenSSL import SSL, crypto +from OpenSSL.version import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "SSL", + "__author__", + "__copyright__", + "__email__", + "__license__", + "__summary__", + "__title__", + "__uri__", + "__version__", + "crypto", +] diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/SSL.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/SSL.cpython-312.pyc new file mode 100644 index 00000000..0263af6c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/SSL.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..19fc85ea Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/_util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/_util.cpython-312.pyc new file mode 100644 index 00000000..b1add367 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/_util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/crypto.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/crypto.cpython-312.pyc new file mode 100644 index 00000000..4735342a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/crypto.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/debug.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/debug.cpython-312.pyc new file mode 100644 index 00000000..fedf9073 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/debug.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/rand.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/rand.cpython-312.pyc new file mode 100644 index 00000000..b0259ada Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/rand.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/version.cpython-312.pyc new file mode 100644 index 00000000..9f6b7e43 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/OpenSSL/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/_util.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/_util.py new file mode 100644 index 00000000..41a64526 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/_util.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import os +import sys +import warnings +from typing import Any, Callable, NoReturn, Union + +from cryptography.hazmat.bindings.openssl.binding import Binding + +if sys.version_info >= (3, 9): + StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] +else: + StrOrBytesPath = Union[str, bytes, os.PathLike] + +binding = Binding() +ffi = binding.ffi +lib: Any = binding.lib + + +# This is a special CFFI allocator that does not bother to zero its memory +# after allocation. This has vastly better performance on large allocations and +# so should be used whenever we don't need the memory zeroed out. +no_zero_allocator = ffi.new_allocator(should_clear_after_alloc=False) + + +def text(charp: Any) -> str: + """ + Get a native string type representing of the given CFFI ``char*`` object. + + :param charp: A C-style string represented using CFFI. + + :return: :class:`str` + """ + if not charp: + return "" + return ffi.string(charp).decode("utf-8") + + +def exception_from_error_queue(exception_type: type[Exception]) -> NoReturn: + """ + Convert an OpenSSL library failure into a Python exception. + + When a call to the native OpenSSL library fails, this is usually signalled + by the return value, and an error code is stored in an error queue + associated with the current thread. The err library provides functions to + obtain these error codes and textual error messages. + """ + errors = [] + + while True: + error = lib.ERR_get_error() + if error == 0: + break + errors.append( + ( + text(lib.ERR_lib_error_string(error)), + text(lib.ERR_func_error_string(error)), + text(lib.ERR_reason_error_string(error)), + ) + ) + + raise exception_type(errors) + + +def make_assert(error: type[Exception]) -> Callable[[bool], Any]: + """ + Create an assert function that uses :func:`exception_from_error_queue` to + raise an exception wrapped by *error*. + """ + + def openssl_assert(ok: bool) -> None: + """ + If *ok* is not True, retrieve the error from OpenSSL and raise it. + """ + if ok is not True: + exception_from_error_queue(error) + + return openssl_assert + + +def path_bytes(s: StrOrBytesPath) -> bytes: + """ + Convert a Python path to a :py:class:`bytes` for the path which can be + passed into an OpenSSL API accepting a filename. + + :param s: A path (valid for os.fspath). + + :return: An instance of :py:class:`bytes`. + """ + b = os.fspath(s) + + if isinstance(b, str): + return b.encode(sys.getfilesystemencoding()) + else: + return b + + +def byte_string(s: str) -> bytes: + return s.encode("charmap") + + +# A marker object to observe whether some optional arguments are passed any +# value or not. +UNSPECIFIED = object() + +_TEXT_WARNING = "str for {0} is no longer accepted, use bytes" + + +def text_to_bytes_and_warn(label: str, obj: Any) -> Any: + """ + If ``obj`` is text, emit a warning that it should be bytes instead and try + to convert it to bytes automatically. + + :param str label: The name of the parameter from which ``obj`` was taken + (so a developer can easily find the source of the problem and correct + it). + + :return: If ``obj`` is the text string type, a ``bytes`` object giving the + UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is + returned. + """ + if isinstance(obj, str): + warnings.warn( + _TEXT_WARNING.format(label), + category=DeprecationWarning, + stacklevel=3, + ) + return obj.encode("utf-8") + return obj diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/crypto.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/crypto.py new file mode 100644 index 00000000..366007e8 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/crypto.py @@ -0,0 +1,2450 @@ +from __future__ import annotations + +import calendar +import datetime +import functools +import sys +import typing +import warnings +from base64 import b16encode +from collections.abc import Iterable, Sequence +from functools import partial +from typing import ( + Any, + Callable, + Union, +) + +if sys.version_info >= (3, 13): + from warnings import deprecated +elif sys.version_info < (3, 8): + _T = typing.TypeVar("T") + + def deprecated(msg: str, **kwargs: object) -> Callable[[_T], _T]: + return lambda f: f +else: + from typing_extensions import deprecated + +from cryptography import utils, x509 +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + rsa, +) + +from OpenSSL._util import StrOrBytesPath +from OpenSSL._util import ( + byte_string as _byte_string, +) +from OpenSSL._util import ( + exception_from_error_queue as _exception_from_error_queue, +) +from OpenSSL._util import ( + ffi as _ffi, +) +from OpenSSL._util import ( + lib as _lib, +) +from OpenSSL._util import ( + make_assert as _make_assert, +) +from OpenSSL._util import ( + path_bytes as _path_bytes, +) + +__all__ = [ + "FILETYPE_ASN1", + "FILETYPE_PEM", + "FILETYPE_TEXT", + "TYPE_DSA", + "TYPE_RSA", + "X509", + "Error", + "PKey", + "X509Extension", + "X509Name", + "X509Req", + "X509Store", + "X509StoreContext", + "X509StoreContextError", + "X509StoreFlags", + "dump_certificate", + "dump_certificate_request", + "dump_privatekey", + "dump_publickey", + "get_elliptic_curve", + "get_elliptic_curves", + "load_certificate", + "load_certificate_request", + "load_privatekey", + "load_publickey", +] + + +_PrivateKey = Union[ + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + rsa.RSAPrivateKey, +] +_PublicKey = Union[ + dsa.DSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + rsa.RSAPublicKey, +] +_Key = Union[_PrivateKey, _PublicKey] +PassphraseCallableT = Union[bytes, Callable[..., bytes]] + + +FILETYPE_PEM: int = _lib.SSL_FILETYPE_PEM +FILETYPE_ASN1: int = _lib.SSL_FILETYPE_ASN1 + +# TODO This was an API mistake. OpenSSL has no such constant. +FILETYPE_TEXT = 2**16 - 1 + +TYPE_RSA: int = _lib.EVP_PKEY_RSA +TYPE_DSA: int = _lib.EVP_PKEY_DSA +TYPE_DH: int = _lib.EVP_PKEY_DH +TYPE_EC: int = _lib.EVP_PKEY_EC + + +class Error(Exception): + """ + An error occurred in an `OpenSSL.crypto` API. + """ + + +_raise_current_error = partial(_exception_from_error_queue, Error) +_openssl_assert = _make_assert(Error) + + +def _new_mem_buf(buffer: bytes | None = None) -> Any: + """ + Allocate a new OpenSSL memory BIO. + + Arrange for the garbage collector to clean it up automatically. + + :param buffer: None or some bytes to use to put into the BIO so that they + can be read out. + """ + if buffer is None: + bio = _lib.BIO_new(_lib.BIO_s_mem()) + free = _lib.BIO_free + else: + data = _ffi.new("char[]", buffer) + bio = _lib.BIO_new_mem_buf(data, len(buffer)) + + # Keep the memory alive as long as the bio is alive! + def free(bio: Any, ref: Any = data) -> Any: + return _lib.BIO_free(bio) + + _openssl_assert(bio != _ffi.NULL) + + bio = _ffi.gc(bio, free) + return bio + + +def _bio_to_string(bio: Any) -> bytes: + """ + Copy the contents of an OpenSSL BIO object into a Python byte string. + """ + result_buffer = _ffi.new("char**") + buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) + return _ffi.buffer(result_buffer[0], buffer_length)[:] + + +def _set_asn1_time(boundary: Any, when: bytes) -> None: + """ + The the time value of an ASN1 time object. + + @param boundary: An ASN1_TIME pointer (or an object safely + castable to that type) which will have its value set. + @param when: A string representation of the desired time value. + + @raise TypeError: If C{when} is not a L{bytes} string. + @raise ValueError: If C{when} does not represent a time in the required + format. + @raise RuntimeError: If the time value cannot be set for some other + (unspecified) reason. + """ + if not isinstance(when, bytes): + raise TypeError("when must be a byte string") + # ASN1_TIME_set_string validates the string without writing anything + # when the destination is NULL. + _openssl_assert(boundary != _ffi.NULL) + + set_result = _lib.ASN1_TIME_set_string(boundary, when) + if set_result == 0: + raise ValueError("Invalid string") + + +def _new_asn1_time(when: bytes) -> Any: + """ + Behaves like _set_asn1_time but returns a new ASN1_TIME object. + + @param when: A string representation of the desired time value. + + @raise TypeError: If C{when} is not a L{bytes} string. + @raise ValueError: If C{when} does not represent a time in the required + format. + @raise RuntimeError: If the time value cannot be set for some other + (unspecified) reason. + """ + ret = _lib.ASN1_TIME_new() + _openssl_assert(ret != _ffi.NULL) + ret = _ffi.gc(ret, _lib.ASN1_TIME_free) + _set_asn1_time(ret, when) + return ret + + +def _get_asn1_time(timestamp: Any) -> bytes | None: + """ + Retrieve the time value of an ASN1 time object. + + @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to + that type) from which the time value will be retrieved. + + @return: The time value from C{timestamp} as a L{bytes} string in a certain + format. Or C{None} if the object contains no time value. + """ + string_timestamp = _ffi.cast("ASN1_STRING*", timestamp) + if _lib.ASN1_STRING_length(string_timestamp) == 0: + return None + elif ( + _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME + ): + return _ffi.string(_lib.ASN1_STRING_get0_data(string_timestamp)) + else: + generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**") + _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp) + _openssl_assert(generalized_timestamp[0] != _ffi.NULL) + + string_timestamp = _ffi.cast("ASN1_STRING*", generalized_timestamp[0]) + string_data = _lib.ASN1_STRING_get0_data(string_timestamp) + string_result = _ffi.string(string_data) + _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) + return string_result + + +class _X509NameInvalidator: + def __init__(self) -> None: + self._names: list[X509Name] = [] + + def add(self, name: X509Name) -> None: + self._names.append(name) + + def clear(self) -> None: + for name in self._names: + # Breaks the object, but also prevents UAF! + del name._name + + +class PKey: + """ + A class representing an DSA or RSA public key or key pair. + """ + + _only_public = False + _initialized = True + + def __init__(self) -> None: + pkey = _lib.EVP_PKEY_new() + self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) + self._initialized = False + + def to_cryptography_key(self) -> _Key: + """ + Export as a ``cryptography`` key. + + :rtype: One of ``cryptography``'s `key interfaces`_. + + .. _key interfaces: https://cryptography.io/en/latest/hazmat/\ + primitives/asymmetric/rsa/#key-interfaces + + .. versionadded:: 16.1.0 + """ + from cryptography.hazmat.primitives.serialization import ( + load_der_private_key, + load_der_public_key, + ) + + if self._only_public: + der = dump_publickey(FILETYPE_ASN1, self) + return typing.cast(_Key, load_der_public_key(der)) + else: + der = dump_privatekey(FILETYPE_ASN1, self) + return typing.cast(_Key, load_der_private_key(der, password=None)) + + @classmethod + def from_cryptography_key(cls, crypto_key: _Key) -> PKey: + """ + Construct based on a ``cryptography`` *crypto_key*. + + :param crypto_key: A ``cryptography`` key. + :type crypto_key: One of ``cryptography``'s `key interfaces`_. + + :rtype: PKey + + .. versionadded:: 16.1.0 + """ + if not isinstance( + crypto_key, + ( + dsa.DSAPrivateKey, + dsa.DSAPublicKey, + ec.EllipticCurvePrivateKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PrivateKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PrivateKey, + ed448.Ed448PublicKey, + rsa.RSAPrivateKey, + rsa.RSAPublicKey, + ), + ): + raise TypeError("Unsupported key type") + + from cryptography.hazmat.primitives.serialization import ( + Encoding, + NoEncryption, + PrivateFormat, + PublicFormat, + ) + + if isinstance( + crypto_key, + ( + dsa.DSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + rsa.RSAPublicKey, + ), + ): + return load_publickey( + FILETYPE_ASN1, + crypto_key.public_bytes( + Encoding.DER, PublicFormat.SubjectPublicKeyInfo + ), + ) + else: + der = crypto_key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) + return load_privatekey(FILETYPE_ASN1, der) + + def generate_key(self, type: int, bits: int) -> None: + """ + Generate a key pair of the given type, with the given number of bits. + + This generates a key "into" the this object. + + :param type: The key type. + :type type: :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` + :param bits: The number of bits. + :type bits: :py:data:`int` ``>= 0`` + :raises TypeError: If :py:data:`type` or :py:data:`bits` isn't + of the appropriate type. + :raises ValueError: If the number of bits isn't an integer of + the appropriate size. + :return: ``None`` + """ + if not isinstance(type, int): + raise TypeError("type must be an integer") + + if not isinstance(bits, int): + raise TypeError("bits must be an integer") + + if type == TYPE_RSA: + if bits <= 0: + raise ValueError("Invalid number of bits") + + # TODO Check error return + exponent = _lib.BN_new() + exponent = _ffi.gc(exponent, _lib.BN_free) + _lib.BN_set_word(exponent, _lib.RSA_F4) + + rsa = _lib.RSA_new() + + result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL) + _openssl_assert(result == 1) + + result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa) + _openssl_assert(result == 1) + + elif type == TYPE_DSA: + dsa = _lib.DSA_new() + _openssl_assert(dsa != _ffi.NULL) + + dsa = _ffi.gc(dsa, _lib.DSA_free) + res = _lib.DSA_generate_parameters_ex( + dsa, bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL + ) + _openssl_assert(res == 1) + + _openssl_assert(_lib.DSA_generate_key(dsa) == 1) + _openssl_assert(_lib.EVP_PKEY_set1_DSA(self._pkey, dsa) == 1) + else: + raise Error("No such key type") + + self._initialized = True + + def check(self) -> bool: + """ + Check the consistency of an RSA private key. + + This is the Python equivalent of OpenSSL's ``RSA_check_key``. + + :return: ``True`` if key is consistent. + + :raise OpenSSL.crypto.Error: if the key is inconsistent. + + :raise TypeError: if the key is of a type which cannot be checked. + Only RSA keys can currently be checked. + """ + if self._only_public: + raise TypeError("public key only") + + if _lib.EVP_PKEY_type(self.type()) != _lib.EVP_PKEY_RSA: + raise TypeError("Only RSA keys can currently be checked.") + + rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) + rsa = _ffi.gc(rsa, _lib.RSA_free) + result = _lib.RSA_check_key(rsa) + if result == 1: + return True + _raise_current_error() + + def type(self) -> int: + """ + Returns the type of the key + + :return: The type of the key. + """ + return _lib.EVP_PKEY_id(self._pkey) + + def bits(self) -> int: + """ + Returns the number of bits of the key + + :return: The number of bits of the key. + """ + return _lib.EVP_PKEY_bits(self._pkey) + + +class _EllipticCurve: + """ + A representation of a supported elliptic curve. + + @cvar _curves: :py:obj:`None` until an attempt is made to load the curves. + Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve` + instances each of which represents one curve supported by the system. + @type _curves: :py:type:`NoneType` or :py:type:`set` + """ + + _curves = None + + def __ne__(self, other: Any) -> bool: + """ + Implement cooperation with the right-hand side argument of ``!=``. + + Python 3 seems to have dropped this cooperation in this very narrow + circumstance. + """ + if isinstance(other, _EllipticCurve): + return super().__ne__(other) + return NotImplemented + + @classmethod + def _load_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]: + """ + Get the curves supported by OpenSSL. + + :param lib: The OpenSSL library binding object. + + :return: A :py:type:`set` of ``cls`` instances giving the names of the + elliptic curves the underlying library supports. + """ + num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) + builtin_curves = _ffi.new("EC_builtin_curve[]", num_curves) + # The return value on this call should be num_curves again. We + # could check it to make sure but if it *isn't* then.. what could + # we do? Abort the whole process, I suppose...? -exarkun + lib.EC_get_builtin_curves(builtin_curves, num_curves) + return set(cls.from_nid(lib, c.nid) for c in builtin_curves) + + @classmethod + def _get_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]: + """ + Get, cache, and return the curves supported by OpenSSL. + + :param lib: The OpenSSL library binding object. + + :return: A :py:type:`set` of ``cls`` instances giving the names of the + elliptic curves the underlying library supports. + """ + if cls._curves is None: + cls._curves = cls._load_elliptic_curves(lib) + return cls._curves + + @classmethod + def from_nid(cls, lib: Any, nid: int) -> _EllipticCurve: + """ + Instantiate a new :py:class:`_EllipticCurve` associated with the given + OpenSSL NID. + + :param lib: The OpenSSL library binding object. + + :param nid: The OpenSSL NID the resulting curve object will represent. + This must be a curve NID (and not, for example, a hash NID) or + subsequent operations will fail in unpredictable ways. + :type nid: :py:class:`int` + + :return: The curve object. + """ + return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) + + def __init__(self, lib: Any, nid: int, name: str) -> None: + """ + :param _lib: The :py:mod:`cryptography` binding instance used to + interface with OpenSSL. + + :param _nid: The OpenSSL NID identifying the curve this object + represents. + :type _nid: :py:class:`int` + + :param name: The OpenSSL short name identifying the curve this object + represents. + :type name: :py:class:`unicode` + """ + self._lib = lib + self._nid = nid + self.name = name + + def __repr__(self) -> str: + return f"" + + def _to_EC_KEY(self) -> Any: + """ + Create a new OpenSSL EC_KEY structure initialized to use this curve. + + The structure is automatically garbage collected when the Python object + is garbage collected. + """ + key = self._lib.EC_KEY_new_by_curve_name(self._nid) + return _ffi.gc(key, _lib.EC_KEY_free) + + +@deprecated( + "get_elliptic_curves is deprecated. You should use the APIs in " + "cryptography instead." +) +def get_elliptic_curves() -> set[_EllipticCurve]: + """ + Return a set of objects representing the elliptic curves supported in the + OpenSSL build in use. + + The curve objects have a :py:class:`unicode` ``name`` attribute by which + they identify themselves. + + The curve objects are useful as values for the argument accepted by + :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be + used for ECDHE key exchange. + """ + return _EllipticCurve._get_elliptic_curves(_lib) + + +@deprecated( + "get_elliptic_curve is deprecated. You should use the APIs in " + "cryptography instead." +) +def get_elliptic_curve(name: str) -> _EllipticCurve: + """ + Return a single curve object selected by name. + + See :py:func:`get_elliptic_curves` for information about curve objects. + + :param name: The OpenSSL short name identifying the curve object to + retrieve. + :type name: :py:class:`unicode` + + If the named curve is not supported then :py:class:`ValueError` is raised. + """ + for curve in get_elliptic_curves(): + if curve.name == name: + return curve + raise ValueError("unknown curve name", name) + + +@functools.total_ordering +class X509Name: + """ + An X.509 Distinguished Name. + + :ivar countryName: The country of the entity. + :ivar C: Alias for :py:attr:`countryName`. + + :ivar stateOrProvinceName: The state or province of the entity. + :ivar ST: Alias for :py:attr:`stateOrProvinceName`. + + :ivar localityName: The locality of the entity. + :ivar L: Alias for :py:attr:`localityName`. + + :ivar organizationName: The organization name of the entity. + :ivar O: Alias for :py:attr:`organizationName`. + + :ivar organizationalUnitName: The organizational unit of the entity. + :ivar OU: Alias for :py:attr:`organizationalUnitName` + + :ivar commonName: The common name of the entity. + :ivar CN: Alias for :py:attr:`commonName`. + + :ivar emailAddress: The e-mail address of the entity. + """ + + def __init__(self, name: X509Name) -> None: + """ + Create a new X509Name, copying the given X509Name instance. + + :param name: The name to copy. + :type name: :py:class:`X509Name` + """ + name = _lib.X509_NAME_dup(name._name) + self._name: Any = _ffi.gc(name, _lib.X509_NAME_free) + + def __setattr__(self, name: str, value: Any) -> None: + if name.startswith("_"): + return super().__setattr__(name, value) + + # Note: we really do not want str subclasses here, so we do not use + # isinstance. + if type(name) is not str: + raise TypeError( + f"attribute name must be string, not " + f"'{type(value).__name__:.200}'" + ) + + nid = _lib.OBJ_txt2nid(_byte_string(name)) + if nid == _lib.NID_undef: + try: + _raise_current_error() + except Error: + pass + raise AttributeError("No such attribute") + + # If there's an old entry for this NID, remove it + for i in range(_lib.X509_NAME_entry_count(self._name)): + ent = _lib.X509_NAME_get_entry(self._name, i) + ent_obj = _lib.X509_NAME_ENTRY_get_object(ent) + ent_nid = _lib.OBJ_obj2nid(ent_obj) + if nid == ent_nid: + ent = _lib.X509_NAME_delete_entry(self._name, i) + _lib.X509_NAME_ENTRY_free(ent) + break + + if isinstance(value, str): + value = value.encode("utf-8") + + add_result = _lib.X509_NAME_add_entry_by_NID( + self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0 + ) + if not add_result: + _raise_current_error() + + def __getattr__(self, name: str) -> str | None: + """ + Find attribute. An X509Name object has the following attributes: + countryName (alias C), stateOrProvince (alias ST), locality (alias L), + organization (alias O), organizationalUnit (alias OU), commonName + (alias CN) and more... + """ + nid = _lib.OBJ_txt2nid(_byte_string(name)) + if nid == _lib.NID_undef: + # This is a bit weird. OBJ_txt2nid indicated failure, but it seems + # a lower level function, a2d_ASN1_OBJECT, also feels the need to + # push something onto the error queue. If we don't clean that up + # now, someone else will bump into it later and be quite confused. + # See lp#314814. + try: + _raise_current_error() + except Error: + pass + raise AttributeError("No such attribute") + + entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1) + if entry_index == -1: + return None + + entry = _lib.X509_NAME_get_entry(self._name, entry_index) + data = _lib.X509_NAME_ENTRY_get_data(entry) + + result_buffer = _ffi.new("unsigned char**") + data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data) + _openssl_assert(data_length >= 0) + + try: + result = _ffi.buffer(result_buffer[0], data_length)[:].decode( + "utf-8" + ) + finally: + # XXX untested + _lib.OPENSSL_free(result_buffer[0]) + return result + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, X509Name): + return NotImplemented + + return _lib.X509_NAME_cmp(self._name, other._name) == 0 + + def __lt__(self, other: Any) -> bool: + if not isinstance(other, X509Name): + return NotImplemented + + return _lib.X509_NAME_cmp(self._name, other._name) < 0 + + def __repr__(self) -> str: + """ + String representation of an X509Name + """ + result_buffer = _ffi.new("char[]", 512) + format_result = _lib.X509_NAME_oneline( + self._name, result_buffer, len(result_buffer) + ) + _openssl_assert(format_result != _ffi.NULL) + + return "".format( + _ffi.string(result_buffer).decode("utf-8"), + ) + + def hash(self) -> int: + """ + Return an integer representation of the first four bytes of the + MD5 digest of the DER representation of the name. + + This is the Python equivalent of OpenSSL's ``X509_NAME_hash``. + + :return: The (integer) hash of this name. + :rtype: :py:class:`int` + """ + return _lib.X509_NAME_hash(self._name) + + def der(self) -> bytes: + """ + Return the DER encoding of this name. + + :return: The DER encoded form of this name. + :rtype: :py:class:`bytes` + """ + result_buffer = _ffi.new("unsigned char**") + encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) + _openssl_assert(encode_result >= 0) + + string_result = _ffi.buffer(result_buffer[0], encode_result)[:] + _lib.OPENSSL_free(result_buffer[0]) + return string_result + + def get_components(self) -> list[tuple[bytes, bytes]]: + """ + Returns the components of this name, as a sequence of 2-tuples. + + :return: The components of this name. + :rtype: :py:class:`list` of ``name, value`` tuples. + """ + result = [] + for i in range(_lib.X509_NAME_entry_count(self._name)): + ent = _lib.X509_NAME_get_entry(self._name, i) + + fname = _lib.X509_NAME_ENTRY_get_object(ent) + fval = _lib.X509_NAME_ENTRY_get_data(ent) + + nid = _lib.OBJ_obj2nid(fname) + name = _lib.OBJ_nid2sn(nid) + + # ffi.string does not handle strings containing NULL bytes + # (which may have been generated by old, broken software) + value = _ffi.buffer( + _lib.ASN1_STRING_get0_data(fval), _lib.ASN1_STRING_length(fval) + )[:] + result.append((_ffi.string(name), value)) + + return result + + +@deprecated( + "X509Extension support in pyOpenSSL is deprecated. You should use the " + "APIs in cryptography." +) +class X509Extension: + """ + An X.509 v3 certificate extension. + + .. deprecated:: 23.3.0 + Use cryptography's X509 APIs instead. + """ + + def __init__( + self, + type_name: bytes, + critical: bool, + value: bytes, + subject: X509 | None = None, + issuer: X509 | None = None, + ) -> None: + """ + Initializes an X509 extension. + + :param type_name: The name of the type of extension_ to create. + :type type_name: :py:data:`bytes` + + :param bool critical: A flag indicating whether this is a critical + extension. + + :param value: The OpenSSL textual representation of the extension's + value. + :type value: :py:data:`bytes` + + :param subject: Optional X509 certificate to use as subject. + :type subject: :py:class:`X509` + + :param issuer: Optional X509 certificate to use as issuer. + :type issuer: :py:class:`X509` + + .. _extension: https://www.openssl.org/docs/manmaster/man5/ + x509v3_config.html#STANDARD-EXTENSIONS + """ + ctx = _ffi.new("X509V3_CTX*") + + # A context is necessary for any extension which uses the r2i + # conversion method. That is, X509V3_EXT_nconf may segfault if passed + # a NULL ctx. Start off by initializing most of the fields to NULL. + _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0) + + # We have no configuration database - but perhaps we should (some + # extensions may require it). + _lib.X509V3_set_ctx_nodb(ctx) + + # Initialize the subject and issuer, if appropriate. ctx is a local, + # and as far as I can tell none of the X509V3_* APIs invoked here steal + # any references, so no need to mess with reference counts or + # duplicates. + if issuer is not None: + if not isinstance(issuer, X509): + raise TypeError("issuer must be an X509 instance") + ctx.issuer_cert = issuer._x509 + if subject is not None: + if not isinstance(subject, X509): + raise TypeError("subject must be an X509 instance") + ctx.subject_cert = subject._x509 + + if critical: + # There are other OpenSSL APIs which would let us pass in critical + # separately, but they're harder to use, and since value is already + # a pile of crappy junk smuggling a ton of utterly important + # structured data, what's the point of trying to avoid nasty stuff + # with strings? (However, X509V3_EXT_i2d in particular seems like + # it would be a better API to invoke. I do not know where to get + # the ext_struc it desires for its last parameter, though.) + value = b"critical," + value + + extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value) + if extension == _ffi.NULL: + _raise_current_error() + self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) + + @property + def _nid(self) -> Any: + return _lib.OBJ_obj2nid( + _lib.X509_EXTENSION_get_object(self._extension) + ) + + _prefixes: typing.ClassVar[dict[int, str]] = { + _lib.GEN_EMAIL: "email", + _lib.GEN_DNS: "DNS", + _lib.GEN_URI: "URI", + } + + def _subjectAltNameString(self) -> str: + names = _ffi.cast( + "GENERAL_NAMES*", _lib.X509V3_EXT_d2i(self._extension) + ) + + names = _ffi.gc(names, _lib.GENERAL_NAMES_free) + parts = [] + for i in range(_lib.sk_GENERAL_NAME_num(names)): + name = _lib.sk_GENERAL_NAME_value(names, i) + try: + label = self._prefixes[name.type] + except KeyError: + bio = _new_mem_buf() + _lib.GENERAL_NAME_print(bio, name) + parts.append(_bio_to_string(bio).decode("utf-8")) + else: + value = _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[ + : + ].decode("utf-8") + parts.append(label + ":" + value) + return ", ".join(parts) + + def __str__(self) -> str: + """ + :return: a nice text representation of the extension + """ + if _lib.NID_subject_alt_name == self._nid: + return self._subjectAltNameString() + + bio = _new_mem_buf() + print_result = _lib.X509V3_EXT_print(bio, self._extension, 0, 0) + _openssl_assert(print_result != 0) + + return _bio_to_string(bio).decode("utf-8") + + def get_critical(self) -> bool: + """ + Returns the critical field of this X.509 extension. + + :return: The critical field. + """ + return _lib.X509_EXTENSION_get_critical(self._extension) + + def get_short_name(self) -> bytes: + """ + Returns the short type name of this X.509 extension. + + The result is a byte string such as :py:const:`b"basicConstraints"`. + + :return: The short type name. + :rtype: :py:data:`bytes` + + .. versionadded:: 0.12 + """ + obj = _lib.X509_EXTENSION_get_object(self._extension) + nid = _lib.OBJ_obj2nid(obj) + # OpenSSL 3.1.0 has a bug where nid2sn returns NULL for NIDs that + # previously returned UNDEF. This is a workaround for that issue. + # https://github.com/openssl/openssl/commit/908ba3ed9adbb3df90f76 + buf = _lib.OBJ_nid2sn(nid) + if buf != _ffi.NULL: + return _ffi.string(buf) + else: + return b"UNDEF" + + def get_data(self) -> bytes: + """ + Returns the data of the X509 extension, encoded as ASN.1. + + :return: The ASN.1 encoded data of this X509 extension. + :rtype: :py:data:`bytes` + + .. versionadded:: 0.12 + """ + octet_result = _lib.X509_EXTENSION_get_data(self._extension) + string_result = _ffi.cast("ASN1_STRING*", octet_result) + char_result = _lib.ASN1_STRING_get0_data(string_result) + result_length = _lib.ASN1_STRING_length(string_result) + return _ffi.buffer(char_result, result_length)[:] + + +@deprecated( + "CSR support in pyOpenSSL is deprecated. You should use the APIs " + "in cryptography." +) +class X509Req: + """ + An X.509 certificate signing requests. + + .. deprecated:: 24.2.0 + Use `cryptography.x509.CertificateSigningRequest` instead. + """ + + def __init__(self) -> None: + req = _lib.X509_REQ_new() + self._req = _ffi.gc(req, _lib.X509_REQ_free) + # Default to version 0. + self.set_version(0) + + def to_cryptography(self) -> x509.CertificateSigningRequest: + """ + Export as a ``cryptography`` certificate signing request. + + :rtype: ``cryptography.x509.CertificateSigningRequest`` + + .. versionadded:: 17.1.0 + """ + from cryptography.x509 import load_der_x509_csr + + der = _dump_certificate_request_internal(FILETYPE_ASN1, self) + + return load_der_x509_csr(der) + + @classmethod + def from_cryptography( + cls, crypto_req: x509.CertificateSigningRequest + ) -> X509Req: + """ + Construct based on a ``cryptography`` *crypto_req*. + + :param crypto_req: A ``cryptography`` X.509 certificate signing request + :type crypto_req: ``cryptography.x509.CertificateSigningRequest`` + + :rtype: X509Req + + .. versionadded:: 17.1.0 + """ + if not isinstance(crypto_req, x509.CertificateSigningRequest): + raise TypeError("Must be a certificate signing request") + + from cryptography.hazmat.primitives.serialization import Encoding + + der = crypto_req.public_bytes(Encoding.DER) + return _load_certificate_request_internal(FILETYPE_ASN1, der) + + def set_pubkey(self, pkey: PKey) -> None: + """ + Set the public key of the certificate signing request. + + :param pkey: The public key to use. + :type pkey: :py:class:`PKey` + + :return: ``None`` + """ + set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) + _openssl_assert(set_result == 1) + + def get_pubkey(self) -> PKey: + """ + Get the public key of the certificate signing request. + + :return: The public key. + :rtype: :py:class:`PKey` + """ + pkey = PKey.__new__(PKey) + pkey._pkey = _lib.X509_REQ_get_pubkey(self._req) + _openssl_assert(pkey._pkey != _ffi.NULL) + pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) + pkey._only_public = True + return pkey + + def set_version(self, version: int) -> None: + """ + Set the version subfield (RFC 2986, section 4.1) of the certificate + request. + + :param int version: The version number. + :return: ``None`` + """ + if not isinstance(version, int): + raise TypeError("version must be an int") + if version != 0: + raise ValueError( + "Invalid version. The only valid version for X509Req is 0." + ) + set_result = _lib.X509_REQ_set_version(self._req, version) + _openssl_assert(set_result == 1) + + def get_version(self) -> int: + """ + Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate + request. + + :return: The value of the version subfield. + :rtype: :py:class:`int` + """ + return _lib.X509_REQ_get_version(self._req) + + def get_subject(self) -> X509Name: + """ + Return the subject of this certificate signing request. + + This creates a new :class:`X509Name` that wraps the underlying subject + name field on the certificate signing request. Modifying it will modify + the underlying signing request, and will have the effect of modifying + any other :class:`X509Name` that refers to this subject. + + :return: The subject of this certificate signing request. + :rtype: :class:`X509Name` + """ + name = X509Name.__new__(X509Name) + name._name = _lib.X509_REQ_get_subject_name(self._req) + _openssl_assert(name._name != _ffi.NULL) + + # The name is owned by the X509Req structure. As long as the X509Name + # Python object is alive, keep the X509Req Python object alive. + name._owner = self + + return name + + def add_extensions(self, extensions: Iterable[X509Extension]) -> None: + """ + Add extensions to the certificate signing request. + + :param extensions: The X.509 extensions to add. + :type extensions: iterable of :py:class:`X509Extension` + :return: ``None`` + """ + warnings.warn( + ( + "This API is deprecated and will be removed in a future " + "version of pyOpenSSL. You should use pyca/cryptography's " + "X.509 APIs instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + stack = _lib.sk_X509_EXTENSION_new_null() + _openssl_assert(stack != _ffi.NULL) + + stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free) + + for ext in extensions: + if not isinstance(ext, X509Extension): + raise ValueError("One of the elements is not an X509Extension") + + # TODO push can fail (here and elsewhere) + _lib.sk_X509_EXTENSION_push(stack, ext._extension) + + add_result = _lib.X509_REQ_add_extensions(self._req, stack) + _openssl_assert(add_result == 1) + + def get_extensions(self) -> list[X509Extension]: + """ + Get X.509 extensions in the certificate signing request. + + :return: The X.509 extensions in this request. + :rtype: :py:class:`list` of :py:class:`X509Extension` objects. + + .. versionadded:: 0.15 + """ + warnings.warn( + ( + "This API is deprecated and will be removed in a future " + "version of pyOpenSSL. You should use pyca/cryptography's " + "X.509 APIs instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + exts = [] + native_exts_obj = _lib.X509_REQ_get_extensions(self._req) + native_exts_obj = _ffi.gc( + native_exts_obj, + lambda x: _lib.sk_X509_EXTENSION_pop_free( + x, + _ffi.addressof(_lib._original_lib, "X509_EXTENSION_free"), + ), + ) + + for i in range(_lib.sk_X509_EXTENSION_num(native_exts_obj)): + ext = X509Extension.__new__(X509Extension) + extension = _lib.X509_EXTENSION_dup( + _lib.sk_X509_EXTENSION_value(native_exts_obj, i) + ) + ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) + exts.append(ext) + return exts + + def sign(self, pkey: PKey, digest: str) -> None: + """ + Sign the certificate signing request with this key and digest type. + + :param pkey: The key pair to sign with. + :type pkey: :py:class:`PKey` + :param digest: The name of the message digest to use for the signature, + e.g. :py:data:`"sha256"`. + :type digest: :py:class:`str` + :return: ``None`` + """ + if pkey._only_public: + raise ValueError("Key has only public part") + + if not pkey._initialized: + raise ValueError("Key is uninitialized") + + digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) + if digest_obj == _ffi.NULL: + raise ValueError("No such digest method") + + sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj) + _openssl_assert(sign_result > 0) + + def verify(self, pkey: PKey) -> bool: + """ + Verifies the signature on this certificate signing request. + + :param PKey key: A public key. + + :return: ``True`` if the signature is correct. + :rtype: bool + + :raises OpenSSL.crypto.Error: If the signature is invalid or there is a + problem verifying the signature. + """ + if not isinstance(pkey, PKey): + raise TypeError("pkey must be a PKey instance") + + result = _lib.X509_REQ_verify(self._req, pkey._pkey) + if result <= 0: + _raise_current_error() + + return result + + +class X509: + """ + An X.509 certificate. + """ + + def __init__(self) -> None: + x509 = _lib.X509_new() + _openssl_assert(x509 != _ffi.NULL) + self._x509 = _ffi.gc(x509, _lib.X509_free) + + self._issuer_invalidator = _X509NameInvalidator() + self._subject_invalidator = _X509NameInvalidator() + + @classmethod + def _from_raw_x509_ptr(cls, x509: Any) -> X509: + cert = cls.__new__(cls) + cert._x509 = _ffi.gc(x509, _lib.X509_free) + cert._issuer_invalidator = _X509NameInvalidator() + cert._subject_invalidator = _X509NameInvalidator() + return cert + + def to_cryptography(self) -> x509.Certificate: + """ + Export as a ``cryptography`` certificate. + + :rtype: ``cryptography.x509.Certificate`` + + .. versionadded:: 17.1.0 + """ + from cryptography.x509 import load_der_x509_certificate + + der = dump_certificate(FILETYPE_ASN1, self) + return load_der_x509_certificate(der) + + @classmethod + def from_cryptography(cls, crypto_cert: x509.Certificate) -> X509: + """ + Construct based on a ``cryptography`` *crypto_cert*. + + :param crypto_key: A ``cryptography`` X.509 certificate. + :type crypto_key: ``cryptography.x509.Certificate`` + + :rtype: X509 + + .. versionadded:: 17.1.0 + """ + if not isinstance(crypto_cert, x509.Certificate): + raise TypeError("Must be a certificate") + + from cryptography.hazmat.primitives.serialization import Encoding + + der = crypto_cert.public_bytes(Encoding.DER) + return load_certificate(FILETYPE_ASN1, der) + + def set_version(self, version: int) -> None: + """ + Set the version number of the certificate. Note that the + version value is zero-based, eg. a value of 0 is V1. + + :param version: The version number of the certificate. + :type version: :py:class:`int` + + :return: ``None`` + """ + if not isinstance(version, int): + raise TypeError("version must be an integer") + + _openssl_assert(_lib.X509_set_version(self._x509, version) == 1) + + def get_version(self) -> int: + """ + Return the version number of the certificate. + + :return: The version number of the certificate. + :rtype: :py:class:`int` + """ + return _lib.X509_get_version(self._x509) + + def get_pubkey(self) -> PKey: + """ + Get the public key of the certificate. + + :return: The public key. + :rtype: :py:class:`PKey` + """ + pkey = PKey.__new__(PKey) + pkey._pkey = _lib.X509_get_pubkey(self._x509) + if pkey._pkey == _ffi.NULL: + _raise_current_error() + pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) + pkey._only_public = True + return pkey + + def set_pubkey(self, pkey: PKey) -> None: + """ + Set the public key of the certificate. + + :param pkey: The public key. + :type pkey: :py:class:`PKey` + + :return: :py:data:`None` + """ + if not isinstance(pkey, PKey): + raise TypeError("pkey must be a PKey instance") + + set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey) + _openssl_assert(set_result == 1) + + def sign(self, pkey: PKey, digest: str) -> None: + """ + Sign the certificate with this key and digest type. + + :param pkey: The key to sign with. + :type pkey: :py:class:`PKey` + + :param digest: The name of the message digest to use. + :type digest: :py:class:`str` + + :return: :py:data:`None` + """ + if not isinstance(pkey, PKey): + raise TypeError("pkey must be a PKey instance") + + if pkey._only_public: + raise ValueError("Key only has public part") + + if not pkey._initialized: + raise ValueError("Key is uninitialized") + + evp_md = _lib.EVP_get_digestbyname(_byte_string(digest)) + if evp_md == _ffi.NULL: + raise ValueError("No such digest method") + + sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md) + _openssl_assert(sign_result > 0) + + def get_signature_algorithm(self) -> bytes: + """ + Return the signature algorithm used in the certificate. + + :return: The name of the algorithm. + :rtype: :py:class:`bytes` + + :raises ValueError: If the signature algorithm is undefined. + + .. versionadded:: 0.13 + """ + sig_alg = _lib.X509_get0_tbs_sigalg(self._x509) + alg = _ffi.new("ASN1_OBJECT **") + _lib.X509_ALGOR_get0(alg, _ffi.NULL, _ffi.NULL, sig_alg) + nid = _lib.OBJ_obj2nid(alg[0]) + if nid == _lib.NID_undef: + raise ValueError("Undefined signature algorithm") + return _ffi.string(_lib.OBJ_nid2ln(nid)) + + def digest(self, digest_name: str) -> bytes: + """ + Return the digest of the X509 object. + + :param digest_name: The name of the digest algorithm to use. + :type digest_name: :py:class:`str` + + :return: The digest of the object, formatted as + :py:const:`b":"`-delimited hex pairs. + :rtype: :py:class:`bytes` + """ + digest = _lib.EVP_get_digestbyname(_byte_string(digest_name)) + if digest == _ffi.NULL: + raise ValueError("No such digest method") + + result_buffer = _ffi.new("unsigned char[]", _lib.EVP_MAX_MD_SIZE) + result_length = _ffi.new("unsigned int[]", 1) + result_length[0] = len(result_buffer) + + digest_result = _lib.X509_digest( + self._x509, digest, result_buffer, result_length + ) + _openssl_assert(digest_result == 1) + + return b":".join( + [ + b16encode(ch).upper() + for ch in _ffi.buffer(result_buffer, result_length[0]) + ] + ) + + def subject_name_hash(self) -> int: + """ + Return the hash of the X509 subject. + + :return: The hash of the subject. + :rtype: :py:class:`int` + """ + return _lib.X509_subject_name_hash(self._x509) + + def set_serial_number(self, serial: int) -> None: + """ + Set the serial number of the certificate. + + :param serial: The new serial number. + :type serial: :py:class:`int` + + :return: :py:data`None` + """ + if not isinstance(serial, int): + raise TypeError("serial must be an integer") + + hex_serial = hex(serial)[2:] + hex_serial_bytes = hex_serial.encode("ascii") + + bignum_serial = _ffi.new("BIGNUM**") + + # BN_hex2bn stores the result in &bignum. + result = _lib.BN_hex2bn(bignum_serial, hex_serial_bytes) + _openssl_assert(result != _ffi.NULL) + + asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL) + _lib.BN_free(bignum_serial[0]) + _openssl_assert(asn1_serial != _ffi.NULL) + asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free) + set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial) + _openssl_assert(set_result == 1) + + def get_serial_number(self) -> int: + """ + Return the serial number of this certificate. + + :return: The serial number. + :rtype: int + """ + asn1_serial = _lib.X509_get_serialNumber(self._x509) + bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) + try: + hex_serial = _lib.BN_bn2hex(bignum_serial) + try: + hexstring_serial = _ffi.string(hex_serial) + serial = int(hexstring_serial, 16) + return serial + finally: + _lib.OPENSSL_free(hex_serial) + finally: + _lib.BN_free(bignum_serial) + + def gmtime_adj_notAfter(self, amount: int) -> None: + """ + Adjust the time stamp on which the certificate stops being valid. + + :param int amount: The number of seconds by which to adjust the + timestamp. + :return: ``None`` + """ + if not isinstance(amount, int): + raise TypeError("amount must be an integer") + + notAfter = _lib.X509_getm_notAfter(self._x509) + _lib.X509_gmtime_adj(notAfter, amount) + + def gmtime_adj_notBefore(self, amount: int) -> None: + """ + Adjust the timestamp on which the certificate starts being valid. + + :param amount: The number of seconds by which to adjust the timestamp. + :return: ``None`` + """ + if not isinstance(amount, int): + raise TypeError("amount must be an integer") + + notBefore = _lib.X509_getm_notBefore(self._x509) + _lib.X509_gmtime_adj(notBefore, amount) + + def has_expired(self) -> bool: + """ + Check whether the certificate has expired. + + :return: ``True`` if the certificate has expired, ``False`` otherwise. + :rtype: bool + """ + time_bytes = self.get_notAfter() + if time_bytes is None: + raise ValueError("Unable to determine notAfter") + time_string = time_bytes.decode("utf-8") + not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + + UTC = datetime.timezone.utc + utcnow = datetime.datetime.now(UTC).replace(tzinfo=None) + return not_after < utcnow + + def _get_boundary_time(self, which: Any) -> bytes | None: + return _get_asn1_time(which(self._x509)) + + def get_notBefore(self) -> bytes | None: + """ + Get the timestamp at which the certificate starts being valid. + + The timestamp is formatted as an ASN.1 TIME:: + + YYYYMMDDhhmmssZ + + :return: A timestamp string, or ``None`` if there is none. + :rtype: bytes or NoneType + """ + return self._get_boundary_time(_lib.X509_getm_notBefore) + + def _set_boundary_time( + self, which: Callable[..., Any], when: bytes + ) -> None: + return _set_asn1_time(which(self._x509), when) + + def set_notBefore(self, when: bytes) -> None: + """ + Set the timestamp at which the certificate starts being valid. + + The timestamp is formatted as an ASN.1 TIME:: + + YYYYMMDDhhmmssZ + + :param bytes when: A timestamp string. + :return: ``None`` + """ + return self._set_boundary_time(_lib.X509_getm_notBefore, when) + + def get_notAfter(self) -> bytes | None: + """ + Get the timestamp at which the certificate stops being valid. + + The timestamp is formatted as an ASN.1 TIME:: + + YYYYMMDDhhmmssZ + + :return: A timestamp string, or ``None`` if there is none. + :rtype: bytes or NoneType + """ + return self._get_boundary_time(_lib.X509_getm_notAfter) + + def set_notAfter(self, when: bytes) -> None: + """ + Set the timestamp at which the certificate stops being valid. + + The timestamp is formatted as an ASN.1 TIME:: + + YYYYMMDDhhmmssZ + + :param bytes when: A timestamp string. + :return: ``None`` + """ + return self._set_boundary_time(_lib.X509_getm_notAfter, when) + + def _get_name(self, which: Any) -> X509Name: + name = X509Name.__new__(X509Name) + name._name = which(self._x509) + _openssl_assert(name._name != _ffi.NULL) + + # The name is owned by the X509 structure. As long as the X509Name + # Python object is alive, keep the X509 Python object alive. + name._owner = self + + return name + + def _set_name(self, which: Any, name: X509Name) -> None: + if not isinstance(name, X509Name): + raise TypeError("name must be an X509Name") + set_result = which(self._x509, name._name) + _openssl_assert(set_result == 1) + + def get_issuer(self) -> X509Name: + """ + Return the issuer of this certificate. + + This creates a new :class:`X509Name` that wraps the underlying issuer + name field on the certificate. Modifying it will modify the underlying + certificate, and will have the effect of modifying any other + :class:`X509Name` that refers to this issuer. + + :return: The issuer of this certificate. + :rtype: :class:`X509Name` + """ + name = self._get_name(_lib.X509_get_issuer_name) + self._issuer_invalidator.add(name) + return name + + def set_issuer(self, issuer: X509Name) -> None: + """ + Set the issuer of this certificate. + + :param issuer: The issuer. + :type issuer: :py:class:`X509Name` + + :return: ``None`` + """ + self._set_name(_lib.X509_set_issuer_name, issuer) + self._issuer_invalidator.clear() + + def get_subject(self) -> X509Name: + """ + Return the subject of this certificate. + + This creates a new :class:`X509Name` that wraps the underlying subject + name field on the certificate. Modifying it will modify the underlying + certificate, and will have the effect of modifying any other + :class:`X509Name` that refers to this subject. + + :return: The subject of this certificate. + :rtype: :class:`X509Name` + """ + name = self._get_name(_lib.X509_get_subject_name) + self._subject_invalidator.add(name) + return name + + def set_subject(self, subject: X509Name) -> None: + """ + Set the subject of this certificate. + + :param subject: The subject. + :type subject: :py:class:`X509Name` + + :return: ``None`` + """ + self._set_name(_lib.X509_set_subject_name, subject) + self._subject_invalidator.clear() + + def get_extension_count(self) -> int: + """ + Get the number of extensions on this certificate. + + :return: The number of extensions. + :rtype: :py:class:`int` + + .. versionadded:: 0.12 + """ + return _lib.X509_get_ext_count(self._x509) + + def add_extensions(self, extensions: Iterable[X509Extension]) -> None: + """ + Add extensions to the certificate. + + :param extensions: The extensions to add. + :type extensions: An iterable of :py:class:`X509Extension` objects. + :return: ``None`` + """ + warnings.warn( + ( + "This API is deprecated and will be removed in a future " + "version of pyOpenSSL. You should use pyca/cryptography's " + "X.509 APIs instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + for ext in extensions: + if not isinstance(ext, X509Extension): + raise ValueError("One of the elements is not an X509Extension") + + add_result = _lib.X509_add_ext(self._x509, ext._extension, -1) + _openssl_assert(add_result == 1) + + def get_extension(self, index: int) -> X509Extension: + """ + Get a specific extension of the certificate by index. + + Extensions on a certificate are kept in order. The index + parameter selects which extension will be returned. + + :param int index: The index of the extension to retrieve. + :return: The extension at the specified index. + :rtype: :py:class:`X509Extension` + :raises IndexError: If the extension index was out of bounds. + + .. versionadded:: 0.12 + """ + warnings.warn( + ( + "This API is deprecated and will be removed in a future " + "version of pyOpenSSL. You should use pyca/cryptography's " + "X.509 APIs instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + ext = X509Extension.__new__(X509Extension) + ext._extension = _lib.X509_get_ext(self._x509, index) + if ext._extension == _ffi.NULL: + raise IndexError("extension index out of bounds") + + extension = _lib.X509_EXTENSION_dup(ext._extension) + ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) + return ext + + +class X509StoreFlags: + """ + Flags for X509 verification, used to change the behavior of + :class:`X509Store`. + + See `OpenSSL Verification Flags`_ for details. + + .. _OpenSSL Verification Flags: + https://www.openssl.org/docs/manmaster/man3/X509_VERIFY_PARAM_set_flags.html + """ + + CRL_CHECK: int = _lib.X509_V_FLAG_CRL_CHECK + CRL_CHECK_ALL: int = _lib.X509_V_FLAG_CRL_CHECK_ALL + IGNORE_CRITICAL: int = _lib.X509_V_FLAG_IGNORE_CRITICAL + X509_STRICT: int = _lib.X509_V_FLAG_X509_STRICT + ALLOW_PROXY_CERTS: int = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS + POLICY_CHECK: int = _lib.X509_V_FLAG_POLICY_CHECK + EXPLICIT_POLICY: int = _lib.X509_V_FLAG_EXPLICIT_POLICY + INHIBIT_MAP: int = _lib.X509_V_FLAG_INHIBIT_MAP + CHECK_SS_SIGNATURE: int = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE + PARTIAL_CHAIN: int = _lib.X509_V_FLAG_PARTIAL_CHAIN + + +class X509Store: + """ + An X.509 store. + + An X.509 store is used to describe a context in which to verify a + certificate. A description of a context may include a set of certificates + to trust, a set of certificate revocation lists, verification flags and + more. + + An X.509 store, being only a description, cannot be used by itself to + verify a certificate. To carry out the actual verification process, see + :class:`X509StoreContext`. + """ + + def __init__(self) -> None: + store = _lib.X509_STORE_new() + self._store = _ffi.gc(store, _lib.X509_STORE_free) + + def add_cert(self, cert: X509) -> None: + """ + Adds a trusted certificate to this store. + + Adding a certificate with this method adds this certificate as a + *trusted* certificate. + + :param X509 cert: The certificate to add to this store. + + :raises TypeError: If the certificate is not an :class:`X509`. + + :raises OpenSSL.crypto.Error: If OpenSSL was unhappy with your + certificate. + + :return: ``None`` if the certificate was added successfully. + """ + if not isinstance(cert, X509): + raise TypeError() + + res = _lib.X509_STORE_add_cert(self._store, cert._x509) + _openssl_assert(res == 1) + + def add_crl(self, crl: x509.CertificateRevocationList) -> None: + """ + Add a certificate revocation list to this store. + + The certificate revocation lists added to a store will only be used if + the associated flags are configured to check certificate revocation + lists. + + .. versionadded:: 16.1.0 + + :param crl: The certificate revocation list to add to this store. + :type crl: ``cryptography.x509.CertificateRevocationList`` + :return: ``None`` if the certificate revocation list was added + successfully. + """ + if isinstance(crl, x509.CertificateRevocationList): + from cryptography.hazmat.primitives.serialization import Encoding + + bio = _new_mem_buf(crl.public_bytes(Encoding.DER)) + openssl_crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) + _openssl_assert(openssl_crl != _ffi.NULL) + crl = _ffi.gc(openssl_crl, _lib.X509_CRL_free) + else: + raise TypeError( + "CRL must be of type " + "cryptography.x509.CertificateRevocationList" + ) + + _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl) != 0) + + def set_flags(self, flags: int) -> None: + """ + Set verification flags to this store. + + Verification flags can be combined by oring them together. + + .. note:: + + Setting a verification flag sometimes requires clients to add + additional information to the store, otherwise a suitable error will + be raised. + + For example, in setting flags to enable CRL checking a + suitable CRL must be added to the store otherwise an error will be + raised. + + .. versionadded:: 16.1.0 + + :param int flags: The verification flags to set on this store. + See :class:`X509StoreFlags` for available constants. + :return: ``None`` if the verification flags were successfully set. + """ + _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) + + def set_time(self, vfy_time: datetime.datetime) -> None: + """ + Set the time against which the certificates are verified. + + Normally the current time is used. + + .. note:: + + For example, you can determine if a certificate was valid at a given + time. + + .. versionadded:: 17.0.0 + + :param datetime vfy_time: The verification time to set on this store. + :return: ``None`` if the verification time was successfully set. + """ + param = _lib.X509_VERIFY_PARAM_new() + param = _ffi.gc(param, _lib.X509_VERIFY_PARAM_free) + + _lib.X509_VERIFY_PARAM_set_time( + param, calendar.timegm(vfy_time.timetuple()) + ) + _openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0) + + def load_locations( + self, + cafile: StrOrBytesPath | None, + capath: StrOrBytesPath | None = None, + ) -> None: + """ + Let X509Store know where we can find trusted certificates for the + certificate chain. Note that the certificates have to be in PEM + format. + + If *capath* is passed, it must be a directory prepared using the + ``c_rehash`` tool included with OpenSSL. Either, but not both, of + *cafile* or *capath* may be ``None``. + + .. note:: + + Both *cafile* and *capath* may be set simultaneously. + + Call this method multiple times to add more than one location. + For example, CA certificates, and certificate revocation list bundles + may be passed in *cafile* in subsequent calls to this method. + + .. versionadded:: 20.0 + + :param cafile: In which file we can find the certificates (``bytes`` or + ``unicode``). + :param capath: In which directory we can find the certificates + (``bytes`` or ``unicode``). + + :return: ``None`` if the locations were set successfully. + + :raises OpenSSL.crypto.Error: If both *cafile* and *capath* is ``None`` + or the locations could not be set for any reason. + + """ + if cafile is None: + cafile = _ffi.NULL + else: + cafile = _path_bytes(cafile) + + if capath is None: + capath = _ffi.NULL + else: + capath = _path_bytes(capath) + + load_result = _lib.X509_STORE_load_locations( + self._store, cafile, capath + ) + if not load_result: + _raise_current_error() + + +class X509StoreContextError(Exception): + """ + An exception raised when an error occurred while verifying a certificate + using `OpenSSL.X509StoreContext.verify_certificate`. + + :ivar certificate: The certificate which caused verificate failure. + :type certificate: :class:`X509` + """ + + def __init__( + self, message: str, errors: list[Any], certificate: X509 + ) -> None: + super().__init__(message) + self.errors = errors + self.certificate = certificate + + +class X509StoreContext: + """ + An X.509 store context. + + An X.509 store context is used to carry out the actual verification process + of a certificate in a described context. For describing such a context, see + :class:`X509Store`. + + :param X509Store store: The certificates which will be trusted for the + purposes of any verifications. + :param X509 certificate: The certificate to be verified. + :param chain: List of untrusted certificates that may be used for building + the certificate chain. May be ``None``. + :type chain: :class:`list` of :class:`X509` + """ + + def __init__( + self, + store: X509Store, + certificate: X509, + chain: Sequence[X509] | None = None, + ) -> None: + self._store = store + self._cert = certificate + self._chain = self._build_certificate_stack(chain) + + @staticmethod + def _build_certificate_stack( + certificates: Sequence[X509] | None, + ) -> None: + def cleanup(s: Any) -> None: + # Equivalent to sk_X509_pop_free, but we don't + # currently have a CFFI binding for that available + for i in range(_lib.sk_X509_num(s)): + x = _lib.sk_X509_value(s, i) + _lib.X509_free(x) + _lib.sk_X509_free(s) + + if certificates is None or len(certificates) == 0: + return _ffi.NULL + + stack = _lib.sk_X509_new_null() + _openssl_assert(stack != _ffi.NULL) + stack = _ffi.gc(stack, cleanup) + + for cert in certificates: + if not isinstance(cert, X509): + raise TypeError("One of the elements is not an X509 instance") + + _openssl_assert(_lib.X509_up_ref(cert._x509) > 0) + if _lib.sk_X509_push(stack, cert._x509) <= 0: + _lib.X509_free(cert._x509) + _raise_current_error() + + return stack + + @staticmethod + def _exception_from_context(store_ctx: Any) -> X509StoreContextError: + """ + Convert an OpenSSL native context error failure into a Python + exception. + + When a call to native OpenSSL X509_verify_cert fails, additional + information about the failure can be obtained from the store context. + """ + message = _ffi.string( + _lib.X509_verify_cert_error_string( + _lib.X509_STORE_CTX_get_error(store_ctx) + ) + ).decode("utf-8") + errors = [ + _lib.X509_STORE_CTX_get_error(store_ctx), + _lib.X509_STORE_CTX_get_error_depth(store_ctx), + message, + ] + # A context error should always be associated with a certificate, so we + # expect this call to never return :class:`None`. + _x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) + _cert = _lib.X509_dup(_x509) + pycert = X509._from_raw_x509_ptr(_cert) + return X509StoreContextError(message, errors, pycert) + + def _verify_certificate(self) -> Any: + """ + Verifies the certificate and runs an X509_STORE_CTX containing the + results. + + :raises X509StoreContextError: If an error occurred when validating a + certificate in the context. Sets ``certificate`` attribute to + indicate which certificate caused the error. + """ + store_ctx = _lib.X509_STORE_CTX_new() + _openssl_assert(store_ctx != _ffi.NULL) + store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free) + + ret = _lib.X509_STORE_CTX_init( + store_ctx, self._store._store, self._cert._x509, self._chain + ) + _openssl_assert(ret == 1) + + ret = _lib.X509_verify_cert(store_ctx) + if ret <= 0: + raise self._exception_from_context(store_ctx) + + return store_ctx + + def set_store(self, store: X509Store) -> None: + """ + Set the context's X.509 store. + + .. versionadded:: 0.15 + + :param X509Store store: The store description which will be used for + the purposes of any *future* verifications. + """ + self._store = store + + def verify_certificate(self) -> None: + """ + Verify a certificate in a context. + + .. versionadded:: 0.15 + + :raises X509StoreContextError: If an error occurred when validating a + certificate in the context. Sets ``certificate`` attribute to + indicate which certificate caused the error. + """ + self._verify_certificate() + + def get_verified_chain(self) -> list[X509]: + """ + Verify a certificate in a context and return the complete validated + chain. + + :raises X509StoreContextError: If an error occurred when validating a + certificate in the context. Sets ``certificate`` attribute to + indicate which certificate caused the error. + + .. versionadded:: 20.0 + """ + store_ctx = self._verify_certificate() + + # Note: X509_STORE_CTX_get1_chain returns a deep copy of the chain. + cert_stack = _lib.X509_STORE_CTX_get1_chain(store_ctx) + _openssl_assert(cert_stack != _ffi.NULL) + + result = [] + for i in range(_lib.sk_X509_num(cert_stack)): + cert = _lib.sk_X509_value(cert_stack, i) + _openssl_assert(cert != _ffi.NULL) + pycert = X509._from_raw_x509_ptr(cert) + result.append(pycert) + + # Free the stack but not the members which are freed by the X509 class. + _lib.sk_X509_free(cert_stack) + return result + + +def load_certificate(type: int, buffer: bytes) -> X509: + """ + Load a certificate (X509) from the string *buffer* encoded with the + type *type*. + + :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) + + :param bytes buffer: The buffer the certificate is stored in + + :return: The X509 object + """ + if isinstance(buffer, str): + buffer = buffer.encode("ascii") + + bio = _new_mem_buf(buffer) + + if type == FILETYPE_PEM: + x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) + elif type == FILETYPE_ASN1: + x509 = _lib.d2i_X509_bio(bio, _ffi.NULL) + else: + raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") + + if x509 == _ffi.NULL: + _raise_current_error() + + return X509._from_raw_x509_ptr(x509) + + +def dump_certificate(type: int, cert: X509) -> bytes: + """ + Dump the certificate *cert* into a buffer string encoded with the type + *type*. + + :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or + FILETYPE_TEXT) + :param cert: The certificate to dump + :return: The buffer with the dumped certificate in + """ + bio = _new_mem_buf() + + if type == FILETYPE_PEM: + result_code = _lib.PEM_write_bio_X509(bio, cert._x509) + elif type == FILETYPE_ASN1: + result_code = _lib.i2d_X509_bio(bio, cert._x509) + elif type == FILETYPE_TEXT: + result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0) + else: + raise ValueError( + "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " + "FILETYPE_TEXT" + ) + + _openssl_assert(result_code == 1) + return _bio_to_string(bio) + + +def dump_publickey(type: int, pkey: PKey) -> bytes: + """ + Dump a public key to a buffer. + + :param type: The file type (one of :data:`FILETYPE_PEM` or + :data:`FILETYPE_ASN1`). + :param PKey pkey: The public key to dump + :return: The buffer with the dumped key in it. + :rtype: bytes + """ + bio = _new_mem_buf() + if type == FILETYPE_PEM: + write_bio = _lib.PEM_write_bio_PUBKEY + elif type == FILETYPE_ASN1: + write_bio = _lib.i2d_PUBKEY_bio + else: + raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") + + result_code = write_bio(bio, pkey._pkey) + if result_code != 1: # pragma: no cover + _raise_current_error() + + return _bio_to_string(bio) + + +def dump_privatekey( + type: int, + pkey: PKey, + cipher: str | None = None, + passphrase: PassphraseCallableT | None = None, +) -> bytes: + """ + Dump the private key *pkey* into a buffer string encoded with the type + *type*. Optionally (if *type* is :const:`FILETYPE_PEM`) encrypting it + using *cipher* and *passphrase*. + + :param type: The file type (one of :const:`FILETYPE_PEM`, + :const:`FILETYPE_ASN1`, or :const:`FILETYPE_TEXT`) + :param PKey pkey: The PKey to dump + :param cipher: (optional) if encrypted PEM format, the cipher to use + :param passphrase: (optional) if encrypted PEM format, this can be either + the passphrase to use, or a callback for providing the passphrase. + + :return: The buffer with the dumped key in + :rtype: bytes + """ + bio = _new_mem_buf() + + if not isinstance(pkey, PKey): + raise TypeError("pkey must be a PKey") + + if cipher is not None: + if passphrase is None: + raise TypeError( + "if a value is given for cipher " + "one must also be given for passphrase" + ) + cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher)) + if cipher_obj == _ffi.NULL: + raise ValueError("Invalid cipher name") + else: + cipher_obj = _ffi.NULL + + helper = _PassphraseHelper(type, passphrase) + if type == FILETYPE_PEM: + result_code = _lib.PEM_write_bio_PrivateKey( + bio, + pkey._pkey, + cipher_obj, + _ffi.NULL, + 0, + helper.callback, + helper.callback_args, + ) + helper.raise_if_problem() + elif type == FILETYPE_ASN1: + result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey) + elif type == FILETYPE_TEXT: + if _lib.EVP_PKEY_id(pkey._pkey) != _lib.EVP_PKEY_RSA: + raise TypeError("Only RSA keys are supported for FILETYPE_TEXT") + + rsa = _ffi.gc(_lib.EVP_PKEY_get1_RSA(pkey._pkey), _lib.RSA_free) + result_code = _lib.RSA_print(bio, rsa, 0) + else: + raise ValueError( + "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " + "FILETYPE_TEXT" + ) + + _openssl_assert(result_code != 0) + + return _bio_to_string(bio) + + +class _PassphraseHelper: + def __init__( + self, + type: int, + passphrase: PassphraseCallableT | None, + more_args: bool = False, + truncate: bool = False, + ) -> None: + if type != FILETYPE_PEM and passphrase is not None: + raise ValueError( + "only FILETYPE_PEM key format supports encryption" + ) + self._passphrase = passphrase + self._more_args = more_args + self._truncate = truncate + self._problems: list[Exception] = [] + + @property + def callback(self) -> Any: + if self._passphrase is None: + return _ffi.NULL + elif isinstance(self._passphrase, bytes) or callable(self._passphrase): + return _ffi.callback("pem_password_cb", self._read_passphrase) + else: + raise TypeError( + "Last argument must be a byte string or a callable." + ) + + @property + def callback_args(self) -> Any: + if self._passphrase is None: + return _ffi.NULL + elif isinstance(self._passphrase, bytes) or callable(self._passphrase): + return _ffi.NULL + else: + raise TypeError( + "Last argument must be a byte string or a callable." + ) + + def raise_if_problem(self, exceptionType: type[Exception] = Error) -> None: + if self._problems: + # Flush the OpenSSL error queue + try: + _exception_from_error_queue(exceptionType) + except exceptionType: + pass + + raise self._problems.pop(0) + + def _read_passphrase( + self, buf: Any, size: int, rwflag: Any, userdata: Any + ) -> int: + try: + if callable(self._passphrase): + if self._more_args: + result = self._passphrase(size, rwflag, userdata) + else: + result = self._passphrase(rwflag) + else: + assert self._passphrase is not None + result = self._passphrase + if not isinstance(result, bytes): + raise ValueError("Bytes expected") + if len(result) > size: + if self._truncate: + result = result[:size] + else: + raise ValueError( + "passphrase returned by callback is too long" + ) + for i in range(len(result)): + buf[i] = result[i : i + 1] + return len(result) + except Exception as e: + self._problems.append(e) + return 0 + + +def load_publickey(type: int, buffer: str | bytes) -> PKey: + """ + Load a public key from a buffer. + + :param type: The file type (one of :data:`FILETYPE_PEM`, + :data:`FILETYPE_ASN1`). + :param buffer: The buffer the key is stored in. + :type buffer: A Python string object, either unicode or bytestring. + :return: The PKey object. + :rtype: :class:`PKey` + """ + if isinstance(buffer, str): + buffer = buffer.encode("ascii") + + bio = _new_mem_buf(buffer) + + if type == FILETYPE_PEM: + evp_pkey = _lib.PEM_read_bio_PUBKEY( + bio, _ffi.NULL, _ffi.NULL, _ffi.NULL + ) + elif type == FILETYPE_ASN1: + evp_pkey = _lib.d2i_PUBKEY_bio(bio, _ffi.NULL) + else: + raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") + + if evp_pkey == _ffi.NULL: + _raise_current_error() + + pkey = PKey.__new__(PKey) + pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) + pkey._only_public = True + return pkey + + +def load_privatekey( + type: int, + buffer: str | bytes, + passphrase: PassphraseCallableT | None = None, +) -> PKey: + """ + Load a private key (PKey) from the string *buffer* encoded with the type + *type*. + + :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) + :param buffer: The buffer the key is stored in + :param passphrase: (optional) if encrypted PEM format, this can be + either the passphrase to use, or a callback for + providing the passphrase. + + :return: The PKey object + """ + if isinstance(buffer, str): + buffer = buffer.encode("ascii") + + bio = _new_mem_buf(buffer) + + helper = _PassphraseHelper(type, passphrase) + if type == FILETYPE_PEM: + evp_pkey = _lib.PEM_read_bio_PrivateKey( + bio, _ffi.NULL, helper.callback, helper.callback_args + ) + helper.raise_if_problem() + elif type == FILETYPE_ASN1: + evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL) + else: + raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") + + if evp_pkey == _ffi.NULL: + _raise_current_error() + + pkey = PKey.__new__(PKey) + pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) + return pkey + + +def dump_certificate_request(type: int, req: X509Req) -> bytes: + """ + Dump the certificate request *req* into a buffer string encoded with the + type *type*. + + :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) + :param req: The certificate request to dump + :return: The buffer with the dumped certificate request in + + + .. deprecated:: 24.2.0 + Use `cryptography.x509.CertificateSigningRequest` instead. + """ + bio = _new_mem_buf() + + if type == FILETYPE_PEM: + result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req) + elif type == FILETYPE_ASN1: + result_code = _lib.i2d_X509_REQ_bio(bio, req._req) + elif type == FILETYPE_TEXT: + result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0) + else: + raise ValueError( + "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " + "FILETYPE_TEXT" + ) + + _openssl_assert(result_code != 0) + + return _bio_to_string(bio) + + +_dump_certificate_request_internal = dump_certificate_request + +utils.deprecated( + dump_certificate_request, + __name__, + ( + "CSR support in pyOpenSSL is deprecated. You should use the APIs " + "in cryptography." + ), + DeprecationWarning, + name="dump_certificate_request", +) + + +def load_certificate_request(type: int, buffer: bytes) -> X509Req: + """ + Load a certificate request (X509Req) from the string *buffer* encoded with + the type *type*. + + :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) + :param buffer: The buffer the certificate request is stored in + :return: The X509Req object + + .. deprecated:: 24.2.0 + Use `cryptography.x509.load_der_x509_csr` or + `cryptography.x509.load_pem_x509_csr` instead. + """ + if isinstance(buffer, str): + buffer = buffer.encode("ascii") + + bio = _new_mem_buf(buffer) + + if type == FILETYPE_PEM: + req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) + elif type == FILETYPE_ASN1: + req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL) + else: + raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") + + _openssl_assert(req != _ffi.NULL) + + x509req = X509Req.__new__(X509Req) + x509req._req = _ffi.gc(req, _lib.X509_REQ_free) + return x509req + + +_load_certificate_request_internal = load_certificate_request + +utils.deprecated( + load_certificate_request, + __name__, + ( + "CSR support in pyOpenSSL is deprecated. You should use the APIs " + "in cryptography." + ), + DeprecationWarning, + name="load_certificate_request", +) diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/debug.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/debug.py new file mode 100644 index 00000000..e0ed3f81 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/debug.py @@ -0,0 +1,40 @@ +import ssl +import sys + +import cffi +import cryptography + +import OpenSSL.SSL + +from . import version + +_env_info = """\ +pyOpenSSL: {pyopenssl} +cryptography: {cryptography} +cffi: {cffi} +cryptography's compiled against OpenSSL: {crypto_openssl_compile} +cryptography's linked OpenSSL: {crypto_openssl_link} +Python's OpenSSL: {python_openssl} +Python executable: {python} +Python version: {python_version} +Platform: {platform} +sys.path: {sys_path}""".format( + pyopenssl=version.__version__, + crypto_openssl_compile=OpenSSL._util.ffi.string( + OpenSSL._util.lib.OPENSSL_VERSION_TEXT, + ).decode("ascii"), + crypto_openssl_link=OpenSSL.SSL.SSLeay_version( + OpenSSL.SSL.SSLEAY_VERSION + ).decode("ascii"), + python_openssl=getattr(ssl, "OPENSSL_VERSION", "n/a"), + cryptography=cryptography.__version__, + cffi=cffi.__version__, + python=sys.executable, + python_version=sys.version, + platform=sys.platform, + sys_path=sys.path, +) + + +if __name__ == "__main__": + print(_env_info) diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/py.typed b/Backend/venv/lib/python3.12/site-packages/OpenSSL/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/rand.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/rand.py new file mode 100644 index 00000000..e57425f3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/rand.py @@ -0,0 +1,50 @@ +""" +PRNG management routines, thin wrappers. +""" + +from __future__ import annotations + +import warnings + +from OpenSSL._util import lib as _lib + +warnings.warn( + "OpenSSL.rand is deprecated - you should use os.urandom instead", + DeprecationWarning, + stacklevel=3, +) + + +def add(buffer: bytes, entropy: int) -> None: + """ + Mix bytes from *string* into the PRNG state. + + The *entropy* argument is (the lower bound of) an estimate of how much + randomness is contained in *string*, measured in bytes. + + For more information, see e.g. :rfc:`1750`. + + This function is only relevant if you are forking Python processes and + need to reseed the CSPRNG after fork. + + :param buffer: Buffer with random data. + :param entropy: The entropy (in bytes) measurement of the buffer. + + :return: :obj:`None` + """ + if not isinstance(buffer, bytes): + raise TypeError("buffer must be a byte string") + + if not isinstance(entropy, int): + raise TypeError("entropy must be an integer") + + _lib.RAND_add(buffer, len(buffer), entropy) + + +def status() -> int: + """ + Check whether the PRNG has been seeded with enough data. + + :return: 1 if the PRNG is seeded enough, 0 otherwise. + """ + return _lib.RAND_status() diff --git a/Backend/venv/lib/python3.12/site-packages/OpenSSL/version.py b/Backend/venv/lib/python3.12/site-packages/OpenSSL/version.py new file mode 100644 index 00000000..c49055e1 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/OpenSSL/version.py @@ -0,0 +1,28 @@ +# Copyright (C) AB Strakt +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +pyOpenSSL - A simple wrapper around the OpenSSL library +""" + +__all__ = [ + "__author__", + "__copyright__", + "__email__", + "__license__", + "__summary__", + "__title__", + "__uri__", + "__version__", +] + +__version__ = "25.3.0" + +__title__ = "pyOpenSSL" +__uri__ = "https://pyopenssl.org/" +__summary__ = "Python wrapper module around the OpenSSL library" +__author__ = "The pyOpenSSL developers" +__email__ = "cryptography-dev@python.org" +__license__ = "Apache License, Version 2.0" +__copyright__ = f"Copyright 2001-2025 {__author__}" diff --git a/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc new file mode 100644 index 00000000..3467ba8f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/RECORD deleted file mode 100644 index 77b014fe..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/RECORD +++ /dev/null @@ -1,173 +0,0 @@ -cryptography-41.0.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -cryptography-41.0.7.dist-info/LICENSE,sha256=Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs,197 -cryptography-41.0.7.dist-info/LICENSE.APACHE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360 -cryptography-41.0.7.dist-info/LICENSE.BSD,sha256=YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs,1532 -cryptography-41.0.7.dist-info/METADATA,sha256=h4C2cL9sbR7ObF6jD7hUT7xOfSvzZBli6AmX-vngctA,5159 -cryptography-41.0.7.dist-info/RECORD,, -cryptography-41.0.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -cryptography-41.0.7.dist-info/WHEEL,sha256=Bnup3_Y_tMShHsCuO2E9NdrjRJkTtSD1dYVt3WSGhpU,112 -cryptography-41.0.7.dist-info/top_level.txt,sha256=KNaT-Sn2K4uxNaEbe6mYdDn3qWDMlp4y-MtWfB73nJc,13 -cryptography/__about__.py,sha256=uPXMbbcptt7EzZ_jllGRx0pVdMn-NBsAM4L74hOv-b0,445 -cryptography/__init__.py,sha256=iVPlBlXWTJyiFeRedxcbMPhyHB34viOM10d72vGnWuE,364 -cryptography/__pycache__/__about__.cpython-312.pyc,, -cryptography/__pycache__/__init__.cpython-312.pyc,, -cryptography/__pycache__/exceptions.cpython-312.pyc,, -cryptography/__pycache__/fernet.cpython-312.pyc,, -cryptography/__pycache__/utils.cpython-312.pyc,, -cryptography/exceptions.py,sha256=EHe7XM2_OtdOM1bZE0ci-4GUhtOlEQ6fQXhK2Igf0qA,1118 -cryptography/fernet.py,sha256=TVZy4Dtkpl7kWIpvuKcNldE95IEjTQ0MfHgRsLdnDSM,6886 -cryptography/hazmat/__init__.py,sha256=5IwrLWrVp0AjEr_4FdWG_V057NSJGY_W4egNNsuct0g,455 -cryptography/hazmat/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/__pycache__/_oid.cpython-312.pyc,, -cryptography/hazmat/_oid.py,sha256=gxhMHKpu9Xsi6uHCGZ_-soYMXj_izOIFaxjUKWbCPeE,14441 -cryptography/hazmat/backends/__init__.py,sha256=O5jvKFQdZnXhKeqJ-HtulaEL9Ni7mr1mDzZY5kHlYhI,361 -cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__init__.py,sha256=p3jmJfnCag9iE5sdMrN6VvVEu55u46xaS_IjoI0SrmA,305 -cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/aead.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/ciphers.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/cmac.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/decode_asn1.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/ec.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/rsa.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/__pycache__/utils.cpython-312.pyc,, -cryptography/hazmat/backends/openssl/aead.py,sha256=s3zXcVQf0COIOuOzI8usebWpznGnyZ7GhnmlJYu7QXA,15967 -cryptography/hazmat/backends/openssl/backend.py,sha256=491FCrjeOG7S9bXskUosirXFP84ntwAQ-U0BxcibtqM,73321 -cryptography/hazmat/backends/openssl/ciphers.py,sha256=lxWrvnufudsDI2bpwNs2c8XLILbAE2j2rMSD1nhnPVg,10358 -cryptography/hazmat/backends/openssl/cmac.py,sha256=pHgQOIRfR4cIDa5ltcKFtgjqPTXbOLyRQmmqv9JlbUk,3035 -cryptography/hazmat/backends/openssl/decode_asn1.py,sha256=kz6gys8wuJhrx4QyU6enYx7UatNHr0LB3TI1jH3oQ54,1148 -cryptography/hazmat/backends/openssl/ec.py,sha256=GKzh3mZKvgsM1jqM88-4XikHHalpV-Efyskclt8yxYg,11474 -cryptography/hazmat/backends/openssl/rsa.py,sha256=P_ak-2zvA6VBt_P0ldzTSCUkcjo2GhYt_HLn8CVvWtE,21825 -cryptography/hazmat/backends/openssl/utils.py,sha256=UoguO26QzwN4lsMAltsIrgAlbi3SOeSrexZs1-QPNu8,2190 -cryptography/hazmat/bindings/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 -cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/bindings/_rust.abi3.so,sha256=qkbrd72TN7vk0ivAz_VE-ZefNyDxCLwLSiAzhgMF8-Q,13787648 -cryptography/hazmat/bindings/_rust/__init__.pyi,sha256=IumK7zP9Ko3HjLLb5hwZiY2rbfmfsuyTZLLcHOMvSdk,981 -cryptography/hazmat/bindings/_rust/_openssl.pyi,sha256=mpNJLuYLbCVrd5i33FBTmWwL_55Dw7JPkSLlSX9Q7oI,230 -cryptography/hazmat/bindings/_rust/asn1.pyi,sha256=9CyI-grOsLQB_hfnhJPoG9dNOdJ7Zg6B0iUpzCowh44,592 -cryptography/hazmat/bindings/_rust/exceptions.pyi,sha256=exXr2xw_0pB1kk93cYbM3MohbzoUkjOms1ZMUi0uQZE,640 -cryptography/hazmat/bindings/_rust/ocsp.pyi,sha256=RzVaLkY0y9L8W8opAL_uVD8bySKxP23pSQtEbLOStXI,905 -cryptography/hazmat/bindings/_rust/openssl/__init__.pyi,sha256=j764U4RRBZbDuOfjQxRqU7rCf74kgM-3AnTIjLdRy3E,970 -cryptography/hazmat/bindings/_rust/openssl/dh.pyi,sha256=0FVY1t5qM9HV_ZKDIcdJI2a72i1fHKyTvYIJb5UnH4M,896 -cryptography/hazmat/bindings/_rust/openssl/dsa.pyi,sha256=43in4PCsm2kz_H7RQFLBKqhDsUmb4yWop6dpYeVDg-4,764 -cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi,sha256=E2GXAgibfRGqKxskH8MfZI8gHFoMJJOTjG7Elg2gOww,629 -cryptography/hazmat/bindings/_rust/openssl/ed448.pyi,sha256=pk_kx5Biq8O53d2joOT-cXuwCrbFPicV7iaqYdeiIAI,603 -cryptography/hazmat/bindings/_rust/openssl/hashes.pyi,sha256=J8HoN0GdtPcjRAfNHr5Elva_nkmQfq63L75_z9dd8Uc,573 -cryptography/hazmat/bindings/_rust/openssl/hmac.pyi,sha256=ZmLJ73pmxcZFC1XosWEiXMRYtvJJor3ZLdCQOJu85Cw,662 -cryptography/hazmat/bindings/_rust/openssl/kdf.pyi,sha256=wPS5c7NLspM2632II0I4iH1RSxZvSRtBOVqmpyQATfk,544 -cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi,sha256=9iogF7Q4i81IkOS-IMXp6HvxFF_3cNy_ucrAjVQnn14,540 -cryptography/hazmat/bindings/_rust/openssl/x25519.pyi,sha256=-1F5QDZfrdhmDLKTeSERuuDUHBTV-EhxIYk9mjpwcG4,616 -cryptography/hazmat/bindings/_rust/openssl/x448.pyi,sha256=SdL4blscYBEvuWY4SuNAY1s5zFaGj38eQ-bulVBZvFg,590 -cryptography/hazmat/bindings/_rust/pkcs7.pyi,sha256=VkTC78wjJgb_qrboOYIFPuFZ3W46zsr6zsxnlrOMwao,460 -cryptography/hazmat/bindings/_rust/x509.pyi,sha256=j6AbXBZSXeJHLSrXnaapbiPfle-znfk9uJUa_zqxgy4,1878 -cryptography/hazmat/bindings/openssl/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 -cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc,, -cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc,, -cryptography/hazmat/bindings/openssl/_conditional.py,sha256=DeECq7AKguhs390ZmxgItdqPLzyrKGJk-3KlHJMkXoY,9098 -cryptography/hazmat/bindings/openssl/binding.py,sha256=0x3kzvq2grHu4gbbgEIzEVrX6unp71EEs1hx0o-uuOM,6696 -cryptography/hazmat/primitives/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 -cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc,, -cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc,, -cryptography/hazmat/primitives/_asymmetric.py,sha256=RhgcouUB6HTiFDBrR1LxqkMjpUxIiNvQ1r_zJjRG6qQ,532 -cryptography/hazmat/primitives/_cipheralgorithm.py,sha256=7LPkpw-DrgyvmBMUjvXeBvojVZPtXhFgfelUftnxPGw,1093 -cryptography/hazmat/primitives/_serialization.py,sha256=U0DU0ZzOLJppCQsh9EJH6vGYoHotBolfNyRyx3wr1l0,5216 -cryptography/hazmat/primitives/asymmetric/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 -cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc,, -cryptography/hazmat/primitives/asymmetric/dh.py,sha256=XsthqjvExWWOyePs0PxT4MestU9QeGuL-Hx7fWzTguQ,7013 -cryptography/hazmat/primitives/asymmetric/dsa.py,sha256=aaTY7EMLTzaWs-jhOMpMAfa2GnfhoqsCKZPKAs35L40,8263 -cryptography/hazmat/primitives/asymmetric/ec.py,sha256=L1WoWPYevJ6Pk2T1etbnHbvr6AeXFccckPNNiyUVoNM,12867 -cryptography/hazmat/primitives/asymmetric/ed25519.py,sha256=wl2NCCP4bZdUCqZGMkOOd6eaxjU1vXPAIwzUuFPE__w,3489 -cryptography/hazmat/primitives/asymmetric/ed448.py,sha256=2MCJ87qcyCCsjj0OvrfWFxPX8CgaC3d0mr78bt_vDIY,3440 -cryptography/hazmat/primitives/asymmetric/padding.py,sha256=6p8Ojiax_2tcm1aTnNOAkinriCJ67nSTxugg34f-hzk,2717 -cryptography/hazmat/primitives/asymmetric/rsa.py,sha256=vxvOryF00WL8mZQv9bs_-LlgobYLiPYfX246_j_ICtA,11623 -cryptography/hazmat/primitives/asymmetric/types.py,sha256=LnsOJym-wmPUJ7Knu_7bCNU3kIiELCd6krOaW_JU08I,2996 -cryptography/hazmat/primitives/asymmetric/utils.py,sha256=DPTs6T4F-UhwzFQTh-1fSEpQzazH2jf2xpIro3ItF4o,790 -cryptography/hazmat/primitives/asymmetric/x25519.py,sha256=8YJAIaU7w09jTnPU_cLwd98fMHIECgfA3R7P3Ktv-CA,3437 -cryptography/hazmat/primitives/asymmetric/x448.py,sha256=y-Yj-rgciiuH1g6FJLZftvAqgOnzT1on9gCisru7vBc,3358 -cryptography/hazmat/primitives/ciphers/__init__.py,sha256=kAyb9NSczqTrCWj0HEoVp3Cxo7AHW8ibPFQz-ZHsOtA,680 -cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc,, -cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc,, -cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc,, -cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc,, -cryptography/hazmat/primitives/ciphers/aead.py,sha256=DY7qKmbt0bgB1GB7i-fQrbjEfwFG8wfUfVHvc7DA2YY,12067 -cryptography/hazmat/primitives/ciphers/algorithms.py,sha256=SCDskXc9xyzsz0NjND6tAX8t17jYTbUB2sww1ub9GuY,5000 -cryptography/hazmat/primitives/ciphers/base.py,sha256=PqNDltHdDxBhLhgtfO707H07sSOLA6ZVwjZlalOJTAo,8286 -cryptography/hazmat/primitives/ciphers/modes.py,sha256=YJQXi4PJGIIZ1rgchbMH47Ed-YiUcUSjLPEOuV8rgGE,8361 -cryptography/hazmat/primitives/cmac.py,sha256=YaeWksCYaqVoqf9zHRThAJ95ZvPUioAOfXwZUWiPzD8,2065 -cryptography/hazmat/primitives/constant_time.py,sha256=xdunWT0nf8OvKdcqUhhlFKayGp4_PgVJRU2W1wLSr_A,422 -cryptography/hazmat/primitives/hashes.py,sha256=VJpnbK2sQN2bEqwRTOoCB4nuxYx5CnqFiScMJNyhsrI,5115 -cryptography/hazmat/primitives/hmac.py,sha256=RpB3z9z5skirCQrm7zQbtnp9pLMnAjrlTUvKqF5aDDc,423 -cryptography/hazmat/primitives/kdf/__init__.py,sha256=4XibZnrYq4hh5xBjWiIXzaYW6FKx8hPbVaa_cB9zS64,750 -cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc,, -cryptography/hazmat/primitives/kdf/concatkdf.py,sha256=wGYWgILmxQWnCPkbAH1RpsCHrdKgmYrCEVrCvXVGCo8,3726 -cryptography/hazmat/primitives/kdf/hkdf.py,sha256=bBYr1yUIbOlJIEd6ZoLYcXm_yd-H54An9kNcFIJ3kbo,3045 -cryptography/hazmat/primitives/kdf/kbkdf.py,sha256=qPL6TmDUmkus6CW3ylTJfG8N8egZhjQOyXrSyLLpnak,9232 -cryptography/hazmat/primitives/kdf/pbkdf2.py,sha256=1CCH9Q5gXUpnZd3c8d8bCXgpJ3s2hZZGBnuG7FH1waM,2012 -cryptography/hazmat/primitives/kdf/scrypt.py,sha256=4QONhjxA_ZtuQtQ7QV3FnbB8ftrFnM52B4HPfV7hFys,2354 -cryptography/hazmat/primitives/kdf/x963kdf.py,sha256=S3B4Enk2Yxj9txpairotaXkavuZqQ6t6MB5a28U02ek,2002 -cryptography/hazmat/primitives/keywrap.py,sha256=Qb_N2V_E1Dti5VtDXnrtTYtJDZ8aMpur8BY5yxrXclg,5678 -cryptography/hazmat/primitives/padding.py,sha256=8pCeLaqwQPSGf51j06U5C_INvgYWVWPv3m9mxUERGmU,6242 -cryptography/hazmat/primitives/poly1305.py,sha256=P5EPQV-RB_FJPahpg01u0Ts4S_PnAmsroxIGXbGeRRo,355 -cryptography/hazmat/primitives/serialization/__init__.py,sha256=6ZlL3EicEzoGdMOat86w8y_XICCnlHdCjFI97rMxRDg,1653 -cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc,, -cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc,, -cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc,, -cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc,, -cryptography/hazmat/primitives/serialization/base.py,sha256=VZjIIqnbb-x38qpg2Wf_IxZvqjsgcEzNQtQoeJiQfpw,1986 -cryptography/hazmat/primitives/serialization/pkcs12.py,sha256=NOzFxArlZhdjfgfugs8nERho1eyaxujXKGUKINchek4,6767 -cryptography/hazmat/primitives/serialization/pkcs7.py,sha256=BCvlPubXQOunb76emISK89PX9qXcBQI2CRPNe85VTZk,7392 -cryptography/hazmat/primitives/serialization/ssh.py,sha256=aLCYLPY3W1kerfCwadn5aYNzwcwIQl9c7RcsB8CKfuc,51027 -cryptography/hazmat/primitives/twofactor/__init__.py,sha256=tmMZGB-g4IU1r7lIFqASU019zr0uPp_wEBYcwdDCKCA,258 -cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc,, -cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc,, -cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc,, -cryptography/hazmat/primitives/twofactor/hotp.py,sha256=uZ0PSKYDZOL0aAobiw1Zd2HD0W2Ei1niUNC2v7Tnpc8,3010 -cryptography/hazmat/primitives/twofactor/totp.py,sha256=cMbWlAapOM1SfezEx9MoMHpCW9ingNXCg6OsGv4T8jc,1473 -cryptography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -cryptography/utils.py,sha256=DfdXc9M4kmAboE2a0pPiISt5LVnW-jhhXURy8nDHae0,4018 -cryptography/x509/__init__.py,sha256=DzZE8bR-3iiVi3Wrcq7-g5Pm64fCr5aqsTNyi_rjJu0,7870 -cryptography/x509/__pycache__/__init__.cpython-312.pyc,, -cryptography/x509/__pycache__/base.cpython-312.pyc,, -cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc,, -cryptography/x509/__pycache__/extensions.cpython-312.pyc,, -cryptography/x509/__pycache__/general_name.cpython-312.pyc,, -cryptography/x509/__pycache__/name.cpython-312.pyc,, -cryptography/x509/__pycache__/ocsp.cpython-312.pyc,, -cryptography/x509/__pycache__/oid.cpython-312.pyc,, -cryptography/x509/base.py,sha256=FbS6EFE3uJ3O-zbFPRjsO6DckrNSN5TJNZMJcnzUWFQ,35677 -cryptography/x509/certificate_transparency.py,sha256=6HvzAD0dlSQVxy6tnDhGj0-pisp1MaJ9bxQNRr92inI,2261 -cryptography/x509/extensions.py,sha256=rFEcfZiFvcONs1ot03d68dAMK2U75w0s3g9mhyWBRcI,68365 -cryptography/x509/general_name.py,sha256=zm8GxNgVJuLD6rN488c5zdHhxp5gUxeRzw8enZMWDQ0,7868 -cryptography/x509/name.py,sha256=aZ2dpsinhkza3eTxT1vNmWuFMQ7fmcA0hs4npgnkf9Q,14855 -cryptography/x509/ocsp.py,sha256=48iW7xbZ9mZLELSEl7Wwjb4vYhOQ3KcNtqgKsAb_UD0,18534 -cryptography/x509/oid.py,sha256=fFosjGsnIB_w_0YrzZv1ggkSVwZl7xmY0zofKZNZkDA,829 diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/WHEEL deleted file mode 100644 index 5869d67e..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.42.0) -Root-Is-Purelib: false -Tag: cp37-abi3-manylinux_2_28_x86_64 - diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/top_level.txt deleted file mode 100644 index 0d38bc5e..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -cryptography diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/INSTALLER similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/INSTALLER rename to Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/INSTALLER diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/METADATA similarity index 66% rename from Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/METADATA rename to Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/METADATA index 2106e2e6..7b07ee7b 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/METADATA +++ b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/METADATA @@ -1,18 +1,8 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: cryptography -Version: 41.0.7 -Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. -Author-email: The Python Cryptographic Authority and individual contributors -License: Apache-2.0 OR BSD-3-Clause -Project-URL: homepage, https://github.com/pyca/cryptography -Project-URL: documentation, https://cryptography.io/ -Project-URL: source, https://github.com/pyca/cryptography/ -Project-URL: issues, https://github.com/pyca/cryptography/issues -Project-URL: changelog, https://cryptography.io/en/latest/changelog/ +Version: 46.0.3 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: Apache Software License -Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX @@ -22,46 +12,62 @@ Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable Classifier: Topic :: Security :: Cryptography -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst +Requires-Dist: cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' +Requires-Dist: cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' +Requires-Dist: typing-extensions>=4.13.2 ; python_full_version < '3.11' +Requires-Dist: bcrypt>=3.1.5 ; extra == 'ssh' +Requires-Dist: nox[uv]>=2024.4.15 ; extra == 'nox' +Requires-Dist: cryptography-vectors==46.0.3 ; extra == 'test' +Requires-Dist: pytest>=7.4.0 ; extra == 'test' +Requires-Dist: pytest-benchmark>=4.0 ; extra == 'test' +Requires-Dist: pytest-cov>=2.10.1 ; extra == 'test' +Requires-Dist: pytest-xdist>=3.5.0 ; extra == 'test' +Requires-Dist: pretend>=0.7 ; extra == 'test' +Requires-Dist: certifi>=2024 ; extra == 'test' +Requires-Dist: pytest-randomly ; extra == 'test-randomorder' +Requires-Dist: sphinx>=5.3.0 ; extra == 'docs' +Requires-Dist: sphinx-rtd-theme>=3.0.0 ; extra == 'docs' +Requires-Dist: sphinx-inline-tabs ; extra == 'docs' +Requires-Dist: pyenchant>=3 ; extra == 'docstest' +Requires-Dist: readme-renderer>=30.0 ; extra == 'docstest' +Requires-Dist: sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' +Requires-Dist: build>=1.0.0 ; extra == 'sdist' +Requires-Dist: ruff>=0.11.11 ; extra == 'pep8test' +Requires-Dist: mypy>=1.14 ; extra == 'pep8test' +Requires-Dist: check-sdist ; extra == 'pep8test' +Requires-Dist: click>=8.0.1 ; extra == 'pep8test' +Provides-Extra: ssh +Provides-Extra: nox +Provides-Extra: test +Provides-Extra: test-randomorder +Provides-Extra: docs +Provides-Extra: docstest +Provides-Extra: sdist +Provides-Extra: pep8test License-File: LICENSE License-File: LICENSE.APACHE License-File: LICENSE.BSD -Requires-Dist: cffi >=1.12 -Provides-Extra: docs -Requires-Dist: sphinx >=5.3.0 ; extra == 'docs' -Requires-Dist: sphinx-rtd-theme >=1.1.1 ; extra == 'docs' -Provides-Extra: docstest -Requires-Dist: pyenchant >=1.6.11 ; extra == 'docstest' -Requires-Dist: twine >=1.12.0 ; extra == 'docstest' -Requires-Dist: sphinxcontrib-spelling >=4.0.1 ; extra == 'docstest' -Provides-Extra: nox -Requires-Dist: nox ; extra == 'nox' -Provides-Extra: pep8test -Requires-Dist: black ; extra == 'pep8test' -Requires-Dist: ruff ; extra == 'pep8test' -Requires-Dist: mypy ; extra == 'pep8test' -Requires-Dist: check-sdist ; extra == 'pep8test' -Provides-Extra: sdist -Requires-Dist: build ; extra == 'sdist' -Provides-Extra: ssh -Requires-Dist: bcrypt >=3.1.5 ; extra == 'ssh' -Provides-Extra: test -Requires-Dist: pytest >=6.2.0 ; extra == 'test' -Requires-Dist: pytest-benchmark ; extra == 'test' -Requires-Dist: pytest-cov ; extra == 'test' -Requires-Dist: pytest-xdist ; extra == 'test' -Requires-Dist: pretend ; extra == 'test' -Provides-Extra: test-randomorder -Requires-Dist: pytest-randomly ; extra == 'test-randomorder' +Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. +Author-email: The Python Cryptographic Authority and individual contributors +License-Expression: Apache-2.0 OR BSD-3-Clause +Requires-Python: >=3.8, !=3.9.0, !=3.9.1 +Description-Content-Type: text/x-rst; charset=UTF-8 +Project-URL: homepage, https://github.com/pyca/cryptography +Project-URL: documentation, https://cryptography.io/ +Project-URL: source, https://github.com/pyca/cryptography/ +Project-URL: issues, https://github.com/pyca/cryptography/issues +Project-URL: changelog, https://cryptography.io/en/latest/changelog/ pyca/cryptography ================= @@ -74,13 +80,12 @@ pyca/cryptography :target: https://cryptography.io :alt: Latest Docs -.. image:: https://github.com/pyca/cryptography/workflows/CI/badge.svg?branch=main - :target: https://github.com/pyca/cryptography/actions?query=workflow%3ACI+branch%3Amain - +.. image:: https://github.com/pyca/cryptography/actions/workflows/ci.yml/badge.svg + :target: https://github.com/pyca/cryptography/actions/workflows/ci.yml?query=branch%3Amain ``cryptography`` is a package which provides cryptographic recipes and primitives to Python developers. Our goal is for it to be your "cryptographic -standard library". It supports Python 3.7+ and PyPy3 7.3.10+. +standard library". It supports Python 3.8+ and PyPy3 7.3.11+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and @@ -131,3 +136,4 @@ documentation. .. _`issue tracker`: https://github.com/pyca/cryptography/issues .. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`security reporting`: https://cryptography.io/en/latest/security/ + diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/RECORD new file mode 100644 index 00000000..9099b5bc --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/RECORD @@ -0,0 +1,180 @@ +cryptography-46.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cryptography-46.0.3.dist-info/METADATA,sha256=bx2LyCEmOVUC8FH5hsGEZewWPiZoIIYTq0hM9mu9r4s,5748 +cryptography-46.0.3.dist-info/RECORD,, +cryptography-46.0.3.dist-info/WHEEL,sha256=jkxrJemT4jZpYSr-u9xPalWqoow8benNmiXfjKXLlJw,108 +cryptography-46.0.3.dist-info/licenses/LICENSE,sha256=Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs,197 +cryptography-46.0.3.dist-info/licenses/LICENSE.APACHE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360 +cryptography-46.0.3.dist-info/licenses/LICENSE.BSD,sha256=YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs,1532 +cryptography/__about__.py,sha256=QCLxNH_Abbygdc9RQGpUmrK14Wp3Cl_SEiB2byLwyxo,445 +cryptography/__init__.py,sha256=mthuUrTd4FROCpUYrTIqhjz6s6T9djAZrV7nZ1oMm2o,364 +cryptography/__pycache__/__about__.cpython-312.pyc,, +cryptography/__pycache__/__init__.cpython-312.pyc,, +cryptography/__pycache__/exceptions.cpython-312.pyc,, +cryptography/__pycache__/fernet.cpython-312.pyc,, +cryptography/__pycache__/utils.cpython-312.pyc,, +cryptography/exceptions.py,sha256=835EWILc2fwxw-gyFMriciC2SqhViETB10LBSytnDIc,1087 +cryptography/fernet.py,sha256=3Cvxkh0KJSbX8HbnCHu4wfCW7U0GgfUA3v_qQ8a8iWc,6963 +cryptography/hazmat/__init__.py,sha256=5IwrLWrVp0AjEr_4FdWG_V057NSJGY_W4egNNsuct0g,455 +cryptography/hazmat/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/__pycache__/_oid.cpython-312.pyc,, +cryptography/hazmat/_oid.py,sha256=p8ThjwJB56Ci_rAIrjyJ1f8VjgD6e39es2dh8JIUBOw,17240 +cryptography/hazmat/asn1/__init__.py,sha256=hS_EWx3wVvZzfbCcNV8hzcDnyMM8H-BhIoS1TipUosk,293 +cryptography/hazmat/asn1/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/asn1/__pycache__/asn1.cpython-312.pyc,, +cryptography/hazmat/asn1/asn1.py,sha256=eMEThEXa19LQjcyVofgHsW6tsZnjp3ddH7bWkkcxfLM,3860 +cryptography/hazmat/backends/__init__.py,sha256=O5jvKFQdZnXhKeqJ-HtulaEL9Ni7mr1mDzZY5kHlYhI,361 +cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/backends/openssl/__init__.py,sha256=p3jmJfnCag9iE5sdMrN6VvVEu55u46xaS_IjoI0SrmA,305 +cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc,, +cryptography/hazmat/backends/openssl/backend.py,sha256=tV5AxBoFJ2GfA0DMWSY-0TxQJrpQoexzI9R4Kybb--4,10215 +cryptography/hazmat/bindings/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/bindings/_rust.abi3.so,sha256=4bUN0J2p_ZQMdgmAc9eL0VMj_lgbTsHUmX4doekVIJ4,12955672 +cryptography/hazmat/bindings/_rust/__init__.pyi,sha256=KhqLhXFPArPzzJ7DYO9Fl8FoXB_BagAd_r4Dm_Ze9Xo,1257 +cryptography/hazmat/bindings/_rust/_openssl.pyi,sha256=mpNJLuYLbCVrd5i33FBTmWwL_55Dw7JPkSLlSX9Q7oI,230 +cryptography/hazmat/bindings/_rust/asn1.pyi,sha256=BrGjC8J6nwuS-r3EVcdXJB8ndotfY9mbQYOfpbPG0HA,354 +cryptography/hazmat/bindings/_rust/declarative_asn1.pyi,sha256=2ECFmYue1EPkHEE2Bm7aLwkjB0mSUTpr23v9MN4pri4,892 +cryptography/hazmat/bindings/_rust/exceptions.pyi,sha256=exXr2xw_0pB1kk93cYbM3MohbzoUkjOms1ZMUi0uQZE,640 +cryptography/hazmat/bindings/_rust/ocsp.pyi,sha256=VPVWuKHI9EMs09ZLRYAGvR0Iz0mCMmEzXAkgJHovpoM,4020 +cryptography/hazmat/bindings/_rust/openssl/__init__.pyi,sha256=iOAMDyHoNwwCSZfZzuXDr64g4GpGUeDgEN-LjXqdrBM,1522 +cryptography/hazmat/bindings/_rust/openssl/aead.pyi,sha256=4Nddw6-ynzIB3w2W86WvkGKTLlTDk_6F5l54RHCuy3E,2688 +cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi,sha256=LhPzHWSXJq4grAJXn6zSvSSdV-aYIIscHDwIPlJGGPs,1315 +cryptography/hazmat/bindings/_rust/openssl/cmac.pyi,sha256=nPH0X57RYpsAkRowVpjQiHE566ThUTx7YXrsadmrmHk,564 +cryptography/hazmat/bindings/_rust/openssl/dh.pyi,sha256=Z3TC-G04-THtSdAOPLM1h2G7ml5bda1ElZUcn5wpuhk,1564 +cryptography/hazmat/bindings/_rust/openssl/dsa.pyi,sha256=qBtkgj2albt2qFcnZ9UDrhzoNhCVO7HTby5VSf1EXMI,1299 +cryptography/hazmat/bindings/_rust/openssl/ec.pyi,sha256=zJy0pRa5n-_p2dm45PxECB_-B6SVZyNKfjxFDpPqT38,1691 +cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi,sha256=VXfXd5G6hUivg399R1DYdmW3eTb0EebzDTqjRC2gaRw,532 +cryptography/hazmat/bindings/_rust/openssl/ed448.pyi,sha256=Yx49lqdnjsD7bxiDV1kcaMrDktug5evi5a6zerMiy2s,514 +cryptography/hazmat/bindings/_rust/openssl/hashes.pyi,sha256=OWZvBx7xfo_HJl41Nc--DugVyCVPIprZ3HlOPTSWH9g,984 +cryptography/hazmat/bindings/_rust/openssl/hmac.pyi,sha256=BXZn7NDjL3JAbYW0SQ8pg1iyC5DbQXVhUAiwsi8DFR8,702 +cryptography/hazmat/bindings/_rust/openssl/kdf.pyi,sha256=xXfFBb9QehHfDtEaxV_65Z0YK7NquOVIChpTLkgAs_k,2029 +cryptography/hazmat/bindings/_rust/openssl/keys.pyi,sha256=teIt8M6ZEMJrn4s3W0UnW0DZ-30Jd68WnSsKKG124l0,912 +cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi,sha256=_SW9NtQ5FDlAbdclFtWpT4lGmxKIKHpN-4j8J2BzYfQ,585 +cryptography/hazmat/bindings/_rust/openssl/rsa.pyi,sha256=2OQCNSXkxgc-3uw1xiCCloIQTV6p9_kK79Yu0rhZgPc,1364 +cryptography/hazmat/bindings/_rust/openssl/x25519.pyi,sha256=ewn4GpQyb7zPwE-ni7GtyQgMC0A1mLuqYsSyqv6nI_s,523 +cryptography/hazmat/bindings/_rust/openssl/x448.pyi,sha256=juTZTmli8jO_5Vcufg-vHvx_tCyezmSLIh_9PU3TczI,505 +cryptography/hazmat/bindings/_rust/pkcs12.pyi,sha256=vEEd5wDiZvb8ZGFaziLCaWLzAwoG_tvPUxLQw5_uOl8,1605 +cryptography/hazmat/bindings/_rust/pkcs7.pyi,sha256=txGBJijqZshEcqra6byPNbnisIdlxzOSIHP2hl9arPs,1601 +cryptography/hazmat/bindings/_rust/test_support.pyi,sha256=PPhld-WkO743iXFPebeG0LtgK0aTzGdjcIsay1Gm5GE,757 +cryptography/hazmat/bindings/_rust/x509.pyi,sha256=n9X0IQ6ICbdIi-ExdCFZoBgeY6njm3QOVAVZwDQdnbk,9784 +cryptography/hazmat/bindings/openssl/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc,, +cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc,, +cryptography/hazmat/bindings/openssl/_conditional.py,sha256=DMOpA_XN4l70zTc5_J9DpwlbQeUBRTWpfIJ4yRIn1-U,5791 +cryptography/hazmat/bindings/openssl/binding.py,sha256=x8eocEmukO4cm7cHqfVmOoYY7CCXdoF1v1WhZQt9neo,4610 +cryptography/hazmat/decrepit/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/algorithms.py,sha256=YrKgHS4MfwWaMmPBYRymRRlC0phwWp9ycICFezeJPGk,2595 +cryptography/hazmat/primitives/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc,, +cryptography/hazmat/primitives/_asymmetric.py,sha256=RhgcouUB6HTiFDBrR1LxqkMjpUxIiNvQ1r_zJjRG6qQ,532 +cryptography/hazmat/primitives/_cipheralgorithm.py,sha256=Eh3i7lwedHfi0eLSsH93PZxQKzY9I6lkK67vL4V5tOc,1522 +cryptography/hazmat/primitives/_serialization.py,sha256=chgPCSF2jxI2Cr5gB-qbWXOvOfupBh4CARS0KAhv9AM,5123 +cryptography/hazmat/primitives/asymmetric/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/dh.py,sha256=0v_vEFFz5pQ1QG-FkWDyvgv7IfuVZSH5Q6LyFI5A8rg,3645 +cryptography/hazmat/primitives/asymmetric/dsa.py,sha256=Ld_bbbqQFz12dObHxIkzEQzX0SWWP41RLSWkYSaKhqE,4213 +cryptography/hazmat/primitives/asymmetric/ec.py,sha256=Vf5ig2PcS3PVnsb5N49Kx1uIkFBJyhg4BWXThDz5cug,12999 +cryptography/hazmat/primitives/asymmetric/ed25519.py,sha256=jZW5cs472wXXV3eB0sE1b8w64gdazwwU0_MT5UOTiXs,3700 +cryptography/hazmat/primitives/asymmetric/ed448.py,sha256=yAetgn2f2JYf0BO8MapGzXeThsvSMG5LmUCrxVOidAA,3729 +cryptography/hazmat/primitives/asymmetric/padding.py,sha256=vQ6l6gOg9HqcbOsvHrSiJRVLdEj9L4m4HkRGYziTyFA,2854 +cryptography/hazmat/primitives/asymmetric/rsa.py,sha256=ZnKOo2f34MCCOupC03Y1uR-_jiSG5IrelHEmxaME3D4,8303 +cryptography/hazmat/primitives/asymmetric/types.py,sha256=LnsOJym-wmPUJ7Knu_7bCNU3kIiELCd6krOaW_JU08I,2996 +cryptography/hazmat/primitives/asymmetric/utils.py,sha256=DPTs6T4F-UhwzFQTh-1fSEpQzazH2jf2xpIro3ItF4o,790 +cryptography/hazmat/primitives/asymmetric/x25519.py,sha256=_4nQeZ3yJ3Lg0RpXnaqA-1yt6vbx1F-wzLcaZHwSpeE,3613 +cryptography/hazmat/primitives/asymmetric/x448.py,sha256=WKBLtuVfJqiBRro654fGaQAlvsKbqbNkK7c4A_ZCdV0,3642 +cryptography/hazmat/primitives/ciphers/__init__.py,sha256=eyEXmjk6_CZXaOPYDr7vAYGXr29QvzgWL2-4CSolLFs,680 +cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/aead.py,sha256=Fzlyx7w8KYQakzDp1zWgJnIr62zgZrgVh1u2h4exB54,634 +cryptography/hazmat/primitives/ciphers/algorithms.py,sha256=Q7ZJwcsx83Mgxv5y7r6CyJKSdsOwC-my-5A67-ma2vw,3407 +cryptography/hazmat/primitives/ciphers/base.py,sha256=aBC7HHBBoixebmparVr0UlODs3VD0A7B6oz_AaRjDv8,4253 +cryptography/hazmat/primitives/ciphers/modes.py,sha256=20stpwhDtbAvpH0SMf9EDHIciwmTF-JMBUOZ9bU8WiQ,8318 +cryptography/hazmat/primitives/cmac.py,sha256=sz_s6H_cYnOvx-VNWdIKhRhe3Ymp8z8J0D3CBqOX3gg,338 +cryptography/hazmat/primitives/constant_time.py,sha256=xdunWT0nf8OvKdcqUhhlFKayGp4_PgVJRU2W1wLSr_A,422 +cryptography/hazmat/primitives/hashes.py,sha256=M8BrlKB3U6DEtHvWTV5VRjpteHv1kS3Zxm_Bsk04cr8,5184 +cryptography/hazmat/primitives/hmac.py,sha256=RpB3z9z5skirCQrm7zQbtnp9pLMnAjrlTUvKqF5aDDc,423 +cryptography/hazmat/primitives/kdf/__init__.py,sha256=4XibZnrYq4hh5xBjWiIXzaYW6FKx8hPbVaa_cB9zS64,750 +cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/argon2.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/argon2.py,sha256=UFDNXG0v-rw3DqAQTB1UQAsQC2M5Ejg0k_6OCyhLKus,460 +cryptography/hazmat/primitives/kdf/concatkdf.py,sha256=Ua8KoLXXnzgsrAUmHpyKymaPt8aPRP0EHEaBz7QCQ9I,3737 +cryptography/hazmat/primitives/kdf/hkdf.py,sha256=M0lAEfRoc4kpp4-nwDj9yB-vNZukIOYEQrUlWsBNn9o,543 +cryptography/hazmat/primitives/kdf/kbkdf.py,sha256=oZepvo4evhKkkJQWRDwaPoIbyTaFmDc5NPimxg6lfKg,9165 +cryptography/hazmat/primitives/kdf/pbkdf2.py,sha256=1WIwhELR0w8ztTpTu8BrFiYWmK3hUfJq08I79TxwieE,1957 +cryptography/hazmat/primitives/kdf/scrypt.py,sha256=XyWUdUUmhuI9V6TqAPOvujCSMGv1XQdg0a21IWCmO-U,590 +cryptography/hazmat/primitives/kdf/x963kdf.py,sha256=zLTcF665QFvXX2f8TS7fmBZTteXpFjKahzfjjQcCJyw,1999 +cryptography/hazmat/primitives/keywrap.py,sha256=XV4Pj2fqSeD-RqZVvY2cA3j5_7RwJSFygYuLfk2ujCo,5650 +cryptography/hazmat/primitives/padding.py,sha256=QT-U-NvV2eQGO1wVPbDiNGNSc9keRDS-ig5cQOrLz0E,1865 +cryptography/hazmat/primitives/poly1305.py,sha256=P5EPQV-RB_FJPahpg01u0Ts4S_PnAmsroxIGXbGeRRo,355 +cryptography/hazmat/primitives/serialization/__init__.py,sha256=Q7uTgDlt7n3WfsMT6jYwutC6DIg_7SEeoAm1GHZ5B5E,1705 +cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/base.py,sha256=ikq5MJIwp_oUnjiaBco_PmQwOTYuGi-XkYUYHKy8Vo0,615 +cryptography/hazmat/primitives/serialization/pkcs12.py,sha256=mS9cFNG4afzvseoc5e1MWoY2VskfL8N8Y_OFjl67luY,5104 +cryptography/hazmat/primitives/serialization/pkcs7.py,sha256=5OR_Tkysxaprn4FegvJIfbep9rJ9wok6FLWvWwQ5-Mg,13943 +cryptography/hazmat/primitives/serialization/ssh.py,sha256=hPV5obFznz0QhFfXFPOeQ8y6MsurA0xVMQiLnLESEs8,53700 +cryptography/hazmat/primitives/twofactor/__init__.py,sha256=tmMZGB-g4IU1r7lIFqASU019zr0uPp_wEBYcwdDCKCA,258 +cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc,, +cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc,, +cryptography/hazmat/primitives/twofactor/hotp.py,sha256=ivZo5BrcCGWLsqql4nZV0XXCjyGPi_iHfDFltGlOJwk,3256 +cryptography/hazmat/primitives/twofactor/totp.py,sha256=m5LPpRL00kp4zY8gTjr55Hfz9aMlPS53kHmVkSQCmdY,1652 +cryptography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cryptography/utils.py,sha256=bZAjFC5KVpfmF29qS_18vvpW3mKxmdiRALcusHhTTkg,4301 +cryptography/x509/__init__.py,sha256=xloN0swseNx-m2WFZmCA17gOoxQWqeU82UVjEdJBePQ,8257 +cryptography/x509/__pycache__/__init__.cpython-312.pyc,, +cryptography/x509/__pycache__/base.cpython-312.pyc,, +cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc,, +cryptography/x509/__pycache__/extensions.cpython-312.pyc,, +cryptography/x509/__pycache__/general_name.cpython-312.pyc,, +cryptography/x509/__pycache__/name.cpython-312.pyc,, +cryptography/x509/__pycache__/ocsp.cpython-312.pyc,, +cryptography/x509/__pycache__/oid.cpython-312.pyc,, +cryptography/x509/__pycache__/verification.cpython-312.pyc,, +cryptography/x509/base.py,sha256=OrmTw3y8B6AE_nGXQPN8x9kq-d7rDWeH13gCq6T6D6U,27997 +cryptography/x509/certificate_transparency.py,sha256=JqoOIDhlwInrYMFW6IFn77WJ0viF-PB_rlZV3vs9MYc,797 +cryptography/x509/extensions.py,sha256=QxYrqR6SF1qzR9ZraP8wDiIczlEVlAFuwDRVcltB6Tk,77724 +cryptography/x509/general_name.py,sha256=sP_rV11Qlpsk4x3XXGJY_Mv0Q_s9dtjeLckHsjpLQoQ,7836 +cryptography/x509/name.py,sha256=ty0_xf0LnHwZAdEf-d8FLO1K4hGqx_7DsD3CHwoLJiY,15101 +cryptography/x509/ocsp.py,sha256=Yey6NdFV1MPjop24Mj_VenjEpg3kUaMopSWOK0AbeBs,12699 +cryptography/x509/oid.py,sha256=BUzgXXGVWilkBkdKPTm9R4qElE9gAGHgdYPMZAp7PJo,931 +cryptography/x509/verification.py,sha256=gR2C2c-XZQtblZhT5T5vjSKOtCb74ef2alPVmEcwFlM,958 diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/WHEEL new file mode 100644 index 00000000..8e48aa14 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: maturin (1.9.4) +Root-Is-Purelib: false +Tag: cp311-abi3-manylinux_2_34_x86_64 + diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/LICENSE b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/licenses/LICENSE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/LICENSE rename to Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/licenses/LICENSE diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/LICENSE.APACHE b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/licenses/LICENSE.APACHE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/LICENSE.APACHE rename to Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/licenses/LICENSE.APACHE diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/LICENSE.BSD b/Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/licenses/LICENSE.BSD similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/cryptography-41.0.7.dist-info/LICENSE.BSD rename to Backend/venv/lib/python3.12/site-packages/cryptography-46.0.3.dist-info/licenses/LICENSE.BSD diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__about__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/__about__.py index 014e0adb..a8116284 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/__about__.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/__about__.py @@ -5,13 +5,13 @@ from __future__ import annotations __all__ = [ - "__version__", "__author__", "__copyright__", + "__version__", ] -__version__ = "41.0.7" +__version__ = "46.0.3" __author__ = "The Python Cryptographic Authority and individual contributors" -__copyright__ = f"Copyright 2013-2023 {__author__}" +__copyright__ = f"Copyright 2013-2025 {__author__}" diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/__init__.py index 86b9a257..d374f752 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/__init__.py @@ -7,7 +7,7 @@ from __future__ import annotations from cryptography.__about__ import __author__, __copyright__, __version__ __all__ = [ - "__version__", "__author__", "__copyright__", + "__version__", ] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc index 16a74985..8d19f948 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc index dea6f303..57e9aceb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc index 16c476e0..0310a8f8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc index 0c0e986a..24170122 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc index 643a0e10..2869efbc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/exceptions.py b/Backend/venv/lib/python3.12/site-packages/cryptography/exceptions.py index 47fdd18e..fe125ea9 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/exceptions.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/exceptions.py @@ -15,9 +15,7 @@ _Reasons = rust_exceptions._Reasons class UnsupportedAlgorithm(Exception): - def __init__( - self, message: str, reason: typing.Optional[_Reasons] = None - ) -> None: + def __init__(self, message: str, reason: _Reasons | None = None) -> None: super().__init__(message) self._reason = reason @@ -44,7 +42,7 @@ class InvalidSignature(Exception): class InternalError(Exception): def __init__( - self, msg: str, err_code: typing.List[rust_openssl.OpenSSLError] + self, msg: str, err_code: list[rust_openssl.OpenSSLError] ) -> None: super().__init__(msg) self.err_code = err_code diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/fernet.py b/Backend/venv/lib/python3.12/site-packages/cryptography/fernet.py index ad8fb40b..c6744ae3 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/fernet.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/fernet.py @@ -9,6 +9,7 @@ import binascii import os import time import typing +from collections.abc import Iterable from cryptography import utils from cryptography.exceptions import InvalidSignature @@ -27,7 +28,7 @@ _MAX_CLOCK_SKEW = 60 class Fernet: def __init__( self, - key: typing.Union[bytes, str], + key: bytes | str, backend: typing.Any = None, ) -> None: try: @@ -80,9 +81,7 @@ class Fernet: hmac = h.finalize() return base64.urlsafe_b64encode(basic_parts + hmac) - def decrypt( - self, token: typing.Union[bytes, str], ttl: typing.Optional[int] = None - ) -> bytes: + def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes: timestamp, data = Fernet._get_unverified_token_data(token) if ttl is None: time_info = None @@ -91,7 +90,7 @@ class Fernet: return self._decrypt_data(data, timestamp, time_info) def decrypt_at_time( - self, token: typing.Union[bytes, str], ttl: int, current_time: int + self, token: bytes | str, ttl: int, current_time: int ) -> bytes: if ttl is None: raise ValueError( @@ -100,16 +99,14 @@ class Fernet: timestamp, data = Fernet._get_unverified_token_data(token) return self._decrypt_data(data, timestamp, (ttl, current_time)) - def extract_timestamp(self, token: typing.Union[bytes, str]) -> int: + def extract_timestamp(self, token: bytes | str) -> int: timestamp, data = Fernet._get_unverified_token_data(token) # Verify the token was not tampered with. self._verify_signature(data) return timestamp @staticmethod - def _get_unverified_token_data( - token: typing.Union[bytes, str] - ) -> typing.Tuple[int, bytes]: + def _get_unverified_token_data(token: bytes | str) -> tuple[int, bytes]: if not isinstance(token, (str, bytes)): raise TypeError("token must be bytes or str") @@ -139,7 +136,7 @@ class Fernet: self, data: bytes, timestamp: int, - time_info: typing.Optional[typing.Tuple[int, int]], + time_info: tuple[int, int] | None, ) -> bytes: if time_info is not None: ttl, current_time = time_info @@ -172,7 +169,7 @@ class Fernet: class MultiFernet: - def __init__(self, fernets: typing.Iterable[Fernet]): + def __init__(self, fernets: Iterable[Fernet]): fernets = list(fernets) if not fernets: raise ValueError( @@ -186,7 +183,7 @@ class MultiFernet: def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes: return self._fernets[0].encrypt_at_time(msg, current_time) - def rotate(self, msg: typing.Union[bytes, str]) -> bytes: + def rotate(self, msg: bytes | str) -> bytes: timestamp, data = Fernet._get_unverified_token_data(msg) for f in self._fernets: try: @@ -200,9 +197,7 @@ class MultiFernet: iv = os.urandom(16) return self._fernets[0]._encrypt_from_parts(p, timestamp, iv) - def decrypt( - self, msg: typing.Union[bytes, str], ttl: typing.Optional[int] = None - ) -> bytes: + def decrypt(self, msg: bytes | str, ttl: int | None = None) -> bytes: for f in self._fernets: try: return f.decrypt(msg, ttl) @@ -211,7 +206,7 @@ class MultiFernet: raise InvalidToken def decrypt_at_time( - self, msg: typing.Union[bytes, str], ttl: int, current_time: int + self, msg: bytes | str, ttl: int, current_time: int ) -> bytes: for f in self._fernets: try: @@ -219,3 +214,11 @@ class MultiFernet: except InvalidToken: pass raise InvalidToken + + def extract_timestamp(self, msg: bytes | str) -> int: + for f in self._fernets: + try: + return f.extract_timestamp(msg) + except InvalidToken: + pass + raise InvalidToken diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc index 2c4887a1..46244f4a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc index 23e9f2d9..1e8ce9cc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/_oid.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/_oid.py index 01d4b340..4bf138d4 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/_oid.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/_oid.py @@ -4,8 +4,6 @@ from __future__ import annotations -import typing - from cryptography.hazmat.bindings._rust import ( ObjectIdentifier as ObjectIdentifier, ) @@ -16,6 +14,7 @@ class ExtensionOID: SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") KEY_USAGE = ObjectIdentifier("2.5.29.15") + PRIVATE_KEY_USAGE_PERIOD = ObjectIdentifier("2.5.29.16") SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") @@ -41,6 +40,7 @@ class ExtensionOID: PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7") + ADMISSIONS = ObjectIdentifier("1.3.36.8.3.3") class OCSPExtensionOID: @@ -60,6 +60,7 @@ class NameOID: LOCALITY_NAME = ObjectIdentifier("2.5.4.7") STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") STREET_ADDRESS = ObjectIdentifier("2.5.4.9") + ORGANIZATION_IDENTIFIER = ObjectIdentifier("2.5.4.97") ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") @@ -123,9 +124,7 @@ class SignatureAlgorithmOID: GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") -_SIG_OIDS_TO_HASH: typing.Dict[ - ObjectIdentifier, typing.Optional[hashes.HashAlgorithm] -] = { +_SIG_OIDS_TO_HASH: dict[ObjectIdentifier, hashes.HashAlgorithm | None] = { SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), @@ -157,6 +156,33 @@ _SIG_OIDS_TO_HASH: typing.Dict[ } +class HashAlgorithmOID: + SHA1 = ObjectIdentifier("1.3.14.3.2.26") + SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.2.4") + SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.2.1") + SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.2.2") + SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.2.3") + SHA3_224 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.224") + SHA3_256 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.256") + SHA3_384 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.384") + SHA3_512 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.512") + SHA3_224_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.7") + SHA3_256_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.8") + SHA3_384_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.9") + SHA3_512_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.10") + + +class PublicKeyAlgorithmOID: + DSA = ObjectIdentifier("1.2.840.10040.4.1") + EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1") + RSAES_PKCS1_v1_5 = ObjectIdentifier("1.2.840.113549.1.1.1") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + X25519 = ObjectIdentifier("1.3.101.110") + X448 = ObjectIdentifier("1.3.101.111") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + + class ExtendedKeyUsageOID: SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") @@ -168,9 +194,20 @@ class ExtendedKeyUsageOID: SMARTCARD_LOGON = ObjectIdentifier("1.3.6.1.4.1.311.20.2.2") KERBEROS_PKINIT_KDC = ObjectIdentifier("1.3.6.1.5.2.3.5") IPSEC_IKE = ObjectIdentifier("1.3.6.1.5.5.7.3.17") + BUNDLE_SECURITY = ObjectIdentifier("1.3.6.1.5.5.7.3.35") CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") +class OtherNameFormOID: + PERMANENT_IDENTIFIER = ObjectIdentifier("1.3.6.1.5.5.7.8.3") + HW_MODULE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.4") + DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") + NAI_REALM = ObjectIdentifier("1.3.6.1.5.5.7.8.8") + SMTP_UTF8_MAILBOX = ObjectIdentifier("1.3.6.1.5.5.7.8.9") + ACP_NODE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.10") + BUNDLE_EID = ObjectIdentifier("1.3.6.1.5.5.7.8.11") + + class AuthorityInformationAccessOID: CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") @@ -228,7 +265,7 @@ _OID_NAMES = { SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", - SignatureAlgorithmOID.RSASSA_PSS: "RSASSA-PSS", + SignatureAlgorithmOID.RSASSA_PSS: "rsassaPss", SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", @@ -248,6 +285,24 @@ _OID_NAMES = { SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" ), + HashAlgorithmOID.SHA1: "sha1", + HashAlgorithmOID.SHA224: "sha224", + HashAlgorithmOID.SHA256: "sha256", + HashAlgorithmOID.SHA384: "sha384", + HashAlgorithmOID.SHA512: "sha512", + HashAlgorithmOID.SHA3_224: "sha3_224", + HashAlgorithmOID.SHA3_256: "sha3_256", + HashAlgorithmOID.SHA3_384: "sha3_384", + HashAlgorithmOID.SHA3_512: "sha3_512", + HashAlgorithmOID.SHA3_224_NIST: "sha3_224", + HashAlgorithmOID.SHA3_256_NIST: "sha3_256", + HashAlgorithmOID.SHA3_384_NIST: "sha3_384", + HashAlgorithmOID.SHA3_512_NIST: "sha3_512", + PublicKeyAlgorithmOID.DSA: "dsaEncryption", + PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey", + PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption", + PublicKeyAlgorithmOID.X25519: "X25519", + PublicKeyAlgorithmOID.X448: "X448", ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", @@ -259,6 +314,7 @@ _OID_NAMES = { ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", ExtensionOID.KEY_USAGE: "keyUsage", + ExtensionOID.PRIVATE_KEY_USAGE_PERIOD: "privateKeyUsagePeriod", ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", @@ -270,6 +326,7 @@ _OID_NAMES = { ), ExtensionOID.PRECERT_POISON: "ctPoison", ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", + ExtensionOID.ADMISSIONS: "Admissions", CRLEntryExtensionOID.CRL_REASON: "cRLReason", CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", @@ -282,7 +339,7 @@ _OID_NAMES = { ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", ExtensionOID.FRESHEST_CRL: "freshestCRL", ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", - ExtensionOID.ISSUING_DISTRIBUTION_POINT: ("issuingDistributionPoint"), + ExtensionOID.ISSUING_DISTRIBUTION_POINT: "issuingDistributionPoint", ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__init__.py new file mode 100644 index 00000000..be683736 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__init__.py @@ -0,0 +1,10 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.asn1.asn1 import encode_der, sequence + +__all__ = [ + "encode_der", + "sequence", +] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..641ae7c2 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__pycache__/asn1.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__pycache__/asn1.cpython-312.pyc new file mode 100644 index 00000000..1f39b4ad Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/__pycache__/asn1.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/asn1.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/asn1.py new file mode 100644 index 00000000..dedad6f2 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/asn1/asn1.py @@ -0,0 +1,116 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import dataclasses +import sys +import typing + +if sys.version_info < (3, 11): + import typing_extensions + + # We use the `include_extras` parameter of `get_type_hints`, which was + # added in Python 3.9. This can be replaced by the `typing` version + # once the min version is >= 3.9 + if sys.version_info < (3, 9): + get_type_hints = typing_extensions.get_type_hints + else: + get_type_hints = typing.get_type_hints +else: + get_type_hints = typing.get_type_hints + +from cryptography.hazmat.bindings._rust import declarative_asn1 + +T = typing.TypeVar("T", covariant=True) +U = typing.TypeVar("U") + + +encode_der = declarative_asn1.encode_der + + +def _normalize_field_type( + field_type: typing.Any, field_name: str +) -> declarative_asn1.AnnotatedType: + annotation = declarative_asn1.Annotation() + + if hasattr(field_type, "__asn1_root__"): + annotated_root = field_type.__asn1_root__ + if not isinstance(annotated_root, declarative_asn1.AnnotatedType): + raise TypeError(f"unsupported root type: {annotated_root}") + return annotated_root + else: + rust_field_type = declarative_asn1.non_root_python_to_rust(field_type) + + return declarative_asn1.AnnotatedType(rust_field_type, annotation) + + +def _annotate_fields( + raw_fields: dict[str, type], +) -> dict[str, declarative_asn1.AnnotatedType]: + fields = {} + for field_name, field_type in raw_fields.items(): + # Recursively normalize the field type into something that the + # Rust code can understand. + annotated_field_type = _normalize_field_type(field_type, field_name) + fields[field_name] = annotated_field_type + + return fields + + +def _register_asn1_sequence(cls: type[U]) -> None: + raw_fields = get_type_hints(cls, include_extras=True) + root = declarative_asn1.AnnotatedType( + declarative_asn1.Type.Sequence(cls, _annotate_fields(raw_fields)), + declarative_asn1.Annotation(), + ) + + setattr(cls, "__asn1_root__", root) + + +# Due to https://github.com/python/mypy/issues/19731, we can't define an alias +# for `dataclass_transform` that conditionally points to `typing` or +# `typing_extensions` depending on the Python version (like we do for +# `get_type_hints`). +# We work around it by making the whole decorated class conditional on the +# Python version. +if sys.version_info < (3, 11): + + @typing_extensions.dataclass_transform(kw_only_default=True) + def sequence(cls: type[U]) -> type[U]: + # We use `dataclasses.dataclass` to add an __init__ method + # to the class with keyword-only parameters. + if sys.version_info >= (3, 10): + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + # `match_args` was added in Python 3.10 and defaults + # to True + match_args=False, + # `kw_only` was added in Python 3.10 and defaults to + # False + kw_only=True, + )(cls) + else: + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + )(cls) + _register_asn1_sequence(dataclass_cls) + return dataclass_cls + +else: + + @typing.dataclass_transform(kw_only_default=True) + def sequence(cls: type[U]) -> type[U]: + # Only add an __init__ method, with keyword-only + # parameters. + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + _register_asn1_sequence(dataclass_cls) + return dataclass_cls diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc index 3d6578df..0b77f63a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc index be352723..9d0a07ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/aead.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/aead.cpython-312.pyc deleted file mode 100644 index ab26fdd8..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/aead.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc index 1282ae3b..cdf6b898 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/ciphers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/ciphers.cpython-312.pyc deleted file mode 100644 index 1be2e05b..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/ciphers.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/cmac.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/cmac.cpython-312.pyc deleted file mode 100644 index 8266324c..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/cmac.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/decode_asn1.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/decode_asn1.cpython-312.pyc deleted file mode 100644 index ded76cda..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/decode_asn1.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/ec.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/ec.cpython-312.pyc deleted file mode 100644 index ce01d9ca..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/ec.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/rsa.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/rsa.cpython-312.pyc deleted file mode 100644 index f341cff7..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/rsa.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/utils.cpython-312.pyc deleted file mode 100644 index 947ff333..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/utils.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/aead.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/aead.py deleted file mode 100644 index b36f535f..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/aead.py +++ /dev/null @@ -1,527 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import InvalidTag - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - from cryptography.hazmat.primitives.ciphers.aead import ( - AESCCM, - AESGCM, - AESOCB3, - AESSIV, - ChaCha20Poly1305, - ) - - _AEADTypes = typing.Union[ - AESCCM, AESGCM, AESOCB3, AESSIV, ChaCha20Poly1305 - ] - - -def _is_evp_aead_supported_cipher( - backend: Backend, cipher: _AEADTypes -) -> bool: - """ - Checks whether the given cipher is supported through - EVP_AEAD rather than the normal OpenSSL EVP_CIPHER API. - """ - from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 - - return backend._lib.Cryptography_HAS_EVP_AEAD and isinstance( - cipher, ChaCha20Poly1305 - ) - - -def _aead_cipher_supported(backend: Backend, cipher: _AEADTypes) -> bool: - if _is_evp_aead_supported_cipher(backend, cipher): - return True - else: - cipher_name = _evp_cipher_cipher_name(cipher) - if backend._fips_enabled and cipher_name not in backend._fips_aead: - return False - # SIV isn't loaded through get_cipherbyname but instead a new fetch API - # only available in 3.0+. But if we know we're on 3.0+ then we know - # it's supported. - if cipher_name.endswith(b"-siv"): - return backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER == 1 - else: - return ( - backend._lib.EVP_get_cipherbyname(cipher_name) - != backend._ffi.NULL - ) - - -def _aead_create_ctx( - backend: Backend, - cipher: _AEADTypes, - key: bytes, -): - if _is_evp_aead_supported_cipher(backend, cipher): - return _evp_aead_create_ctx(backend, cipher, key) - else: - return _evp_cipher_create_ctx(backend, cipher, key) - - -def _encrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any = None, -) -> bytes: - if _is_evp_aead_supported_cipher(backend, cipher): - return _evp_aead_encrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - else: - return _evp_cipher_encrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - - -def _decrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any = None, -) -> bytes: - if _is_evp_aead_supported_cipher(backend, cipher): - return _evp_aead_decrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - else: - return _evp_cipher_decrypt( - backend, cipher, nonce, data, associated_data, tag_length, ctx - ) - - -def _evp_aead_create_ctx( - backend: Backend, - cipher: _AEADTypes, - key: bytes, - tag_len: typing.Optional[int] = None, -): - aead_cipher = _evp_aead_get_cipher(backend, cipher) - assert aead_cipher is not None - key_ptr = backend._ffi.from_buffer(key) - tag_len = ( - backend._lib.EVP_AEAD_DEFAULT_TAG_LENGTH - if tag_len is None - else tag_len - ) - ctx = backend._lib.Cryptography_EVP_AEAD_CTX_new( - aead_cipher, key_ptr, len(key), tag_len - ) - backend.openssl_assert(ctx != backend._ffi.NULL) - ctx = backend._ffi.gc(ctx, backend._lib.EVP_AEAD_CTX_free) - return ctx - - -def _evp_aead_get_cipher(backend: Backend, cipher: _AEADTypes): - from cryptography.hazmat.primitives.ciphers.aead import ( - ChaCha20Poly1305, - ) - - # Currently only ChaCha20-Poly1305 is supported using this API - assert isinstance(cipher, ChaCha20Poly1305) - return backend._lib.EVP_aead_chacha20_poly1305() - - -def _evp_aead_encrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any, -) -> bytes: - assert ctx is not None - - aead_cipher = _evp_aead_get_cipher(backend, cipher) - assert aead_cipher is not None - - out_len = backend._ffi.new("size_t *") - # max_out_len should be in_len plus the result of - # EVP_AEAD_max_overhead. - max_out_len = len(data) + backend._lib.EVP_AEAD_max_overhead(aead_cipher) - out_buf = backend._ffi.new("uint8_t[]", max_out_len) - data_ptr = backend._ffi.from_buffer(data) - nonce_ptr = backend._ffi.from_buffer(nonce) - aad = b"".join(associated_data) - aad_ptr = backend._ffi.from_buffer(aad) - - res = backend._lib.EVP_AEAD_CTX_seal( - ctx, - out_buf, - out_len, - max_out_len, - nonce_ptr, - len(nonce), - data_ptr, - len(data), - aad_ptr, - len(aad), - ) - backend.openssl_assert(res == 1) - encrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] - return encrypted_data - - -def _evp_aead_decrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any, -) -> bytes: - if len(data) < tag_length: - raise InvalidTag - - assert ctx is not None - - out_len = backend._ffi.new("size_t *") - # max_out_len should at least in_len - max_out_len = len(data) - out_buf = backend._ffi.new("uint8_t[]", max_out_len) - data_ptr = backend._ffi.from_buffer(data) - nonce_ptr = backend._ffi.from_buffer(nonce) - aad = b"".join(associated_data) - aad_ptr = backend._ffi.from_buffer(aad) - - res = backend._lib.EVP_AEAD_CTX_open( - ctx, - out_buf, - out_len, - max_out_len, - nonce_ptr, - len(nonce), - data_ptr, - len(data), - aad_ptr, - len(aad), - ) - - if res == 0: - backend._consume_errors() - raise InvalidTag - - decrypted_data = backend._ffi.buffer(out_buf, out_len[0])[:] - return decrypted_data - - -_ENCRYPT = 1 -_DECRYPT = 0 - - -def _evp_cipher_cipher_name(cipher: _AEADTypes) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import ( - AESCCM, - AESGCM, - AESOCB3, - AESSIV, - ChaCha20Poly1305, - ) - - if isinstance(cipher, ChaCha20Poly1305): - return b"chacha20-poly1305" - elif isinstance(cipher, AESCCM): - return f"aes-{len(cipher._key) * 8}-ccm".encode("ascii") - elif isinstance(cipher, AESOCB3): - return f"aes-{len(cipher._key) * 8}-ocb".encode("ascii") - elif isinstance(cipher, AESSIV): - return f"aes-{len(cipher._key) * 8 // 2}-siv".encode("ascii") - else: - assert isinstance(cipher, AESGCM) - return f"aes-{len(cipher._key) * 8}-gcm".encode("ascii") - - -def _evp_cipher(cipher_name: bytes, backend: Backend): - if cipher_name.endswith(b"-siv"): - evp_cipher = backend._lib.EVP_CIPHER_fetch( - backend._ffi.NULL, - cipher_name, - backend._ffi.NULL, - ) - backend.openssl_assert(evp_cipher != backend._ffi.NULL) - evp_cipher = backend._ffi.gc(evp_cipher, backend._lib.EVP_CIPHER_free) - else: - evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name) - backend.openssl_assert(evp_cipher != backend._ffi.NULL) - - return evp_cipher - - -def _evp_cipher_create_ctx( - backend: Backend, - cipher: _AEADTypes, - key: bytes, -): - ctx = backend._lib.EVP_CIPHER_CTX_new() - backend.openssl_assert(ctx != backend._ffi.NULL) - ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) - cipher_name = _evp_cipher_cipher_name(cipher) - evp_cipher = _evp_cipher(cipher_name, backend) - key_ptr = backend._ffi.from_buffer(key) - res = backend._lib.EVP_CipherInit_ex( - ctx, - evp_cipher, - backend._ffi.NULL, - key_ptr, - backend._ffi.NULL, - 0, - ) - backend.openssl_assert(res != 0) - return ctx - - -def _evp_cipher_aead_setup( - backend: Backend, - cipher_name: bytes, - key: bytes, - nonce: bytes, - tag: typing.Optional[bytes], - tag_len: int, - operation: int, -): - evp_cipher = _evp_cipher(cipher_name, backend) - ctx = backend._lib.EVP_CIPHER_CTX_new() - ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) - res = backend._lib.EVP_CipherInit_ex( - ctx, - evp_cipher, - backend._ffi.NULL, - backend._ffi.NULL, - backend._ffi.NULL, - int(operation == _ENCRYPT), - ) - backend.openssl_assert(res != 0) - # CCM requires the IVLEN to be set before calling SET_TAG on decrypt - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - backend._lib.EVP_CTRL_AEAD_SET_IVLEN, - len(nonce), - backend._ffi.NULL, - ) - backend.openssl_assert(res != 0) - if operation == _DECRYPT: - assert tag is not None - _evp_cipher_set_tag(backend, ctx, tag) - elif cipher_name.endswith(b"-ccm"): - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - backend._lib.EVP_CTRL_AEAD_SET_TAG, - tag_len, - backend._ffi.NULL, - ) - backend.openssl_assert(res != 0) - - nonce_ptr = backend._ffi.from_buffer(nonce) - key_ptr = backend._ffi.from_buffer(key) - res = backend._lib.EVP_CipherInit_ex( - ctx, - backend._ffi.NULL, - backend._ffi.NULL, - key_ptr, - nonce_ptr, - int(operation == _ENCRYPT), - ) - backend.openssl_assert(res != 0) - return ctx - - -def _evp_cipher_set_tag(backend, ctx, tag: bytes) -> None: - tag_ptr = backend._ffi.from_buffer(tag) - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag_ptr - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_set_nonce_operation( - backend, ctx, nonce: bytes, operation: int -) -> None: - nonce_ptr = backend._ffi.from_buffer(nonce) - res = backend._lib.EVP_CipherInit_ex( - ctx, - backend._ffi.NULL, - backend._ffi.NULL, - backend._ffi.NULL, - nonce_ptr, - int(operation == _ENCRYPT), - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_set_length(backend: Backend, ctx, data_len: int) -> None: - intptr = backend._ffi.new("int *") - res = backend._lib.EVP_CipherUpdate( - ctx, backend._ffi.NULL, intptr, backend._ffi.NULL, data_len - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_process_aad( - backend: Backend, ctx, associated_data: bytes -) -> None: - outlen = backend._ffi.new("int *") - a_data_ptr = backend._ffi.from_buffer(associated_data) - res = backend._lib.EVP_CipherUpdate( - ctx, backend._ffi.NULL, outlen, a_data_ptr, len(associated_data) - ) - backend.openssl_assert(res != 0) - - -def _evp_cipher_process_data(backend: Backend, ctx, data: bytes) -> bytes: - outlen = backend._ffi.new("int *") - buf = backend._ffi.new("unsigned char[]", len(data)) - data_ptr = backend._ffi.from_buffer(data) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data_ptr, len(data)) - if res == 0: - # AES SIV can error here if the data is invalid on decrypt - backend._consume_errors() - raise InvalidTag - return backend._ffi.buffer(buf, outlen[0])[:] - - -def _evp_cipher_encrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any = None, -) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV - - if ctx is None: - cipher_name = _evp_cipher_cipher_name(cipher) - ctx = _evp_cipher_aead_setup( - backend, - cipher_name, - cipher._key, - nonce, - None, - tag_length, - _ENCRYPT, - ) - else: - _evp_cipher_set_nonce_operation(backend, ctx, nonce, _ENCRYPT) - - # CCM requires us to pass the length of the data before processing - # anything. - # However calling this with any other AEAD results in an error - if isinstance(cipher, AESCCM): - _evp_cipher_set_length(backend, ctx, len(data)) - - for ad in associated_data: - _evp_cipher_process_aad(backend, ctx, ad) - processed_data = _evp_cipher_process_data(backend, ctx, data) - outlen = backend._ffi.new("int *") - # All AEADs we support besides OCB are streaming so they return nothing - # in finalization. OCB can return up to (16 byte block - 1) bytes so - # we need a buffer here too. - buf = backend._ffi.new("unsigned char[]", 16) - res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen) - backend.openssl_assert(res != 0) - processed_data += backend._ffi.buffer(buf, outlen[0])[:] - tag_buf = backend._ffi.new("unsigned char[]", tag_length) - res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_GET_TAG, tag_length, tag_buf - ) - backend.openssl_assert(res != 0) - tag = backend._ffi.buffer(tag_buf)[:] - - if isinstance(cipher, AESSIV): - # RFC 5297 defines the output as IV || C, where the tag we generate - # is the "IV" and C is the ciphertext. This is the opposite of our - # other AEADs, which are Ciphertext || Tag - backend.openssl_assert(len(tag) == 16) - return tag + processed_data - else: - return processed_data + tag - - -def _evp_cipher_decrypt( - backend: Backend, - cipher: _AEADTypes, - nonce: bytes, - data: bytes, - associated_data: typing.List[bytes], - tag_length: int, - ctx: typing.Any = None, -) -> bytes: - from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV - - if len(data) < tag_length: - raise InvalidTag - - if isinstance(cipher, AESSIV): - # RFC 5297 defines the output as IV || C, where the tag we generate - # is the "IV" and C is the ciphertext. This is the opposite of our - # other AEADs, which are Ciphertext || Tag - tag = data[:tag_length] - data = data[tag_length:] - else: - tag = data[-tag_length:] - data = data[:-tag_length] - if ctx is None: - cipher_name = _evp_cipher_cipher_name(cipher) - ctx = _evp_cipher_aead_setup( - backend, - cipher_name, - cipher._key, - nonce, - tag, - tag_length, - _DECRYPT, - ) - else: - _evp_cipher_set_nonce_operation(backend, ctx, nonce, _DECRYPT) - _evp_cipher_set_tag(backend, ctx, tag) - - # CCM requires us to pass the length of the data before processing - # anything. - # However calling this with any other AEAD results in an error - if isinstance(cipher, AESCCM): - _evp_cipher_set_length(backend, ctx, len(data)) - - for ad in associated_data: - _evp_cipher_process_aad(backend, ctx, ad) - # CCM has a different error path if the tag doesn't match. Errors are - # raised in Update and Final is irrelevant. - if isinstance(cipher, AESCCM): - outlen = backend._ffi.new("int *") - buf = backend._ffi.new("unsigned char[]", len(data)) - d_ptr = backend._ffi.from_buffer(data) - res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, d_ptr, len(data)) - if res != 1: - backend._consume_errors() - raise InvalidTag - - processed_data = backend._ffi.buffer(buf, outlen[0])[:] - else: - processed_data = _evp_cipher_process_data(backend, ctx, data) - outlen = backend._ffi.new("int *") - # OCB can return up to 15 bytes (16 byte block - 1) in finalization - buf = backend._ffi.new("unsigned char[]", 16) - res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen) - processed_data += backend._ffi.buffer(buf, outlen[0])[:] - if res == 0: - backend._consume_errors() - raise InvalidTag - - return processed_data diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/backend.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/backend.py index f1c79008..248b8c52 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/backend.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/backend.py @@ -4,93 +4,28 @@ from __future__ import annotations -import collections -import contextlib -import itertools -import typing -from contextlib import contextmanager - -from cryptography import utils, x509 -from cryptography.exceptions import UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.backends.openssl import aead -from cryptography.hazmat.backends.openssl.ciphers import _CipherContext -from cryptography.hazmat.backends.openssl.cmac import _CMACContext -from cryptography.hazmat.backends.openssl.ec import ( - _EllipticCurvePrivateKey, - _EllipticCurvePublicKey, -) -from cryptography.hazmat.backends.openssl.rsa import ( - _RSAPrivateKey, - _RSAPublicKey, -) from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.bindings.openssl import binding -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding -from cryptography.hazmat.primitives.asymmetric import ( - dh, - dsa, - ec, - ed448, - ed25519, - rsa, - x448, - x25519, -) +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.asymmetric.padding import ( MGF1, OAEP, PSS, PKCS1v15, ) -from cryptography.hazmat.primitives.asymmetric.types import ( - PrivateKeyTypes, - PublicKeyTypes, -) from cryptography.hazmat.primitives.ciphers import ( - BlockCipherAlgorithm, CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, - AES128, - AES256, - ARC4, - SM4, - Camellia, - ChaCha20, - TripleDES, - _BlowfishInternal, - _CAST5Internal, - _IDEAInternal, - _SEEDInternal, ) from cryptography.hazmat.primitives.ciphers.modes import ( CBC, - CFB, - CFB8, - CTR, - ECB, - GCM, - OFB, - XTS, Mode, ) -from cryptography.hazmat.primitives.serialization import ssh -from cryptography.hazmat.primitives.serialization.pkcs12 import ( - PBES, - PKCS12Certificate, - PKCS12KeyAndCertificates, - PKCS12PrivateKeyTypes, - _PKCS12CATypes, -) - -_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) - - -# Not actually supported, just used as a marker for some serialization tests. -class _RC2: - pass class Backend: @@ -100,18 +35,6 @@ class Backend: name = "openssl" - # FIPS has opinions about acceptable algorithms and key sizes, but the - # disallowed algorithms are still present in OpenSSL. They just error if - # you try to use them. To avoid that we allowlist the algorithms in - # FIPS 140-3. This isn't ideal, but FIPS 140-3 is trash so here we are. - _fips_aead = { - b"aes-128-ccm", - b"aes-192-ccm", - b"aes-256-ccm", - b"aes-128-gcm", - b"aes-192-gcm", - b"aes-256-gcm", - } # TripleDES encryption is disallowed/deprecated throughout 2023 in # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA). _fips_ciphers = (AES,) @@ -149,33 +72,20 @@ class Backend: self._lib = self._binding.lib self._fips_enabled = rust_openssl.is_fips_enabled() - self._cipher_registry: typing.Dict[ - typing.Tuple[typing.Type[CipherAlgorithm], typing.Type[Mode]], - typing.Callable, - ] = {} - self._register_default_ciphers() - self._dh_types = [self._lib.EVP_PKEY_DH] - if self._lib.Cryptography_HAS_EVP_PKEY_DHX: - self._dh_types.append(self._lib.EVP_PKEY_DHX) - def __repr__(self) -> str: - return "".format( - self.openssl_version_text(), - self._fips_enabled, - self._binding._legacy_provider_loaded, + return ( + f"" ) - def openssl_assert( - self, - ok: bool, - errors: typing.Optional[typing.List[rust_openssl.OpenSSLError]] = None, - ) -> None: - return binding._openssl_assert(self._lib, ok, errors=errors) + def openssl_assert(self, ok: bool) -> None: + return binding._openssl_assert(ok) def _enable_fips(self) -> None: # This function enables FIPS mode for OpenSSL 3.0.0 on installs that # have the FIPS provider installed properly. - self._binding._enable_fips() + rust_openssl.enable_fips(rust_openssl._providers) assert rust_openssl.is_fips_enabled() self._fips_enabled = rust_openssl.is_fips_enabled() @@ -184,37 +94,18 @@ class Backend: Friendly string name of the loaded OpenSSL library. This is not necessarily the same version as it was compiled against. - Example: OpenSSL 1.1.1d 10 Sep 2019 + Example: OpenSSL 3.2.1 30 Jan 2024 """ - return self._ffi.string( - self._lib.OpenSSL_version(self._lib.OPENSSL_VERSION) - ).decode("ascii") + return rust_openssl.openssl_version_text() def openssl_version_number(self) -> int: - return self._lib.OpenSSL_version_num() - - def _evp_md_from_algorithm(self, algorithm: hashes.HashAlgorithm): - if algorithm.name == "blake2b" or algorithm.name == "blake2s": - alg = "{}{}".format( - algorithm.name, algorithm.digest_size * 8 - ).encode("ascii") - else: - alg = algorithm.name.encode("ascii") - - evp_md = self._lib.EVP_get_digestbyname(alg) - return evp_md - - def _evp_md_non_null_from_algorithm(self, algorithm: hashes.HashAlgorithm): - evp_md = self._evp_md_from_algorithm(algorithm) - self.openssl_assert(evp_md != self._ffi.NULL) - return evp_md + return rust_openssl.openssl_version() def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): return False - evp_md = self._evp_md_from_algorithm(algorithm) - return evp_md != self._ffi.NULL + return rust_openssl.hashes.hash_supported(algorithm) def signature_hash_supported( self, algorithm: hashes.HashAlgorithm @@ -229,13 +120,31 @@ class Backend: if self._fips_enabled: return False else: - return self._lib.Cryptography_HAS_SCRYPT == 1 + return hasattr(rust_openssl.kdf.Scrypt, "derive") + + def argon2_supported(self) -> bool: + if self._fips_enabled: + return False + else: + return hasattr(rust_openssl.kdf.Argon2id, "derive") def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: # FIPS mode still allows SHA1 for HMAC if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return True - + if rust_openssl.CRYPTOGRAPHY_IS_AWSLC: + return isinstance( + algorithm, + ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + ), + ) return self.hash_supported(algorithm) def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: @@ -245,404 +154,14 @@ class Backend: if not isinstance(cipher, self._fips_ciphers): return False - try: - adapter = self._cipher_registry[type(cipher), type(mode)] - except KeyError: - return False - evp_cipher = adapter(self, cipher, mode) - return self._ffi.NULL != evp_cipher - - def register_cipher_adapter(self, cipher_cls, mode_cls, adapter) -> None: - if (cipher_cls, mode_cls) in self._cipher_registry: - raise ValueError( - "Duplicate registration for: {} {}.".format( - cipher_cls, mode_cls - ) - ) - self._cipher_registry[cipher_cls, mode_cls] = adapter - - def _register_default_ciphers(self) -> None: - for cipher_cls in [AES, AES128, AES256]: - for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8, GCM]: - self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName( - "{cipher.name}-{cipher.key_size}-{mode.name}" - ), - ) - for mode_cls in [CBC, CTR, ECB, OFB, CFB]: - self.register_cipher_adapter( - Camellia, - mode_cls, - GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}"), - ) - for mode_cls in [CBC, CFB, CFB8, OFB]: - self.register_cipher_adapter( - TripleDES, mode_cls, GetCipherByName("des-ede3-{mode.name}") - ) - self.register_cipher_adapter( - TripleDES, ECB, GetCipherByName("des-ede3") - ) - self.register_cipher_adapter( - ChaCha20, type(None), GetCipherByName("chacha20") - ) - self.register_cipher_adapter(AES, XTS, _get_xts_cipher) - for mode_cls in [ECB, CBC, OFB, CFB, CTR]: - self.register_cipher_adapter( - SM4, mode_cls, GetCipherByName("sm4-{mode.name}") - ) - # Don't register legacy ciphers if they're unavailable. Hypothetically - # this wouldn't be necessary because we test availability by seeing if - # we get an EVP_CIPHER * in the _CipherContext __init__, but OpenSSL 3 - # will return a valid pointer even though the cipher is unavailable. - if ( - self._binding._legacy_provider_loaded - or not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER - ): - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - _BlowfishInternal, - mode_cls, - GetCipherByName("bf-{mode.name}"), - ) - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - _SEEDInternal, - mode_cls, - GetCipherByName("seed-{mode.name}"), - ) - for cipher_cls, mode_cls in itertools.product( - [_CAST5Internal, _IDEAInternal], - [CBC, OFB, CFB, ECB], - ): - self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName("{cipher.name}-{mode.name}"), - ) - self.register_cipher_adapter( - ARC4, type(None), GetCipherByName("rc4") - ) - # We don't actually support RC2, this is just used by some tests. - self.register_cipher_adapter( - _RC2, type(None), GetCipherByName("rc2") - ) - - def create_symmetric_encryption_ctx( - self, cipher: CipherAlgorithm, mode: Mode - ) -> _CipherContext: - return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) - - def create_symmetric_decryption_ctx( - self, cipher: CipherAlgorithm, mode: Mode - ) -> _CipherContext: - return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + return rust_openssl.ciphers.cipher_supported(cipher, mode) def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: return self.hmac_supported(algorithm) - def _consume_errors(self) -> typing.List[rust_openssl.OpenSSLError]: + def _consume_errors(self) -> list[rust_openssl.OpenSSLError]: return rust_openssl.capture_error_stack() - def _bn_to_int(self, bn) -> int: - assert bn != self._ffi.NULL - self.openssl_assert(not self._lib.BN_is_negative(bn)) - - bn_num_bytes = self._lib.BN_num_bytes(bn) - bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes) - bin_len = self._lib.BN_bn2bin(bn, bin_ptr) - # A zero length means the BN has value 0 - self.openssl_assert(bin_len >= 0) - val = int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") - return val - - def _int_to_bn(self, num: int): - """ - Converts a python integer to a BIGNUM. The returned BIGNUM will not - be garbage collected (to support adding them to structs that take - ownership of the object). Be sure to register it for GC if it will - be discarded after use. - """ - binary = num.to_bytes(int(num.bit_length() / 8.0 + 1), "big") - bn_ptr = self._lib.BN_bin2bn(binary, len(binary), self._ffi.NULL) - self.openssl_assert(bn_ptr != self._ffi.NULL) - return bn_ptr - - def generate_rsa_private_key( - self, public_exponent: int, key_size: int - ) -> rsa.RSAPrivateKey: - rsa._verify_rsa_parameters(public_exponent, key_size) - - rsa_cdata = self._lib.RSA_new() - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - - bn = self._int_to_bn(public_exponent) - bn = self._ffi.gc(bn, self._lib.BN_free) - - res = self._lib.RSA_generate_key_ex( - rsa_cdata, key_size, bn, self._ffi.NULL - ) - self.openssl_assert(res == 1) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - - # We can skip RSA key validation here since we just generated the key - return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, unsafe_skip_rsa_key_validation=True - ) - - def generate_rsa_parameters_supported( - self, public_exponent: int, key_size: int - ) -> bool: - return ( - public_exponent >= 3 - and public_exponent & 1 != 0 - and key_size >= 512 - ) - - def load_rsa_private_numbers( - self, - numbers: rsa.RSAPrivateNumbers, - unsafe_skip_rsa_key_validation: bool, - ) -> rsa.RSAPrivateKey: - rsa._check_private_key_components( - numbers.p, - numbers.q, - numbers.d, - numbers.dmp1, - numbers.dmq1, - numbers.iqmp, - numbers.public_numbers.e, - numbers.public_numbers.n, - ) - rsa_cdata = self._lib.RSA_new() - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - p = self._int_to_bn(numbers.p) - q = self._int_to_bn(numbers.q) - d = self._int_to_bn(numbers.d) - dmp1 = self._int_to_bn(numbers.dmp1) - dmq1 = self._int_to_bn(numbers.dmq1) - iqmp = self._int_to_bn(numbers.iqmp) - e = self._int_to_bn(numbers.public_numbers.e) - n = self._int_to_bn(numbers.public_numbers.n) - res = self._lib.RSA_set0_factors(rsa_cdata, p, q) - self.openssl_assert(res == 1) - res = self._lib.RSA_set0_key(rsa_cdata, n, e, d) - self.openssl_assert(res == 1) - res = self._lib.RSA_set0_crt_params(rsa_cdata, dmp1, dmq1, iqmp) - self.openssl_assert(res == 1) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - - return _RSAPrivateKey( - self, - rsa_cdata, - evp_pkey, - unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, - ) - - def load_rsa_public_numbers( - self, numbers: rsa.RSAPublicNumbers - ) -> rsa.RSAPublicKey: - rsa._check_public_key_components(numbers.e, numbers.n) - rsa_cdata = self._lib.RSA_new() - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - e = self._int_to_bn(numbers.e) - n = self._int_to_bn(numbers.n) - res = self._lib.RSA_set0_key(rsa_cdata, n, e, self._ffi.NULL) - self.openssl_assert(res == 1) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - - def _create_evp_pkey_gc(self): - evp_pkey = self._lib.EVP_PKEY_new() - self.openssl_assert(evp_pkey != self._ffi.NULL) - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return evp_pkey - - def _rsa_cdata_to_evp_pkey(self, rsa_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_RSA(evp_pkey, rsa_cdata) - self.openssl_assert(res == 1) - return evp_pkey - - def _bytes_to_bio(self, data: bytes) -> _MemoryBIO: - """ - Return a _MemoryBIO namedtuple of (BIO, char*). - - The char* is the storage for the BIO and it must stay alive until the - BIO is finished with. - """ - data_ptr = self._ffi.from_buffer(data) - bio = self._lib.BIO_new_mem_buf(data_ptr, len(data)) - self.openssl_assert(bio != self._ffi.NULL) - - return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_ptr) - - def _create_mem_bio_gc(self): - """ - Creates an empty memory BIO. - """ - bio_method = self._lib.BIO_s_mem() - self.openssl_assert(bio_method != self._ffi.NULL) - bio = self._lib.BIO_new(bio_method) - self.openssl_assert(bio != self._ffi.NULL) - bio = self._ffi.gc(bio, self._lib.BIO_free) - return bio - - def _read_mem_bio(self, bio) -> bytes: - """ - Reads a memory BIO. This only works on memory BIOs. - """ - buf = self._ffi.new("char **") - buf_len = self._lib.BIO_get_mem_data(bio, buf) - self.openssl_assert(buf_len > 0) - self.openssl_assert(buf[0] != self._ffi.NULL) - bio_data = self._ffi.buffer(buf[0], buf_len)[:] - return bio_data - - def _evp_pkey_to_private_key( - self, evp_pkey, unsafe_skip_rsa_key_validation: bool - ) -> PrivateKeyTypes: - """ - Return the appropriate type of PrivateKey given an evp_pkey cdata - pointer. - """ - - key_type = self._lib.EVP_PKEY_id(evp_pkey) - - if key_type == self._lib.EVP_PKEY_RSA: - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - return _RSAPrivateKey( - self, - rsa_cdata, - evp_pkey, - unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, - ) - elif ( - key_type == self._lib.EVP_PKEY_RSA_PSS - and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ): - # At the moment the way we handle RSA PSS keys is to strip the - # PSS constraints from them and treat them as normal RSA keys - # Unfortunately the RSA * itself tracks this data so we need to - # extract, serialize, and reload it without the constraints. - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - bio = self._create_mem_bio_gc() - res = self._lib.i2d_RSAPrivateKey_bio(bio, rsa_cdata) - self.openssl_assert(res == 1) - return self.load_der_private_key( - self._read_mem_bio(bio), - password=None, - unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, - ) - elif key_type == self._lib.EVP_PKEY_DSA: - return rust_openssl.dsa.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_EC: - ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) - self.openssl_assert(ec_cdata != self._ffi.NULL) - ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - elif key_type in self._dh_types: - return rust_openssl.dh.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed25519.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.x448.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_X25519: - return rust_openssl.x25519.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed448.private_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - else: - raise UnsupportedAlgorithm("Unsupported key type.") - - def _evp_pkey_to_public_key(self, evp_pkey) -> PublicKeyTypes: - """ - Return the appropriate type of PublicKey given an evp_pkey cdata - pointer. - """ - - key_type = self._lib.EVP_PKEY_id(evp_pkey) - - if key_type == self._lib.EVP_PKEY_RSA: - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - elif ( - key_type == self._lib.EVP_PKEY_RSA_PSS - and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E - ): - rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) - self.openssl_assert(rsa_cdata != self._ffi.NULL) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - bio = self._create_mem_bio_gc() - res = self._lib.i2d_RSAPublicKey_bio(bio, rsa_cdata) - self.openssl_assert(res == 1) - return self.load_der_public_key(self._read_mem_bio(bio)) - elif key_type == self._lib.EVP_PKEY_DSA: - return rust_openssl.dsa.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_EC: - ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) - if ec_cdata == self._ffi.NULL: - errors = self._consume_errors() - raise ValueError("Unable to load EC key", errors) - ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) - return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - elif key_type in self._dh_types: - return rust_openssl.dh.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): - # EVP_PKEY_ED25519 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed25519.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): - # EVP_PKEY_X448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.x448.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == self._lib.EVP_PKEY_X25519: - return rust_openssl.x25519.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): - # EVP_PKEY_ED448 is not present in CRYPTOGRAPHY_IS_LIBRESSL - return rust_openssl.ed448.public_key_from_ptr( - int(self._ffi.cast("uintptr_t", evp_pkey)) - ) - else: - raise UnsupportedAlgorithm("Unsupported key type.") - def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: if self._fips_enabled and isinstance(algorithm, hashes.SHA1): return False @@ -662,14 +181,17 @@ class Backend: if isinstance(padding, PKCS1v15): return True elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): - # SHA1 is permissible in MGF1 in FIPS even when SHA1 is blocked - # as signature algorithm. - if self._fips_enabled and isinstance( - padding._mgf._algorithm, hashes.SHA1 + # FIPS 186-4 only allows salt length == digest length for PSS + # It is technically acceptable to set an explicit salt length + # equal to the digest length and this will incorrectly fail, but + # since we don't do that in the tests and this method is + # private, we'll ignore that until we need to do otherwise. + if ( + self._fips_enabled + and padding._salt_length != PSS.DIGEST_LENGTH ): - return True - else: - return self.hash_supported(padding._mgf._algorithm) + return False + return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): return self._oaep_hash_supported( padding._mgf._algorithm @@ -683,46 +205,10 @@ class Backend: else: return self.rsa_padding_supported(padding) - def generate_dsa_parameters(self, key_size: int) -> dsa.DSAParameters: - if key_size not in (1024, 2048, 3072, 4096): - raise ValueError( - "Key size must be 1024, 2048, 3072, or 4096 bits." - ) - - return rust_openssl.dsa.generate_parameters(key_size) - - def generate_dsa_private_key( - self, parameters: dsa.DSAParameters - ) -> dsa.DSAPrivateKey: - return parameters.generate_private_key() - - def generate_dsa_private_key_and_parameters( - self, key_size: int - ) -> dsa.DSAPrivateKey: - parameters = self.generate_dsa_parameters(key_size) - return self.generate_dsa_private_key(parameters) - - def load_dsa_private_numbers( - self, numbers: dsa.DSAPrivateNumbers - ) -> dsa.DSAPrivateKey: - dsa._check_dsa_private_numbers(numbers) - return rust_openssl.dsa.from_private_numbers(numbers) - - def load_dsa_public_numbers( - self, numbers: dsa.DSAPublicNumbers - ) -> dsa.DSAPublicKey: - dsa._check_dsa_parameters(numbers.parameter_numbers) - return rust_openssl.dsa.from_public_numbers(numbers) - - def load_dsa_parameter_numbers( - self, numbers: dsa.DSAParameterNumbers - ) -> dsa.DSAParameters: - dsa._check_dsa_parameters(numbers) - return rust_openssl.dsa.from_parameter_numbers(numbers) - def dsa_supported(self) -> bool: return ( - not self._lib.CRYPTOGRAPHY_IS_BORINGSSL and not self._fips_enabled + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not self._fips_enabled ) def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: @@ -735,275 +221,13 @@ class Backend: algorithm, CBC(b"\x00" * algorithm.block_size) ) - def create_cmac_ctx(self, algorithm: BlockCipherAlgorithm) -> _CMACContext: - return _CMACContext(self, algorithm) - - def load_pem_private_key( - self, - data: bytes, - password: typing.Optional[bytes], - unsafe_skip_rsa_key_validation: bool, - ) -> PrivateKeyTypes: - return self._load_key( - self._lib.PEM_read_bio_PrivateKey, - data, - password, - unsafe_skip_rsa_key_validation, - ) - - def load_pem_public_key(self, data: bytes) -> PublicKeyTypes: - mem_bio = self._bytes_to_bio(data) - # In OpenSSL 3.0.x the PEM_read_bio_PUBKEY function will invoke - # the default password callback if you pass an encrypted private - # key. This is very, very, very bad as the default callback can - # trigger an interactive console prompt, which will hang the - # Python process. We therefore provide our own callback to - # catch this and error out properly. - userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") - evp_pkey = self._lib.PEM_read_bio_PUBKEY( - mem_bio.bio, - self._ffi.NULL, - self._ffi.addressof( - self._lib._original_lib, "Cryptography_pem_password_cb" - ), - userdata, - ) - if evp_pkey != self._ffi.NULL: - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return self._evp_pkey_to_public_key(evp_pkey) - else: - # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still - # need to check to see if it is a pure PKCS1 RSA public key (not - # embedded in a subjectPublicKeyInfo) - self._consume_errors() - res = self._lib.BIO_reset(mem_bio.bio) - self.openssl_assert(res == 1) - rsa_cdata = self._lib.PEM_read_bio_RSAPublicKey( - mem_bio.bio, - self._ffi.NULL, - self._ffi.addressof( - self._lib._original_lib, "Cryptography_pem_password_cb" - ), - userdata, - ) - if rsa_cdata != self._ffi.NULL: - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - else: - self._handle_key_loading_error() - - def load_pem_parameters(self, data: bytes) -> dh.DHParameters: - return rust_openssl.dh.from_pem_parameters(data) - - def load_der_private_key( - self, - data: bytes, - password: typing.Optional[bytes], - unsafe_skip_rsa_key_validation: bool, - ) -> PrivateKeyTypes: - # OpenSSL has a function called d2i_AutoPrivateKey that in theory - # handles this automatically, however it doesn't handle encrypted - # private keys. Instead we try to load the key two different ways. - # First we'll try to load it as a traditional key. - bio_data = self._bytes_to_bio(data) - key = self._evp_pkey_from_der_traditional_key(bio_data, password) - if key: - return self._evp_pkey_to_private_key( - key, unsafe_skip_rsa_key_validation - ) - else: - # Finally we try to load it with the method that handles encrypted - # PKCS8 properly. - return self._load_key( - self._lib.d2i_PKCS8PrivateKey_bio, - data, - password, - unsafe_skip_rsa_key_validation, - ) - - def _evp_pkey_from_der_traditional_key(self, bio_data, password): - key = self._lib.d2i_PrivateKey_bio(bio_data.bio, self._ffi.NULL) - if key != self._ffi.NULL: - key = self._ffi.gc(key, self._lib.EVP_PKEY_free) - if password is not None: - raise TypeError( - "Password was given but private key is not encrypted." - ) - - return key - else: - self._consume_errors() - return None - - def load_der_public_key(self, data: bytes) -> PublicKeyTypes: - mem_bio = self._bytes_to_bio(data) - evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL) - if evp_pkey != self._ffi.NULL: - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - return self._evp_pkey_to_public_key(evp_pkey) - else: - # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still - # need to check to see if it is a pure PKCS1 RSA public key (not - # embedded in a subjectPublicKeyInfo) - self._consume_errors() - res = self._lib.BIO_reset(mem_bio.bio) - self.openssl_assert(res == 1) - rsa_cdata = self._lib.d2i_RSAPublicKey_bio( - mem_bio.bio, self._ffi.NULL - ) - if rsa_cdata != self._ffi.NULL: - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) - return _RSAPublicKey(self, rsa_cdata, evp_pkey) - else: - self._handle_key_loading_error() - - def load_der_parameters(self, data: bytes) -> dh.DHParameters: - return rust_openssl.dh.from_der_parameters(data) - - def _cert2ossl(self, cert: x509.Certificate) -> typing.Any: - data = cert.public_bytes(serialization.Encoding.DER) - mem_bio = self._bytes_to_bio(data) - x509 = self._lib.d2i_X509_bio(mem_bio.bio, self._ffi.NULL) - self.openssl_assert(x509 != self._ffi.NULL) - x509 = self._ffi.gc(x509, self._lib.X509_free) - return x509 - - def _ossl2cert(self, x509_ptr: typing.Any) -> x509.Certificate: - bio = self._create_mem_bio_gc() - res = self._lib.i2d_X509_bio(bio, x509_ptr) - self.openssl_assert(res == 1) - return x509.load_der_x509_certificate(self._read_mem_bio(bio)) - - def _key2ossl(self, key: PKCS12PrivateKeyTypes) -> typing.Any: - data = key.private_bytes( - serialization.Encoding.DER, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption(), - ) - mem_bio = self._bytes_to_bio(data) - - evp_pkey = self._lib.d2i_PrivateKey_bio( - mem_bio.bio, - self._ffi.NULL, - ) - self.openssl_assert(evp_pkey != self._ffi.NULL) - return self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - def _load_key( - self, openssl_read_func, data, password, unsafe_skip_rsa_key_validation - ) -> PrivateKeyTypes: - mem_bio = self._bytes_to_bio(data) - - userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") - if password is not None: - utils._check_byteslike("password", password) - password_ptr = self._ffi.from_buffer(password) - userdata.password = password_ptr - userdata.length = len(password) - - evp_pkey = openssl_read_func( - mem_bio.bio, - self._ffi.NULL, - self._ffi.addressof( - self._lib._original_lib, "Cryptography_pem_password_cb" - ), - userdata, - ) - - if evp_pkey == self._ffi.NULL: - if userdata.error != 0: - self._consume_errors() - if userdata.error == -1: - raise TypeError( - "Password was not given but private key is encrypted" - ) - else: - assert userdata.error == -2 - raise ValueError( - "Passwords longer than {} bytes are not supported " - "by this backend.".format(userdata.maxsize - 1) - ) - else: - self._handle_key_loading_error() - - evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) - - if password is not None and userdata.called == 0: - raise TypeError( - "Password was given but private key is not encrypted." - ) - - assert ( - password is not None and userdata.called == 1 - ) or password is None - - return self._evp_pkey_to_private_key( - evp_pkey, unsafe_skip_rsa_key_validation - ) - - def _handle_key_loading_error(self) -> typing.NoReturn: - errors = self._consume_errors() - - if not errors: - raise ValueError( - "Could not deserialize key data. The data may be in an " - "incorrect format or it may be encrypted with an unsupported " - "algorithm." - ) - - elif ( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT - ) - or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PKCS12, - self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, - ) - or ( - self._lib.Cryptography_HAS_PROVIDERS - and errors[0]._lib_reason_match( - self._lib.ERR_LIB_PROV, - self._lib.PROV_R_BAD_DECRYPT, - ) - ) - ): - raise ValueError("Bad decrypt. Incorrect password?") - - elif any( - error._lib_reason_match( - self._lib.ERR_LIB_EVP, - self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM, - ) - for error in errors - ): - raise ValueError("Unsupported public key algorithm.") - - else: - raise ValueError( - "Could not deserialize key data. The data may be in an " - "incorrect format, it may be encrypted with an unsupported " - "algorithm, or it may be an unsupported key type (e.g. EC " - "curves with explicit parameters).", - errors, - ) - def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: - try: - curve_nid = self._elliptic_curve_to_nid(curve) - except UnsupportedAlgorithm: - curve_nid = self._lib.NID_undef - - group = self._lib.EC_GROUP_new_by_curve_name(curve_nid) - - if group == self._ffi.NULL: - self._consume_errors() + if self._fips_enabled and not isinstance( + curve, self._fips_ecdh_curves + ): return False - else: - self.openssl_assert(curve_nid != self._lib.NID_undef) - self._lib.EC_GROUP_free(group) - return True + + return rust_openssl.ec.curve_supported(curve) def elliptic_curve_signature_algorithm_supported( self, @@ -1014,925 +238,65 @@ class Backend: if not isinstance(signature_algorithm, ec.ECDSA): return False - return self.elliptic_curve_supported(curve) - - def generate_elliptic_curve_private_key( - self, curve: ec.EllipticCurve - ) -> ec.EllipticCurvePrivateKey: - """ - Generate a new private key on the named curve. - """ - - if self.elliptic_curve_supported(curve): - ec_cdata = self._ec_key_new_by_curve(curve) - - res = self._lib.EC_KEY_generate_key(ec_cdata) - self.openssl_assert(res == 1) - - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - else: - raise UnsupportedAlgorithm( - f"Backend object does not support {curve.name}.", - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, - ) - - def load_elliptic_curve_private_numbers( - self, numbers: ec.EllipticCurvePrivateNumbers - ) -> ec.EllipticCurvePrivateKey: - public = numbers.public_numbers - - ec_cdata = self._ec_key_new_by_curve(public.curve) - - private_value = self._ffi.gc( - self._int_to_bn(numbers.private_value), self._lib.BN_clear_free + return self.elliptic_curve_supported(curve) and ( + isinstance(signature_algorithm.algorithm, asym_utils.Prehashed) + or self.hash_supported(signature_algorithm.algorithm) ) - res = self._lib.EC_KEY_set_private_key(ec_cdata, private_value) - if res != 1: - self._consume_errors() - raise ValueError("Invalid EC key.") - - with self._tmp_bn_ctx() as bn_ctx: - self._ec_key_set_public_key_affine_coordinates( - ec_cdata, public.x, public.y, bn_ctx - ) - # derive the expected public point and compare it to the one we - # just set based on the values we were given. If they don't match - # this isn't a valid key pair. - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - set_point = backend._lib.EC_KEY_get0_public_key(ec_cdata) - self.openssl_assert(set_point != self._ffi.NULL) - computed_point = self._lib.EC_POINT_new(group) - self.openssl_assert(computed_point != self._ffi.NULL) - computed_point = self._ffi.gc( - computed_point, self._lib.EC_POINT_free - ) - res = self._lib.EC_POINT_mul( - group, - computed_point, - private_value, - self._ffi.NULL, - self._ffi.NULL, - bn_ctx, - ) - self.openssl_assert(res == 1) - if ( - self._lib.EC_POINT_cmp( - group, set_point, computed_point, bn_ctx - ) - != 0 - ): - raise ValueError("Invalid EC key.") - - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - - def load_elliptic_curve_public_numbers( - self, numbers: ec.EllipticCurvePublicNumbers - ) -> ec.EllipticCurvePublicKey: - ec_cdata = self._ec_key_new_by_curve(numbers.curve) - with self._tmp_bn_ctx() as bn_ctx: - self._ec_key_set_public_key_affine_coordinates( - ec_cdata, numbers.x, numbers.y, bn_ctx - ) - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - - def load_elliptic_curve_public_bytes( - self, curve: ec.EllipticCurve, point_bytes: bytes - ) -> ec.EllipticCurvePublicKey: - ec_cdata = self._ec_key_new_by_curve(curve) - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - point = self._lib.EC_POINT_new(group) - self.openssl_assert(point != self._ffi.NULL) - point = self._ffi.gc(point, self._lib.EC_POINT_free) - with self._tmp_bn_ctx() as bn_ctx: - res = self._lib.EC_POINT_oct2point( - group, point, point_bytes, len(point_bytes), bn_ctx - ) - if res != 1: - self._consume_errors() - raise ValueError("Invalid public bytes for the given curve") - - res = self._lib.EC_KEY_set_public_key(ec_cdata, point) - self.openssl_assert(res == 1) - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - - def derive_elliptic_curve_private_key( - self, private_value: int, curve: ec.EllipticCurve - ) -> ec.EllipticCurvePrivateKey: - ec_cdata = self._ec_key_new_by_curve(curve) - - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - - point = self._lib.EC_POINT_new(group) - self.openssl_assert(point != self._ffi.NULL) - point = self._ffi.gc(point, self._lib.EC_POINT_free) - - value = self._int_to_bn(private_value) - value = self._ffi.gc(value, self._lib.BN_clear_free) - - with self._tmp_bn_ctx() as bn_ctx: - res = self._lib.EC_POINT_mul( - group, point, value, self._ffi.NULL, self._ffi.NULL, bn_ctx - ) - self.openssl_assert(res == 1) - - bn_x = self._lib.BN_CTX_get(bn_ctx) - bn_y = self._lib.BN_CTX_get(bn_ctx) - - res = self._lib.EC_POINT_get_affine_coordinates( - group, point, bn_x, bn_y, bn_ctx - ) - if res != 1: - self._consume_errors() - raise ValueError("Unable to derive key from private_value") - - res = self._lib.EC_KEY_set_public_key(ec_cdata, point) - self.openssl_assert(res == 1) - private = self._int_to_bn(private_value) - private = self._ffi.gc(private, self._lib.BN_clear_free) - res = self._lib.EC_KEY_set_private_key(ec_cdata, private) - self.openssl_assert(res == 1) - - evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) - - return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) - - def _ec_key_new_by_curve(self, curve: ec.EllipticCurve): - curve_nid = self._elliptic_curve_to_nid(curve) - return self._ec_key_new_by_curve_nid(curve_nid) - - def _ec_key_new_by_curve_nid(self, curve_nid: int): - ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) - self.openssl_assert(ec_cdata != self._ffi.NULL) - return self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) def elliptic_curve_exchange_algorithm_supported( self, algorithm: ec.ECDH, curve: ec.EllipticCurve ) -> bool: - if self._fips_enabled and not isinstance( - curve, self._fips_ecdh_curves - ): - return False - return self.elliptic_curve_supported(curve) and isinstance( algorithm, ec.ECDH ) - def _ec_cdata_to_evp_pkey(self, ec_cdata): - evp_pkey = self._create_evp_pkey_gc() - res = self._lib.EVP_PKEY_set1_EC_KEY(evp_pkey, ec_cdata) - self.openssl_assert(res == 1) - return evp_pkey - - def _elliptic_curve_to_nid(self, curve: ec.EllipticCurve) -> int: - """ - Get the NID for a curve name. - """ - - curve_aliases = {"secp192r1": "prime192v1", "secp256r1": "prime256v1"} - - curve_name = curve_aliases.get(curve.name, curve.name) - - curve_nid = self._lib.OBJ_sn2nid(curve_name.encode()) - if curve_nid == self._lib.NID_undef: - raise UnsupportedAlgorithm( - f"{curve.name} is not a supported elliptic curve", - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, - ) - return curve_nid - - @contextmanager - def _tmp_bn_ctx(self): - bn_ctx = self._lib.BN_CTX_new() - self.openssl_assert(bn_ctx != self._ffi.NULL) - bn_ctx = self._ffi.gc(bn_ctx, self._lib.BN_CTX_free) - self._lib.BN_CTX_start(bn_ctx) - try: - yield bn_ctx - finally: - self._lib.BN_CTX_end(bn_ctx) - - def _ec_key_set_public_key_affine_coordinates( - self, - ec_cdata, - x: int, - y: int, - bn_ctx, - ) -> None: - """ - Sets the public key point in the EC_KEY context to the affine x and y - values. - """ - - if x < 0 or y < 0: - raise ValueError( - "Invalid EC key. Both x and y must be non-negative." - ) - - x = self._ffi.gc(self._int_to_bn(x), self._lib.BN_free) - y = self._ffi.gc(self._int_to_bn(y), self._lib.BN_free) - group = self._lib.EC_KEY_get0_group(ec_cdata) - self.openssl_assert(group != self._ffi.NULL) - point = self._lib.EC_POINT_new(group) - self.openssl_assert(point != self._ffi.NULL) - point = self._ffi.gc(point, self._lib.EC_POINT_free) - res = self._lib.EC_POINT_set_affine_coordinates( - group, point, x, y, bn_ctx - ) - if res != 1: - self._consume_errors() - raise ValueError("Invalid EC key.") - res = self._lib.EC_KEY_set_public_key(ec_cdata, point) - self.openssl_assert(res == 1) - - def _private_key_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - key, - evp_pkey, - cdata, - ) -> bytes: - # validate argument types - if not isinstance(encoding, serialization.Encoding): - raise TypeError("encoding must be an item from the Encoding enum") - if not isinstance(format, serialization.PrivateFormat): - raise TypeError( - "format must be an item from the PrivateFormat enum" - ) - if not isinstance( - encryption_algorithm, serialization.KeySerializationEncryption - ): - raise TypeError( - "Encryption algorithm must be a KeySerializationEncryption " - "instance" - ) - - # validate password - if isinstance(encryption_algorithm, serialization.NoEncryption): - password = b"" - elif isinstance( - encryption_algorithm, serialization.BestAvailableEncryption - ): - password = encryption_algorithm.password - if len(password) > 1023: - raise ValueError( - "Passwords longer than 1023 bytes are not supported by " - "this backend" - ) - elif ( - isinstance( - encryption_algorithm, serialization._KeySerializationEncryption - ) - and encryption_algorithm._format - is format - is serialization.PrivateFormat.OpenSSH - ): - password = encryption_algorithm.password - else: - raise ValueError("Unsupported encryption type") - - # PKCS8 + PEM/DER - if format is serialization.PrivateFormat.PKCS8: - if encoding is serialization.Encoding.PEM: - write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey - elif encoding is serialization.Encoding.DER: - write_bio = self._lib.i2d_PKCS8PrivateKey_bio - else: - raise ValueError("Unsupported encoding for PKCS8") - return self._private_key_bytes_via_bio( - write_bio, evp_pkey, password - ) - - # TraditionalOpenSSL + PEM/DER - if format is serialization.PrivateFormat.TraditionalOpenSSL: - if self._fips_enabled and not isinstance( - encryption_algorithm, serialization.NoEncryption - ): - raise ValueError( - "Encrypted traditional OpenSSL format is not " - "supported in FIPS mode." - ) - key_type = self._lib.EVP_PKEY_id(evp_pkey) - - if encoding is serialization.Encoding.PEM: - if key_type == self._lib.EVP_PKEY_RSA: - write_bio = self._lib.PEM_write_bio_RSAPrivateKey - else: - assert key_type == self._lib.EVP_PKEY_EC - write_bio = self._lib.PEM_write_bio_ECPrivateKey - return self._private_key_bytes_via_bio( - write_bio, cdata, password - ) - - if encoding is serialization.Encoding.DER: - if password: - raise ValueError( - "Encryption is not supported for DER encoded " - "traditional OpenSSL keys" - ) - if key_type == self._lib.EVP_PKEY_RSA: - write_bio = self._lib.i2d_RSAPrivateKey_bio - else: - assert key_type == self._lib.EVP_PKEY_EC - write_bio = self._lib.i2d_ECPrivateKey_bio - return self._bio_func_output(write_bio, cdata) - - raise ValueError("Unsupported encoding for TraditionalOpenSSL") - - # OpenSSH + PEM - if format is serialization.PrivateFormat.OpenSSH: - if encoding is serialization.Encoding.PEM: - return ssh._serialize_ssh_private_key( - key, password, encryption_algorithm - ) - - raise ValueError( - "OpenSSH private key format can only be used" - " with PEM encoding" - ) - - # Anything that key-specific code was supposed to handle earlier, - # like Raw. - raise ValueError("format is invalid with this key") - - def _private_key_bytes_via_bio( - self, write_bio, evp_pkey, password - ) -> bytes: - if not password: - evp_cipher = self._ffi.NULL - else: - # This is a curated value that we will update over time. - evp_cipher = self._lib.EVP_get_cipherbyname(b"aes-256-cbc") - - return self._bio_func_output( - write_bio, - evp_pkey, - evp_cipher, - password, - len(password), - self._ffi.NULL, - self._ffi.NULL, - ) - - def _bio_func_output(self, write_bio, *args) -> bytes: - bio = self._create_mem_bio_gc() - res = write_bio(bio, *args) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio) - - def _public_key_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - key, - evp_pkey, - cdata, - ) -> bytes: - if not isinstance(encoding, serialization.Encoding): - raise TypeError("encoding must be an item from the Encoding enum") - if not isinstance(format, serialization.PublicFormat): - raise TypeError( - "format must be an item from the PublicFormat enum" - ) - - # SubjectPublicKeyInfo + PEM/DER - if format is serialization.PublicFormat.SubjectPublicKeyInfo: - if encoding is serialization.Encoding.PEM: - write_bio = self._lib.PEM_write_bio_PUBKEY - elif encoding is serialization.Encoding.DER: - write_bio = self._lib.i2d_PUBKEY_bio - else: - raise ValueError( - "SubjectPublicKeyInfo works only with PEM or DER encoding" - ) - return self._bio_func_output(write_bio, evp_pkey) - - # PKCS1 + PEM/DER - if format is serialization.PublicFormat.PKCS1: - # Only RSA is supported here. - key_type = self._lib.EVP_PKEY_id(evp_pkey) - if key_type != self._lib.EVP_PKEY_RSA: - raise ValueError("PKCS1 format is supported only for RSA keys") - - if encoding is serialization.Encoding.PEM: - write_bio = self._lib.PEM_write_bio_RSAPublicKey - elif encoding is serialization.Encoding.DER: - write_bio = self._lib.i2d_RSAPublicKey_bio - else: - raise ValueError("PKCS1 works only with PEM or DER encoding") - return self._bio_func_output(write_bio, cdata) - - # OpenSSH + OpenSSH - if format is serialization.PublicFormat.OpenSSH: - if encoding is serialization.Encoding.OpenSSH: - return ssh.serialize_ssh_public_key(key) - - raise ValueError( - "OpenSSH format must be used with OpenSSH encoding" - ) - - # Anything that key-specific code was supposed to handle earlier, - # like Raw, CompressedPoint, UncompressedPoint - raise ValueError("format is invalid with this key") - def dh_supported(self) -> bool: - return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - - def generate_dh_parameters( - self, generator: int, key_size: int - ) -> dh.DHParameters: - return rust_openssl.dh.generate_parameters(generator, key_size) - - def generate_dh_private_key( - self, parameters: dh.DHParameters - ) -> dh.DHPrivateKey: - return parameters.generate_private_key() - - def generate_dh_private_key_and_parameters( - self, generator: int, key_size: int - ) -> dh.DHPrivateKey: - return self.generate_dh_private_key( - self.generate_dh_parameters(generator, key_size) + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC ) - def load_dh_private_numbers( - self, numbers: dh.DHPrivateNumbers - ) -> dh.DHPrivateKey: - return rust_openssl.dh.from_private_numbers(numbers) - - def load_dh_public_numbers( - self, numbers: dh.DHPublicNumbers - ) -> dh.DHPublicKey: - return rust_openssl.dh.from_public_numbers(numbers) - - def load_dh_parameter_numbers( - self, numbers: dh.DHParameterNumbers - ) -> dh.DHParameters: - return rust_openssl.dh.from_parameter_numbers(numbers) - - def dh_parameters_supported( - self, p: int, g: int, q: typing.Optional[int] = None - ) -> bool: - try: - rust_openssl.dh.from_parameter_numbers( - dh.DHParameterNumbers(p=p, g=g, q=q) - ) - except ValueError: - return False - else: - return True - def dh_x942_serialization_supported(self) -> bool: return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 - def x25519_load_public_bytes(self, data: bytes) -> x25519.X25519PublicKey: - return rust_openssl.x25519.from_public_bytes(data) - - def x25519_load_private_bytes( - self, data: bytes - ) -> x25519.X25519PrivateKey: - return rust_openssl.x25519.from_private_bytes(data) - - def x25519_generate_key(self) -> x25519.X25519PrivateKey: - return rust_openssl.x25519.generate_key() - def x25519_supported(self) -> bool: - if self._fips_enabled: - return False - return not self._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370 - - def x448_load_public_bytes(self, data: bytes) -> x448.X448PublicKey: - return rust_openssl.x448.from_public_bytes(data) - - def x448_load_private_bytes(self, data: bytes) -> x448.X448PrivateKey: - return rust_openssl.x448.from_private_bytes(data) - - def x448_generate_key(self) -> x448.X448PrivateKey: - return rust_openssl.x448.generate_key() + return not self._fips_enabled def x448_supported(self) -> bool: if self._fips_enabled: return False return ( - not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC ) def ed25519_supported(self) -> bool: - if self._fips_enabled: - return False - return self._lib.CRYPTOGRAPHY_HAS_WORKING_ED25519 - - def ed25519_load_public_bytes( - self, data: bytes - ) -> ed25519.Ed25519PublicKey: - return rust_openssl.ed25519.from_public_bytes(data) - - def ed25519_load_private_bytes( - self, data: bytes - ) -> ed25519.Ed25519PrivateKey: - return rust_openssl.ed25519.from_private_bytes(data) - - def ed25519_generate_key(self) -> ed25519.Ed25519PrivateKey: - return rust_openssl.ed25519.generate_key() + return not self._fips_enabled def ed448_supported(self) -> bool: if self._fips_enabled: return False return ( - not self._lib.CRYPTOGRAPHY_IS_LIBRESSL - and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC ) - def ed448_load_public_bytes(self, data: bytes) -> ed448.Ed448PublicKey: - return rust_openssl.ed448.from_public_bytes(data) - - def ed448_load_private_bytes(self, data: bytes) -> ed448.Ed448PrivateKey: - return rust_openssl.ed448.from_private_bytes(data) - - def ed448_generate_key(self) -> ed448.Ed448PrivateKey: - return rust_openssl.ed448.generate_key() - - def aead_cipher_supported(self, cipher) -> bool: - return aead._aead_cipher_supported(self, cipher) - - def _zero_data(self, data, length: int) -> None: - # We clear things this way because at the moment we're not - # sure of a better way that can guarantee it overwrites the - # memory of a bytearray and doesn't just replace the underlying char *. - for i in range(length): - data[i] = 0 - - @contextlib.contextmanager - def _zeroed_null_terminated_buf(self, data): - """ - This method takes bytes, which can be a bytestring or a mutable - buffer like a bytearray, and yields a null-terminated version of that - data. This is required because PKCS12_parse doesn't take a length with - its password char * and ffi.from_buffer doesn't provide null - termination. So, to support zeroing the data via bytearray we - need to build this ridiculous construct that copies the memory, but - zeroes it after use. - """ - if data is None: - yield self._ffi.NULL - else: - data_len = len(data) - buf = self._ffi.new("char[]", data_len + 1) - self._ffi.memmove(buf, data, data_len) - try: - yield buf - finally: - # Cast to a uint8_t * so we can assign by integer - self._zero_data(self._ffi.cast("uint8_t *", buf), data_len) - - def load_key_and_certificates_from_pkcs12( - self, data: bytes, password: typing.Optional[bytes] - ) -> typing.Tuple[ - typing.Optional[PrivateKeyTypes], - typing.Optional[x509.Certificate], - typing.List[x509.Certificate], - ]: - pkcs12 = self.load_pkcs12(data, password) + def ecdsa_deterministic_supported(self) -> bool: return ( - pkcs12.key, - pkcs12.cert.certificate if pkcs12.cert else None, - [cert.certificate for cert in pkcs12.additional_certs], + rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + and not self._fips_enabled ) - def load_pkcs12( - self, data: bytes, password: typing.Optional[bytes] - ) -> PKCS12KeyAndCertificates: - if password is not None: - utils._check_byteslike("password", password) - - bio = self._bytes_to_bio(data) - p12 = self._lib.d2i_PKCS12_bio(bio.bio, self._ffi.NULL) - if p12 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Could not deserialize PKCS12 data") - - p12 = self._ffi.gc(p12, self._lib.PKCS12_free) - evp_pkey_ptr = self._ffi.new("EVP_PKEY **") - x509_ptr = self._ffi.new("X509 **") - sk_x509_ptr = self._ffi.new("Cryptography_STACK_OF_X509 **") - with self._zeroed_null_terminated_buf(password) as password_buf: - res = self._lib.PKCS12_parse( - p12, password_buf, evp_pkey_ptr, x509_ptr, sk_x509_ptr - ) - if res == 0: - self._consume_errors() - raise ValueError("Invalid password or PKCS12 data") - - cert = None - key = None - additional_certificates = [] - - if evp_pkey_ptr[0] != self._ffi.NULL: - evp_pkey = self._ffi.gc(evp_pkey_ptr[0], self._lib.EVP_PKEY_free) - # We don't support turning off RSA key validation when loading - # PKCS12 keys - key = self._evp_pkey_to_private_key( - evp_pkey, unsafe_skip_rsa_key_validation=False - ) - - if x509_ptr[0] != self._ffi.NULL: - x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free) - cert_obj = self._ossl2cert(x509) - name = None - maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL) - if maybe_name != self._ffi.NULL: - name = self._ffi.string(maybe_name) - cert = PKCS12Certificate(cert_obj, name) - - if sk_x509_ptr[0] != self._ffi.NULL: - sk_x509 = self._ffi.gc(sk_x509_ptr[0], self._lib.sk_X509_free) - num = self._lib.sk_X509_num(sk_x509_ptr[0]) - - # In OpenSSL < 3.0.0 PKCS12 parsing reverses the order of the - # certificates. - indices: typing.Iterable[int] - if ( - self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER - or self._lib.CRYPTOGRAPHY_IS_BORINGSSL - ): - indices = range(num) - else: - indices = reversed(range(num)) - - for i in indices: - x509 = self._lib.sk_X509_value(sk_x509, i) - self.openssl_assert(x509 != self._ffi.NULL) - x509 = self._ffi.gc(x509, self._lib.X509_free) - addl_cert = self._ossl2cert(x509) - addl_name = None - maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL) - if maybe_name != self._ffi.NULL: - addl_name = self._ffi.string(maybe_name) - additional_certificates.append( - PKCS12Certificate(addl_cert, addl_name) - ) - - return PKCS12KeyAndCertificates(key, cert, additional_certificates) - - def serialize_key_and_certificates_to_pkcs12( - self, - name: typing.Optional[bytes], - key: typing.Optional[PKCS12PrivateKeyTypes], - cert: typing.Optional[x509.Certificate], - cas: typing.Optional[typing.List[_PKCS12CATypes]], - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - password = None - if name is not None: - utils._check_bytes("name", name) - - if isinstance(encryption_algorithm, serialization.NoEncryption): - nid_cert = -1 - nid_key = -1 - pkcs12_iter = 0 - mac_iter = 0 - mac_alg = self._ffi.NULL - elif isinstance( - encryption_algorithm, serialization.BestAvailableEncryption - ): - # PKCS12 encryption is hopeless trash and can never be fixed. - # OpenSSL 3 supports PBESv2, but Libre and Boring do not, so - # we use PBESv1 with 3DES on the older paths. - if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - nid_cert = self._lib.NID_aes_256_cbc - nid_key = self._lib.NID_aes_256_cbc - else: - nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - # At least we can set this higher than OpenSSL's default - pkcs12_iter = 20000 - # mac_iter chosen for compatibility reasons, see: - # https://www.openssl.org/docs/man1.1.1/man3/PKCS12_create.html - # Did we mention how lousy PKCS12 encryption is? - mac_iter = 1 - # MAC algorithm can only be set on OpenSSL 3.0.0+ - mac_alg = self._ffi.NULL - password = encryption_algorithm.password - elif ( - isinstance( - encryption_algorithm, serialization._KeySerializationEncryption - ) - and encryption_algorithm._format - is serialization.PrivateFormat.PKCS12 - ): - # Default to OpenSSL's defaults. Behavior will vary based on the - # version of OpenSSL cryptography is compiled against. - nid_cert = 0 - nid_key = 0 - # Use the default iters we use in best available - pkcs12_iter = 20000 - # See the Best Available comment for why this is 1 - mac_iter = 1 - password = encryption_algorithm.password - keycertalg = encryption_algorithm._key_cert_algorithm - if keycertalg is PBES.PBESv1SHA1And3KeyTripleDESCBC: - nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC - elif keycertalg is PBES.PBESv2SHA256AndAES256CBC: - if not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - raise UnsupportedAlgorithm( - "PBESv2 is not supported by this version of OpenSSL" - ) - nid_cert = self._lib.NID_aes_256_cbc - nid_key = self._lib.NID_aes_256_cbc - else: - assert keycertalg is None - # We use OpenSSL's defaults - - if encryption_algorithm._hmac_hash is not None: - if not self._lib.Cryptography_HAS_PKCS12_SET_MAC: - raise UnsupportedAlgorithm( - "Setting MAC algorithm is not supported by this " - "version of OpenSSL." - ) - mac_alg = self._evp_md_non_null_from_algorithm( - encryption_algorithm._hmac_hash - ) - self.openssl_assert(mac_alg != self._ffi.NULL) - else: - mac_alg = self._ffi.NULL - - if encryption_algorithm._kdf_rounds is not None: - pkcs12_iter = encryption_algorithm._kdf_rounds - - else: - raise ValueError("Unsupported key encryption type") - - if cas is None or len(cas) == 0: - sk_x509 = self._ffi.NULL - else: - sk_x509 = self._lib.sk_X509_new_null() - sk_x509 = self._ffi.gc(sk_x509, self._lib.sk_X509_free) - - # This list is to keep the x509 values alive until end of function - ossl_cas = [] - for ca in cas: - if isinstance(ca, PKCS12Certificate): - ca_alias = ca.friendly_name - ossl_ca = self._cert2ossl(ca.certificate) - if ca_alias is None: - res = self._lib.X509_alias_set1( - ossl_ca, self._ffi.NULL, -1 - ) - else: - res = self._lib.X509_alias_set1( - ossl_ca, ca_alias, len(ca_alias) - ) - self.openssl_assert(res == 1) - else: - ossl_ca = self._cert2ossl(ca) - ossl_cas.append(ossl_ca) - res = self._lib.sk_X509_push(sk_x509, ossl_ca) - backend.openssl_assert(res >= 1) - - with self._zeroed_null_terminated_buf(password) as password_buf: - with self._zeroed_null_terminated_buf(name) as name_buf: - ossl_cert = self._cert2ossl(cert) if cert else self._ffi.NULL - ossl_pkey = ( - self._key2ossl(key) if key is not None else self._ffi.NULL - ) - - p12 = self._lib.PKCS12_create( - password_buf, - name_buf, - ossl_pkey, - ossl_cert, - sk_x509, - nid_key, - nid_cert, - pkcs12_iter, - mac_iter, - 0, - ) - - if ( - self._lib.Cryptography_HAS_PKCS12_SET_MAC - and mac_alg != self._ffi.NULL - ): - self._lib.PKCS12_set_mac( - p12, - password_buf, - -1, - self._ffi.NULL, - 0, - mac_iter, - mac_alg, - ) - - self.openssl_assert(p12 != self._ffi.NULL) - p12 = self._ffi.gc(p12, self._lib.PKCS12_free) - - bio = self._create_mem_bio_gc() - res = self._lib.i2d_PKCS12_bio(bio, p12) - self.openssl_assert(res > 0) - return self._read_mem_bio(bio) - def poly1305_supported(self) -> bool: - if self._fips_enabled: - return False - return self._lib.Cryptography_HAS_POLY1305 == 1 + return not self._fips_enabled def pkcs7_supported(self) -> bool: - return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - - def load_pem_pkcs7_certificates( - self, data: bytes - ) -> typing.List[x509.Certificate]: - utils._check_bytes("data", data) - bio = self._bytes_to_bio(data) - p7 = self._lib.PEM_read_bio_PKCS7( - bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC ) - if p7 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to parse PKCS7 data") - - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - return self._load_pkcs7_certificates(p7) - - def load_der_pkcs7_certificates( - self, data: bytes - ) -> typing.List[x509.Certificate]: - utils._check_bytes("data", data) - bio = self._bytes_to_bio(data) - p7 = self._lib.d2i_PKCS7_bio(bio.bio, self._ffi.NULL) - if p7 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to parse PKCS7 data") - - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - return self._load_pkcs7_certificates(p7) - - def _load_pkcs7_certificates(self, p7) -> typing.List[x509.Certificate]: - nid = self._lib.OBJ_obj2nid(p7.type) - self.openssl_assert(nid != self._lib.NID_undef) - if nid != self._lib.NID_pkcs7_signed: - raise UnsupportedAlgorithm( - "Only basic signed structures are currently supported. NID" - " for this data was {}".format(nid), - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - certs: list[x509.Certificate] = [] - if p7.d.sign == self._ffi.NULL: - return certs - - sk_x509 = p7.d.sign.cert - num = self._lib.sk_X509_num(sk_x509) - for i in range(num): - x509 = self._lib.sk_X509_value(sk_x509, i) - self.openssl_assert(x509 != self._ffi.NULL) - cert = self._ossl2cert(x509) - certs.append(cert) - - return certs - - -class GetCipherByName: - def __init__(self, fmt: str): - self._fmt = fmt - - def __call__(self, backend: Backend, cipher: CipherAlgorithm, mode: Mode): - cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower() - evp_cipher = backend._lib.EVP_get_cipherbyname( - cipher_name.encode("ascii") - ) - - # try EVP_CIPHER_fetch if present - if ( - evp_cipher == backend._ffi.NULL - and backend._lib.Cryptography_HAS_300_EVP_CIPHER - ): - evp_cipher = backend._lib.EVP_CIPHER_fetch( - backend._ffi.NULL, - cipher_name.encode("ascii"), - backend._ffi.NULL, - ) - - backend._consume_errors() - return evp_cipher - - -def _get_xts_cipher(backend: Backend, cipher: AES, mode): - cipher_name = f"aes-{cipher.key_size // 2}-xts" - return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) backend = Backend() diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/ciphers.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/ciphers.py deleted file mode 100644 index bc42adbd..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/ciphers.py +++ /dev/null @@ -1,281 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons -from cryptography.hazmat.primitives import ciphers -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -class _CipherContext: - _ENCRYPT = 1 - _DECRYPT = 0 - _MAX_CHUNK_SIZE = 2**30 - 1 - - def __init__(self, backend: Backend, cipher, mode, operation: int) -> None: - self._backend = backend - self._cipher = cipher - self._mode = mode - self._operation = operation - self._tag: typing.Optional[bytes] = None - - if isinstance(self._cipher, ciphers.BlockCipherAlgorithm): - self._block_size_bytes = self._cipher.block_size // 8 - else: - self._block_size_bytes = 1 - - ctx = self._backend._lib.EVP_CIPHER_CTX_new() - ctx = self._backend._ffi.gc( - ctx, self._backend._lib.EVP_CIPHER_CTX_free - ) - - registry = self._backend._cipher_registry - try: - adapter = registry[type(cipher), type(mode)] - except KeyError: - raise UnsupportedAlgorithm( - "cipher {} in {} mode is not supported " - "by this backend.".format( - cipher.name, mode.name if mode else mode - ), - _Reasons.UNSUPPORTED_CIPHER, - ) - - evp_cipher = adapter(self._backend, cipher, mode) - if evp_cipher == self._backend._ffi.NULL: - msg = f"cipher {cipher.name} " - if mode is not None: - msg += f"in {mode.name} mode " - msg += ( - "is not supported by this backend (Your version of OpenSSL " - "may be too old. Current version: {}.)" - ).format(self._backend.openssl_version_text()) - raise UnsupportedAlgorithm(msg, _Reasons.UNSUPPORTED_CIPHER) - - if isinstance(mode, modes.ModeWithInitializationVector): - iv_nonce = self._backend._ffi.from_buffer( - mode.initialization_vector - ) - elif isinstance(mode, modes.ModeWithTweak): - iv_nonce = self._backend._ffi.from_buffer(mode.tweak) - elif isinstance(mode, modes.ModeWithNonce): - iv_nonce = self._backend._ffi.from_buffer(mode.nonce) - elif isinstance(cipher, algorithms.ChaCha20): - iv_nonce = self._backend._ffi.from_buffer(cipher.nonce) - else: - iv_nonce = self._backend._ffi.NULL - # begin init with cipher and operation type - res = self._backend._lib.EVP_CipherInit_ex( - ctx, - evp_cipher, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - operation, - ) - self._backend.openssl_assert(res != 0) - # set the key length to handle variable key ciphers - res = self._backend._lib.EVP_CIPHER_CTX_set_key_length( - ctx, len(cipher.key) - ) - self._backend.openssl_assert(res != 0) - if isinstance(mode, modes.GCM): - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - self._backend._lib.EVP_CTRL_AEAD_SET_IVLEN, - len(iv_nonce), - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res != 0) - if mode.tag is not None: - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, - self._backend._lib.EVP_CTRL_AEAD_SET_TAG, - len(mode.tag), - mode.tag, - ) - self._backend.openssl_assert(res != 0) - self._tag = mode.tag - - # pass key/iv - res = self._backend._lib.EVP_CipherInit_ex( - ctx, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.from_buffer(cipher.key), - iv_nonce, - operation, - ) - - # Check for XTS mode duplicate keys error - errors = self._backend._consume_errors() - lib = self._backend._lib - if res == 0 and ( - ( - not lib.CRYPTOGRAPHY_IS_LIBRESSL - and errors[0]._lib_reason_match( - lib.ERR_LIB_EVP, lib.EVP_R_XTS_DUPLICATED_KEYS - ) - ) - or ( - lib.Cryptography_HAS_PROVIDERS - and errors[0]._lib_reason_match( - lib.ERR_LIB_PROV, lib.PROV_R_XTS_DUPLICATED_KEYS - ) - ) - ): - raise ValueError("In XTS mode duplicated keys are not allowed") - - self._backend.openssl_assert(res != 0, errors=errors) - - # We purposely disable padding here as it's handled higher up in the - # API. - self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0) - self._ctx = ctx - - def update(self, data: bytes) -> bytes: - buf = bytearray(len(data) + self._block_size_bytes - 1) - n = self.update_into(data, buf) - return bytes(buf[:n]) - - def update_into(self, data: bytes, buf: bytes) -> int: - total_data_len = len(data) - if len(buf) < (total_data_len + self._block_size_bytes - 1): - raise ValueError( - "buffer must be at least {} bytes for this " - "payload".format(len(data) + self._block_size_bytes - 1) - ) - - data_processed = 0 - total_out = 0 - outlen = self._backend._ffi.new("int *") - baseoutbuf = self._backend._ffi.from_buffer(buf, require_writable=True) - baseinbuf = self._backend._ffi.from_buffer(data) - - while data_processed != total_data_len: - outbuf = baseoutbuf + total_out - inbuf = baseinbuf + data_processed - inlen = min(self._MAX_CHUNK_SIZE, total_data_len - data_processed) - - res = self._backend._lib.EVP_CipherUpdate( - self._ctx, outbuf, outlen, inbuf, inlen - ) - if res == 0 and isinstance(self._mode, modes.XTS): - self._backend._consume_errors() - raise ValueError( - "In XTS mode you must supply at least a full block in the " - "first update call. For AES this is 16 bytes." - ) - else: - self._backend.openssl_assert(res != 0) - data_processed += inlen - total_out += outlen[0] - - return total_out - - def finalize(self) -> bytes: - if ( - self._operation == self._DECRYPT - and isinstance(self._mode, modes.ModeWithAuthenticationTag) - and self.tag is None - ): - raise ValueError( - "Authentication tag must be provided when decrypting." - ) - - buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes) - outlen = self._backend._ffi.new("int *") - res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) - if res == 0: - errors = self._backend._consume_errors() - - if not errors and isinstance(self._mode, modes.GCM): - raise InvalidTag - - lib = self._backend._lib - self._backend.openssl_assert( - errors[0]._lib_reason_match( - lib.ERR_LIB_EVP, - lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, - ) - or ( - lib.Cryptography_HAS_PROVIDERS - and errors[0]._lib_reason_match( - lib.ERR_LIB_PROV, - lib.PROV_R_WRONG_FINAL_BLOCK_LENGTH, - ) - ) - or ( - lib.CRYPTOGRAPHY_IS_BORINGSSL - and errors[0].reason - == lib.CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH - ), - errors=errors, - ) - raise ValueError( - "The length of the provided data is not a multiple of " - "the block length." - ) - - if ( - isinstance(self._mode, modes.GCM) - and self._operation == self._ENCRYPT - ): - tag_buf = self._backend._ffi.new( - "unsigned char[]", self._block_size_bytes - ) - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - self._ctx, - self._backend._lib.EVP_CTRL_AEAD_GET_TAG, - self._block_size_bytes, - tag_buf, - ) - self._backend.openssl_assert(res != 0) - self._tag = self._backend._ffi.buffer(tag_buf)[:] - - res = self._backend._lib.EVP_CIPHER_CTX_reset(self._ctx) - self._backend.openssl_assert(res == 1) - return self._backend._ffi.buffer(buf)[: outlen[0]] - - def finalize_with_tag(self, tag: bytes) -> bytes: - tag_len = len(tag) - if tag_len < self._mode._min_tag_length: - raise ValueError( - "Authentication tag must be {} bytes or longer.".format( - self._mode._min_tag_length - ) - ) - elif tag_len > self._block_size_bytes: - raise ValueError( - "Authentication tag cannot be more than {} bytes.".format( - self._block_size_bytes - ) - ) - res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag - ) - self._backend.openssl_assert(res != 0) - self._tag = tag - return self.finalize() - - def authenticate_additional_data(self, data: bytes) -> None: - outlen = self._backend._ffi.new("int *") - res = self._backend._lib.EVP_CipherUpdate( - self._ctx, - self._backend._ffi.NULL, - outlen, - self._backend._ffi.from_buffer(data), - len(data), - ) - self._backend.openssl_assert(res != 0) - - @property - def tag(self) -> typing.Optional[bytes]: - return self._tag diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/cmac.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/cmac.py deleted file mode 100644 index bdd7fec6..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/cmac.py +++ /dev/null @@ -1,89 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.primitives import constant_time -from cryptography.hazmat.primitives.ciphers.modes import CBC - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - from cryptography.hazmat.primitives import ciphers - - -class _CMACContext: - def __init__( - self, - backend: Backend, - algorithm: ciphers.BlockCipherAlgorithm, - ctx=None, - ) -> None: - if not backend.cmac_algorithm_supported(algorithm): - raise UnsupportedAlgorithm( - "This backend does not support CMAC.", - _Reasons.UNSUPPORTED_CIPHER, - ) - - self._backend = backend - self._key = algorithm.key - self._algorithm = algorithm - self._output_length = algorithm.block_size // 8 - - if ctx is None: - registry = self._backend._cipher_registry - adapter = registry[type(algorithm), CBC] - - evp_cipher = adapter(self._backend, algorithm, CBC) - - ctx = self._backend._lib.CMAC_CTX_new() - - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free) - - key_ptr = self._backend._ffi.from_buffer(self._key) - res = self._backend._lib.CMAC_Init( - ctx, - key_ptr, - len(self._key), - evp_cipher, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(res == 1) - - self._ctx = ctx - - def update(self, data: bytes) -> None: - res = self._backend._lib.CMAC_Update(self._ctx, data, len(data)) - self._backend.openssl_assert(res == 1) - - def finalize(self) -> bytes: - buf = self._backend._ffi.new("unsigned char[]", self._output_length) - length = self._backend._ffi.new("size_t *", self._output_length) - res = self._backend._lib.CMAC_Final(self._ctx, buf, length) - self._backend.openssl_assert(res == 1) - - self._ctx = None - - return self._backend._ffi.buffer(buf)[:] - - def copy(self) -> _CMACContext: - copied_ctx = self._backend._lib.CMAC_CTX_new() - copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.CMAC_CTX_free - ) - res = self._backend._lib.CMAC_CTX_copy(copied_ctx, self._ctx) - self._backend.openssl_assert(res == 1) - return _CMACContext(self._backend, self._algorithm, ctx=copied_ctx) - - def verify(self, signature: bytes) -> None: - digest = self.finalize() - if not constant_time.bytes_eq(digest, signature): - raise InvalidSignature("Signature did not match digest.") diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/decode_asn1.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/decode_asn1.py deleted file mode 100644 index bf123b62..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/decode_asn1.py +++ /dev/null @@ -1,32 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -from cryptography import x509 - -# CRLReason ::= ENUMERATED { -# unspecified (0), -# keyCompromise (1), -# cACompromise (2), -# affiliationChanged (3), -# superseded (4), -# cessationOfOperation (5), -# certificateHold (6), -# -- value 7 is not used -# removeFromCRL (8), -# privilegeWithdrawn (9), -# aACompromise (10) } -_CRL_ENTRY_REASON_ENUM_TO_CODE = { - x509.ReasonFlags.unspecified: 0, - x509.ReasonFlags.key_compromise: 1, - x509.ReasonFlags.ca_compromise: 2, - x509.ReasonFlags.affiliation_changed: 3, - x509.ReasonFlags.superseded: 4, - x509.ReasonFlags.cessation_of_operation: 5, - x509.ReasonFlags.certificate_hold: 6, - x509.ReasonFlags.remove_from_crl: 8, - x509.ReasonFlags.privilege_withdrawn: 9, - x509.ReasonFlags.aa_compromise: 10, -} diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/ec.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/ec.py deleted file mode 100644 index 9821bd19..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/ec.py +++ /dev/null @@ -1,328 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, - _evp_pkey_derive, -) -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import ec - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _check_signature_algorithm( - signature_algorithm: ec.EllipticCurveSignatureAlgorithm, -) -> None: - if not isinstance(signature_algorithm, ec.ECDSA): - raise UnsupportedAlgorithm( - "Unsupported elliptic curve signature algorithm.", - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, - ) - - -def _ec_key_curve_sn(backend: Backend, ec_key) -> str: - group = backend._lib.EC_KEY_get0_group(ec_key) - backend.openssl_assert(group != backend._ffi.NULL) - - nid = backend._lib.EC_GROUP_get_curve_name(group) - # The following check is to find EC keys with unnamed curves and raise - # an error for now. - if nid == backend._lib.NID_undef: - raise ValueError( - "ECDSA keys with explicit parameters are unsupported at this time" - ) - - # This is like the above check, but it also catches the case where you - # explicitly encoded a curve with the same parameters as a named curve. - # Don't do that. - if ( - not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - and backend._lib.EC_GROUP_get_asn1_flag(group) == 0 - ): - raise ValueError( - "ECDSA keys with explicit parameters are unsupported at this time" - ) - - curve_name = backend._lib.OBJ_nid2sn(nid) - backend.openssl_assert(curve_name != backend._ffi.NULL) - - sn = backend._ffi.string(curve_name).decode("ascii") - return sn - - -def _mark_asn1_named_ec_curve(backend: Backend, ec_cdata): - """ - Set the named curve flag on the EC_KEY. This causes OpenSSL to - serialize EC keys along with their curve OID which makes - deserialization easier. - """ - - backend._lib.EC_KEY_set_asn1_flag( - ec_cdata, backend._lib.OPENSSL_EC_NAMED_CURVE - ) - - -def _check_key_infinity(backend: Backend, ec_cdata) -> None: - point = backend._lib.EC_KEY_get0_public_key(ec_cdata) - backend.openssl_assert(point != backend._ffi.NULL) - group = backend._lib.EC_KEY_get0_group(ec_cdata) - backend.openssl_assert(group != backend._ffi.NULL) - if backend._lib.EC_POINT_is_at_infinity(group, point): - raise ValueError( - "Cannot load an EC public key where the point is at infinity" - ) - - -def _sn_to_elliptic_curve(backend: Backend, sn: str) -> ec.EllipticCurve: - try: - return ec._CURVE_TYPES[sn]() - except KeyError: - raise UnsupportedAlgorithm( - f"{sn} is not a supported elliptic curve", - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, - ) - - -def _ecdsa_sig_sign( - backend: Backend, private_key: _EllipticCurvePrivateKey, data: bytes -) -> bytes: - max_size = backend._lib.ECDSA_size(private_key._ec_key) - backend.openssl_assert(max_size > 0) - - sigbuf = backend._ffi.new("unsigned char[]", max_size) - siglen_ptr = backend._ffi.new("unsigned int[]", 1) - res = backend._lib.ECDSA_sign( - 0, data, len(data), sigbuf, siglen_ptr, private_key._ec_key - ) - backend.openssl_assert(res == 1) - return backend._ffi.buffer(sigbuf)[: siglen_ptr[0]] - - -def _ecdsa_sig_verify( - backend: Backend, - public_key: _EllipticCurvePublicKey, - signature: bytes, - data: bytes, -) -> None: - res = backend._lib.ECDSA_verify( - 0, data, len(data), signature, len(signature), public_key._ec_key - ) - if res != 1: - backend._consume_errors() - raise InvalidSignature - - -class _EllipticCurvePrivateKey(ec.EllipticCurvePrivateKey): - def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): - self._backend = backend - self._ec_key = ec_key_cdata - self._evp_pkey = evp_pkey - - sn = _ec_key_curve_sn(backend, ec_key_cdata) - self._curve = _sn_to_elliptic_curve(backend, sn) - _mark_asn1_named_ec_curve(backend, ec_key_cdata) - _check_key_infinity(backend, ec_key_cdata) - - @property - def curve(self) -> ec.EllipticCurve: - return self._curve - - @property - def key_size(self) -> int: - return self.curve.key_size - - def exchange( - self, algorithm: ec.ECDH, peer_public_key: ec.EllipticCurvePublicKey - ) -> bytes: - if not ( - self._backend.elliptic_curve_exchange_algorithm_supported( - algorithm, self.curve - ) - ): - raise UnsupportedAlgorithm( - "This backend does not support the ECDH algorithm.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, - ) - - if peer_public_key.curve.name != self.curve.name: - raise ValueError( - "peer_public_key and self are not on the same curve" - ) - - return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) - - def public_key(self) -> ec.EllipticCurvePublicKey: - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - self._backend.openssl_assert(group != self._backend._ffi.NULL) - - curve_nid = self._backend._lib.EC_GROUP_get_curve_name(group) - public_ec_key = self._backend._ec_key_new_by_curve_nid(curve_nid) - - point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) - self._backend.openssl_assert(point != self._backend._ffi.NULL) - - res = self._backend._lib.EC_KEY_set_public_key(public_ec_key, point) - self._backend.openssl_assert(res == 1) - - evp_pkey = self._backend._ec_cdata_to_evp_pkey(public_ec_key) - - return _EllipticCurvePublicKey(self._backend, public_ec_key, evp_pkey) - - def private_numbers(self) -> ec.EllipticCurvePrivateNumbers: - bn = self._backend._lib.EC_KEY_get0_private_key(self._ec_key) - private_value = self._backend._bn_to_int(bn) - return ec.EllipticCurvePrivateNumbers( - private_value=private_value, - public_numbers=self.public_key().public_numbers(), - ) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._ec_key, - ) - - def sign( - self, - data: bytes, - signature_algorithm: ec.EllipticCurveSignatureAlgorithm, - ) -> bytes: - _check_signature_algorithm(signature_algorithm) - data, _ = _calculate_digest_and_algorithm( - data, - signature_algorithm.algorithm, - ) - return _ecdsa_sig_sign(self._backend, self, data) - - -class _EllipticCurvePublicKey(ec.EllipticCurvePublicKey): - def __init__(self, backend: Backend, ec_key_cdata, evp_pkey): - self._backend = backend - self._ec_key = ec_key_cdata - self._evp_pkey = evp_pkey - - sn = _ec_key_curve_sn(backend, ec_key_cdata) - self._curve = _sn_to_elliptic_curve(backend, sn) - _mark_asn1_named_ec_curve(backend, ec_key_cdata) - _check_key_infinity(backend, ec_key_cdata) - - @property - def curve(self) -> ec.EllipticCurve: - return self._curve - - @property - def key_size(self) -> int: - return self.curve.key_size - - def __eq__(self, other: object) -> bool: - if not isinstance(other, _EllipticCurvePublicKey): - return NotImplemented - - return ( - self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) - == 1 - ) - - def public_numbers(self) -> ec.EllipticCurvePublicNumbers: - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - self._backend.openssl_assert(group != self._backend._ffi.NULL) - - point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) - self._backend.openssl_assert(point != self._backend._ffi.NULL) - - with self._backend._tmp_bn_ctx() as bn_ctx: - bn_x = self._backend._lib.BN_CTX_get(bn_ctx) - bn_y = self._backend._lib.BN_CTX_get(bn_ctx) - - res = self._backend._lib.EC_POINT_get_affine_coordinates( - group, point, bn_x, bn_y, bn_ctx - ) - self._backend.openssl_assert(res == 1) - - x = self._backend._bn_to_int(bn_x) - y = self._backend._bn_to_int(bn_y) - - return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=self._curve) - - def _encode_point(self, format: serialization.PublicFormat) -> bytes: - if format is serialization.PublicFormat.CompressedPoint: - conversion = self._backend._lib.POINT_CONVERSION_COMPRESSED - else: - assert format is serialization.PublicFormat.UncompressedPoint - conversion = self._backend._lib.POINT_CONVERSION_UNCOMPRESSED - - group = self._backend._lib.EC_KEY_get0_group(self._ec_key) - self._backend.openssl_assert(group != self._backend._ffi.NULL) - point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) - self._backend.openssl_assert(point != self._backend._ffi.NULL) - with self._backend._tmp_bn_ctx() as bn_ctx: - buflen = self._backend._lib.EC_POINT_point2oct( - group, point, conversion, self._backend._ffi.NULL, 0, bn_ctx - ) - self._backend.openssl_assert(buflen > 0) - buf = self._backend._ffi.new("char[]", buflen) - res = self._backend._lib.EC_POINT_point2oct( - group, point, conversion, buf, buflen, bn_ctx - ) - self._backend.openssl_assert(buflen == res) - - return self._backend._ffi.buffer(buf)[:] - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - if ( - encoding is serialization.Encoding.X962 - or format is serialization.PublicFormat.CompressedPoint - or format is serialization.PublicFormat.UncompressedPoint - ): - if encoding is not serialization.Encoding.X962 or format not in ( - serialization.PublicFormat.CompressedPoint, - serialization.PublicFormat.UncompressedPoint, - ): - raise ValueError( - "X962 encoding must be used with CompressedPoint or " - "UncompressedPoint format" - ) - - return self._encode_point(format) - else: - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, None - ) - - def verify( - self, - signature: bytes, - data: bytes, - signature_algorithm: ec.EllipticCurveSignatureAlgorithm, - ) -> None: - _check_signature_algorithm(signature_algorithm) - data, _ = _calculate_digest_and_algorithm( - data, - signature_algorithm.algorithm, - ) - _ecdsa_sig_verify(self._backend, self, signature, data) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/rsa.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/rsa.py deleted file mode 100644 index ef27d4ea..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/rsa.py +++ /dev/null @@ -1,599 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import threading -import typing - -from cryptography.exceptions import ( - InvalidSignature, - UnsupportedAlgorithm, - _Reasons, -) -from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, -) -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import utils as asym_utils -from cryptography.hazmat.primitives.asymmetric.padding import ( - MGF1, - OAEP, - PSS, - AsymmetricPadding, - PKCS1v15, - _Auto, - _DigestLength, - _MaxLength, - calculate_max_pss_salt_length, -) -from cryptography.hazmat.primitives.asymmetric.rsa import ( - RSAPrivateKey, - RSAPrivateNumbers, - RSAPublicKey, - RSAPublicNumbers, -) - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _get_rsa_pss_salt_length( - backend: Backend, - pss: PSS, - key: typing.Union[RSAPrivateKey, RSAPublicKey], - hash_algorithm: hashes.HashAlgorithm, -) -> int: - salt = pss._salt_length - - if isinstance(salt, _MaxLength): - return calculate_max_pss_salt_length(key, hash_algorithm) - elif isinstance(salt, _DigestLength): - return hash_algorithm.digest_size - elif isinstance(salt, _Auto): - if isinstance(key, RSAPrivateKey): - raise ValueError( - "PSS salt length can only be set to AUTO when verifying" - ) - return backend._lib.RSA_PSS_SALTLEN_AUTO - else: - return salt - - -def _enc_dec_rsa( - backend: Backend, - key: typing.Union[_RSAPrivateKey, _RSAPublicKey], - data: bytes, - padding: AsymmetricPadding, -) -> bytes: - if not isinstance(padding, AsymmetricPadding): - raise TypeError("Padding must be an instance of AsymmetricPadding.") - - if isinstance(padding, PKCS1v15): - padding_enum = backend._lib.RSA_PKCS1_PADDING - elif isinstance(padding, OAEP): - padding_enum = backend._lib.RSA_PKCS1_OAEP_PADDING - - if not isinstance(padding._mgf, MGF1): - raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend.", - _Reasons.UNSUPPORTED_MGF, - ) - - if not backend.rsa_padding_supported(padding): - raise UnsupportedAlgorithm( - "This combination of padding and hash algorithm is not " - "supported by this backend.", - _Reasons.UNSUPPORTED_PADDING, - ) - - else: - raise UnsupportedAlgorithm( - f"{padding.name} is not supported by this backend.", - _Reasons.UNSUPPORTED_PADDING, - ) - - return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding) - - -def _enc_dec_rsa_pkey_ctx( - backend: Backend, - key: typing.Union[_RSAPrivateKey, _RSAPublicKey], - data: bytes, - padding_enum: int, - padding: AsymmetricPadding, -) -> bytes: - init: typing.Callable[[typing.Any], int] - crypt: typing.Callable[[typing.Any, typing.Any, int, bytes, int], int] - if isinstance(key, _RSAPublicKey): - init = backend._lib.EVP_PKEY_encrypt_init - crypt = backend._lib.EVP_PKEY_encrypt - else: - init = backend._lib.EVP_PKEY_decrypt_init - crypt = backend._lib.EVP_PKEY_decrypt - - pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL) - backend.openssl_assert(pkey_ctx != backend._ffi.NULL) - pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) - res = init(pkey_ctx) - backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) - backend.openssl_assert(res > 0) - buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey) - backend.openssl_assert(buf_size > 0) - if isinstance(padding, OAEP): - mgf1_md = backend._evp_md_non_null_from_algorithm( - padding._mgf._algorithm - ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) - backend.openssl_assert(res > 0) - oaep_md = backend._evp_md_non_null_from_algorithm(padding._algorithm) - res = backend._lib.EVP_PKEY_CTX_set_rsa_oaep_md(pkey_ctx, oaep_md) - backend.openssl_assert(res > 0) - - if ( - isinstance(padding, OAEP) - and padding._label is not None - and len(padding._label) > 0 - ): - # set0_rsa_oaep_label takes ownership of the char * so we need to - # copy it into some new memory - labelptr = backend._lib.OPENSSL_malloc(len(padding._label)) - backend.openssl_assert(labelptr != backend._ffi.NULL) - backend._ffi.memmove(labelptr, padding._label, len(padding._label)) - res = backend._lib.EVP_PKEY_CTX_set0_rsa_oaep_label( - pkey_ctx, labelptr, len(padding._label) - ) - backend.openssl_assert(res == 1) - - outlen = backend._ffi.new("size_t *", buf_size) - buf = backend._ffi.new("unsigned char[]", buf_size) - # Everything from this line onwards is written with the goal of being as - # constant-time as is practical given the constraints of Python and our - # API. See Bleichenbacher's '98 attack on RSA, and its many many variants. - # As such, you should not attempt to change this (particularly to "clean it - # up") without understanding why it was written this way (see - # Chesterton's Fence), and without measuring to verify you have not - # introduced observable time differences. - res = crypt(pkey_ctx, buf, outlen, data, len(data)) - resbuf = backend._ffi.buffer(buf)[: outlen[0]] - backend._lib.ERR_clear_error() - if res <= 0: - raise ValueError("Encryption/decryption failed.") - return resbuf - - -def _rsa_sig_determine_padding( - backend: Backend, - key: typing.Union[_RSAPrivateKey, _RSAPublicKey], - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], -) -> int: - if not isinstance(padding, AsymmetricPadding): - raise TypeError("Expected provider of AsymmetricPadding.") - - pkey_size = backend._lib.EVP_PKEY_size(key._evp_pkey) - backend.openssl_assert(pkey_size > 0) - - if isinstance(padding, PKCS1v15): - # Hash algorithm is ignored for PKCS1v15-padding, may be None. - padding_enum = backend._lib.RSA_PKCS1_PADDING - elif isinstance(padding, PSS): - if not isinstance(padding._mgf, MGF1): - raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend.", - _Reasons.UNSUPPORTED_MGF, - ) - - # PSS padding requires a hash algorithm - if not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError("Expected instance of hashes.HashAlgorithm.") - - # Size of key in bytes - 2 is the maximum - # PSS signature length (salt length is checked later) - if pkey_size - algorithm.digest_size - 2 < 0: - raise ValueError( - "Digest too large for key size. Use a larger " - "key or different digest." - ) - - padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING - else: - raise UnsupportedAlgorithm( - f"{padding.name} is not supported by this backend.", - _Reasons.UNSUPPORTED_PADDING, - ) - - return padding_enum - - -# Hash algorithm can be absent (None) to initialize the context without setting -# any message digest algorithm. This is currently only valid for the PKCS1v15 -# padding type, where it means that the signature data is encoded/decoded -# as provided, without being wrapped in a DigestInfo structure. -def _rsa_sig_setup( - backend: Backend, - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], - key: typing.Union[_RSAPublicKey, _RSAPrivateKey], - init_func: typing.Callable[[typing.Any], int], -): - padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm) - pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL) - backend.openssl_assert(pkey_ctx != backend._ffi.NULL) - pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) - res = init_func(pkey_ctx) - if res != 1: - errors = backend._consume_errors() - raise ValueError("Unable to sign/verify with this key", errors) - - if algorithm is not None: - evp_md = backend._evp_md_non_null_from_algorithm(algorithm) - res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md) - if res <= 0: - backend._consume_errors() - raise UnsupportedAlgorithm( - "{} is not supported by this backend for RSA signing.".format( - algorithm.name - ), - _Reasons.UNSUPPORTED_HASH, - ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) - if res <= 0: - backend._consume_errors() - raise UnsupportedAlgorithm( - "{} is not supported for the RSA signature operation.".format( - padding.name - ), - _Reasons.UNSUPPORTED_PADDING, - ) - if isinstance(padding, PSS): - assert isinstance(algorithm, hashes.HashAlgorithm) - res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( - pkey_ctx, - _get_rsa_pss_salt_length(backend, padding, key, algorithm), - ) - backend.openssl_assert(res > 0) - - mgf1_md = backend._evp_md_non_null_from_algorithm( - padding._mgf._algorithm - ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) - backend.openssl_assert(res > 0) - - return pkey_ctx - - -def _rsa_sig_sign( - backend: Backend, - padding: AsymmetricPadding, - algorithm: hashes.HashAlgorithm, - private_key: _RSAPrivateKey, - data: bytes, -) -> bytes: - pkey_ctx = _rsa_sig_setup( - backend, - padding, - algorithm, - private_key, - backend._lib.EVP_PKEY_sign_init, - ) - buflen = backend._ffi.new("size_t *") - res = backend._lib.EVP_PKEY_sign( - pkey_ctx, backend._ffi.NULL, buflen, data, len(data) - ) - backend.openssl_assert(res == 1) - buf = backend._ffi.new("unsigned char[]", buflen[0]) - res = backend._lib.EVP_PKEY_sign(pkey_ctx, buf, buflen, data, len(data)) - if res != 1: - errors = backend._consume_errors() - raise ValueError( - "Digest or salt length too long for key size. Use a larger key " - "or shorter salt length if you are specifying a PSS salt", - errors, - ) - - return backend._ffi.buffer(buf)[:] - - -def _rsa_sig_verify( - backend: Backend, - padding: AsymmetricPadding, - algorithm: hashes.HashAlgorithm, - public_key: _RSAPublicKey, - signature: bytes, - data: bytes, -) -> None: - pkey_ctx = _rsa_sig_setup( - backend, - padding, - algorithm, - public_key, - backend._lib.EVP_PKEY_verify_init, - ) - res = backend._lib.EVP_PKEY_verify( - pkey_ctx, signature, len(signature), data, len(data) - ) - # The previous call can return negative numbers in the event of an - # error. This is not a signature failure but we need to fail if it - # occurs. - backend.openssl_assert(res >= 0) - if res == 0: - backend._consume_errors() - raise InvalidSignature - - -def _rsa_sig_recover( - backend: Backend, - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], - public_key: _RSAPublicKey, - signature: bytes, -) -> bytes: - pkey_ctx = _rsa_sig_setup( - backend, - padding, - algorithm, - public_key, - backend._lib.EVP_PKEY_verify_recover_init, - ) - - # Attempt to keep the rest of the code in this function as constant/time - # as possible. See the comment in _enc_dec_rsa_pkey_ctx. Note that the - # buflen parameter is used even though its value may be undefined in the - # error case. Due to the tolerant nature of Python slicing this does not - # trigger any exceptions. - maxlen = backend._lib.EVP_PKEY_size(public_key._evp_pkey) - backend.openssl_assert(maxlen > 0) - buf = backend._ffi.new("unsigned char[]", maxlen) - buflen = backend._ffi.new("size_t *", maxlen) - res = backend._lib.EVP_PKEY_verify_recover( - pkey_ctx, buf, buflen, signature, len(signature) - ) - resbuf = backend._ffi.buffer(buf)[: buflen[0]] - backend._lib.ERR_clear_error() - # Assume that all parameter errors are handled during the setup phase and - # any error here is due to invalid signature. - if res != 1: - raise InvalidSignature - return resbuf - - -class _RSAPrivateKey(RSAPrivateKey): - _evp_pkey: object - _rsa_cdata: object - _key_size: int - - def __init__( - self, - backend: Backend, - rsa_cdata, - evp_pkey, - *, - unsafe_skip_rsa_key_validation: bool, - ): - res: int - # RSA_check_key is slower in OpenSSL 3.0.0 due to improved - # primality checking. In normal use this is unlikely to be a problem - # since users don't load new keys constantly, but for TESTING we've - # added an init arg that allows skipping the checks. You should not - # use this in production code unless you understand the consequences. - if not unsafe_skip_rsa_key_validation: - res = backend._lib.RSA_check_key(rsa_cdata) - if res != 1: - errors = backend._consume_errors() - raise ValueError("Invalid private key", errors) - # 2 is prime and passes an RSA key check, so we also check - # if p and q are odd just to be safe. - p = backend._ffi.new("BIGNUM **") - q = backend._ffi.new("BIGNUM **") - backend._lib.RSA_get0_factors(rsa_cdata, p, q) - backend.openssl_assert(p[0] != backend._ffi.NULL) - backend.openssl_assert(q[0] != backend._ffi.NULL) - p_odd = backend._lib.BN_is_odd(p[0]) - q_odd = backend._lib.BN_is_odd(q[0]) - if p_odd != 1 or q_odd != 1: - errors = backend._consume_errors() - raise ValueError("Invalid private key", errors) - - self._backend = backend - self._rsa_cdata = rsa_cdata - self._evp_pkey = evp_pkey - # Used for lazy blinding - self._blinded = False - self._blinding_lock = threading.Lock() - - n = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key( - self._rsa_cdata, - n, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(n[0]) - - def _enable_blinding(self) -> None: - # If you call blind on an already blinded RSA key OpenSSL will turn - # it off and back on, which is a performance hit we want to avoid. - if not self._blinded: - with self._blinding_lock: - self._non_threadsafe_enable_blinding() - - def _non_threadsafe_enable_blinding(self) -> None: - # This is only a separate function to allow for testing to cover both - # branches. It should never be invoked except through _enable_blinding. - # Check if it's not True again in case another thread raced past the - # first non-locked check. - if not self._blinded: - res = self._backend._lib.RSA_blinding_on( - self._rsa_cdata, self._backend._ffi.NULL - ) - self._backend.openssl_assert(res == 1) - self._blinded = True - - @property - def key_size(self) -> int: - return self._key_size - - def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: - self._enable_blinding() - key_size_bytes = (self.key_size + 7) // 8 - if key_size_bytes != len(ciphertext): - raise ValueError("Ciphertext length must be equal to key size.") - - return _enc_dec_rsa(self._backend, self, ciphertext, padding) - - def public_key(self) -> RSAPublicKey: - ctx = self._backend._lib.RSAPublicKey_dup(self._rsa_cdata) - self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc(ctx, self._backend._lib.RSA_free) - evp_pkey = self._backend._rsa_cdata_to_evp_pkey(ctx) - return _RSAPublicKey(self._backend, ctx, evp_pkey) - - def private_numbers(self) -> RSAPrivateNumbers: - n = self._backend._ffi.new("BIGNUM **") - e = self._backend._ffi.new("BIGNUM **") - d = self._backend._ffi.new("BIGNUM **") - p = self._backend._ffi.new("BIGNUM **") - q = self._backend._ffi.new("BIGNUM **") - dmp1 = self._backend._ffi.new("BIGNUM **") - dmq1 = self._backend._ffi.new("BIGNUM **") - iqmp = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key(self._rsa_cdata, n, e, d) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(d[0] != self._backend._ffi.NULL) - self._backend._lib.RSA_get0_factors(self._rsa_cdata, p, q) - self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) - self._backend._lib.RSA_get0_crt_params( - self._rsa_cdata, dmp1, dmq1, iqmp - ) - self._backend.openssl_assert(dmp1[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(dmq1[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(iqmp[0] != self._backend._ffi.NULL) - return RSAPrivateNumbers( - p=self._backend._bn_to_int(p[0]), - q=self._backend._bn_to_int(q[0]), - d=self._backend._bn_to_int(d[0]), - dmp1=self._backend._bn_to_int(dmp1[0]), - dmq1=self._backend._bn_to_int(dmq1[0]), - iqmp=self._backend._bn_to_int(iqmp[0]), - public_numbers=RSAPublicNumbers( - e=self._backend._bn_to_int(e[0]), - n=self._backend._bn_to_int(n[0]), - ), - ) - - def private_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PrivateFormat, - encryption_algorithm: serialization.KeySerializationEncryption, - ) -> bytes: - return self._backend._private_key_bytes( - encoding, - format, - encryption_algorithm, - self, - self._evp_pkey, - self._rsa_cdata, - ) - - def sign( - self, - data: bytes, - padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], - ) -> bytes: - self._enable_blinding() - data, algorithm = _calculate_digest_and_algorithm(data, algorithm) - return _rsa_sig_sign(self._backend, padding, algorithm, self, data) - - -class _RSAPublicKey(RSAPublicKey): - _evp_pkey: object - _rsa_cdata: object - _key_size: int - - def __init__(self, backend: Backend, rsa_cdata, evp_pkey): - self._backend = backend - self._rsa_cdata = rsa_cdata - self._evp_pkey = evp_pkey - - n = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key( - self._rsa_cdata, - n, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - ) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._key_size = self._backend._lib.BN_num_bits(n[0]) - - @property - def key_size(self) -> int: - return self._key_size - - def __eq__(self, other: object) -> bool: - if not isinstance(other, _RSAPublicKey): - return NotImplemented - - return ( - self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey) - == 1 - ) - - def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: - return _enc_dec_rsa(self._backend, self, plaintext, padding) - - def public_numbers(self) -> RSAPublicNumbers: - n = self._backend._ffi.new("BIGNUM **") - e = self._backend._ffi.new("BIGNUM **") - self._backend._lib.RSA_get0_key( - self._rsa_cdata, n, e, self._backend._ffi.NULL - ) - self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) - self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) - return RSAPublicNumbers( - e=self._backend._bn_to_int(e[0]), - n=self._backend._bn_to_int(n[0]), - ) - - def public_bytes( - self, - encoding: serialization.Encoding, - format: serialization.PublicFormat, - ) -> bytes: - return self._backend._public_key_bytes( - encoding, format, self, self._evp_pkey, self._rsa_cdata - ) - - def verify( - self, - signature: bytes, - data: bytes, - padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], - ) -> None: - data, algorithm = _calculate_digest_and_algorithm(data, algorithm) - _rsa_sig_verify( - self._backend, padding, algorithm, self, signature, data - ) - - def recover_data_from_signature( - self, - signature: bytes, - padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], - ) -> bytes: - if isinstance(algorithm, asym_utils.Prehashed): - raise TypeError( - "Prehashed is only supported in the sign and verify methods. " - "It cannot be used with recover_data_from_signature." - ) - return _rsa_sig_recover( - self._backend, padding, algorithm, self, signature - ) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/utils.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/utils.py deleted file mode 100644 index 5b404def..00000000 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import annotations - -import typing - -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.backend import Backend - - -def _evp_pkey_derive(backend: Backend, evp_pkey, peer_public_key) -> bytes: - ctx = backend._lib.EVP_PKEY_CTX_new(evp_pkey, backend._ffi.NULL) - backend.openssl_assert(ctx != backend._ffi.NULL) - ctx = backend._ffi.gc(ctx, backend._lib.EVP_PKEY_CTX_free) - res = backend._lib.EVP_PKEY_derive_init(ctx) - backend.openssl_assert(res == 1) - - if backend._lib.Cryptography_HAS_EVP_PKEY_SET_PEER_EX: - res = backend._lib.EVP_PKEY_derive_set_peer_ex( - ctx, peer_public_key._evp_pkey, 0 - ) - else: - res = backend._lib.EVP_PKEY_derive_set_peer( - ctx, peer_public_key._evp_pkey - ) - backend.openssl_assert(res == 1) - - keylen = backend._ffi.new("size_t *") - res = backend._lib.EVP_PKEY_derive(ctx, backend._ffi.NULL, keylen) - backend.openssl_assert(res == 1) - backend.openssl_assert(keylen[0] > 0) - buf = backend._ffi.new("unsigned char[]", keylen[0]) - res = backend._lib.EVP_PKEY_derive(ctx, buf, keylen) - if res != 1: - errors = backend._consume_errors() - raise ValueError("Error computing shared key.", errors) - - return backend._ffi.buffer(buf, keylen[0])[:] - - -def _calculate_digest_and_algorithm( - data: bytes, - algorithm: typing.Union[Prehashed, hashes.HashAlgorithm], -) -> typing.Tuple[bytes, hashes.HashAlgorithm]: - if not isinstance(algorithm, Prehashed): - hash_ctx = hashes.Hash(algorithm) - hash_ctx.update(data) - data = hash_ctx.finalize() - else: - algorithm = algorithm._algorithm - - if len(data) != algorithm.digest_size: - raise ValueError( - "The provided data must be the same length as the hash " - "algorithm's digest size." - ) - - return (data, algorithm) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc index b018cd61..af2393f6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust.abi3.so b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust.abi3.so index de9d24d4..f0883977 100755 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust.abi3.so and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust.abi3.so differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/__init__.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/__init__.pyi index 94a37a20..2f4eef4e 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/__init__.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -2,33 +2,36 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import types import typing -def check_pkcs7_padding(data: bytes) -> bool: ... -def check_ansix923_padding(data: bytes) -> bool: ... +from cryptography.hazmat.primitives import padding +from cryptography.utils import Buffer + +class PKCS7PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class PKCS7UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... class ObjectIdentifier: - def __init__(self, val: str) -> None: ... + def __init__(self, value: str) -> None: ... @property def dotted_string(self) -> str: ... @property def _name(self) -> str: ... T = typing.TypeVar("T") - -class FixedPool(typing.Generic[T]): - def __init__( - self, - create: typing.Callable[[], T], - ) -> None: ... - def acquire(self) -> PoolAcquisition[T]: ... - -class PoolAcquisition(typing.Generic[T]): - def __enter__(self) -> T: ... - def __exit__( - self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc_value: typing.Optional[BaseException], - exc_tb: typing.Optional[types.TracebackType], - ) -> None: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/asn1.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/asn1.pyi index a8369ba8..3b5f208e 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/asn1.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/asn1.pyi @@ -2,15 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing - -class TestCertificate: - not_after_tag: int - not_before_tag: int - issuer_value_tags: typing.List[int] - subject_value_tags: typing.List[int] - -def decode_dss_signature(signature: bytes) -> typing.Tuple[int, int]: ... +def decode_dss_signature(signature: bytes) -> tuple[int, int]: ... def encode_dss_signature(r: int, s: int) -> bytes: ... def parse_spki_for_data(data: bytes) -> bytes: ... -def test_parse_certificate(data: bytes) -> TestCertificate: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi new file mode 100644 index 00000000..8563c11f --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi @@ -0,0 +1,32 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +import typing + +def encode_der(value: typing.Any) -> bytes: ... +def non_root_python_to_rust(cls: type) -> Type: ... + +# Type is a Rust enum with tuple variants. For now, we express the type +# annotations like this: +class Type: + Sequence: typing.ClassVar[type] + PyInt: typing.ClassVar[type] + +class Annotation: + def __new__( + cls, + ) -> Annotation: ... + +class AnnotatedType: + inner: Type + annotation: Annotation + + def __new__(cls, inner: Type, annotation: Annotation) -> AnnotatedType: ... + +class AnnotatedTypeObject: + annotated_type: AnnotatedType + value: typing.Any + + def __new__( + cls, annotated_type: AnnotatedType, value: typing.Any + ) -> AnnotatedTypeObject: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/ocsp.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/ocsp.pyi index 4671eb9b..103e96c1 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/ocsp.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -2,24 +2,116 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import typing +import datetime +from collections.abc import Iterator -from cryptography.hazmat.primitives import hashes +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes -from cryptography.x509.ocsp import ( - OCSPRequest, - OCSPRequestBuilder, - OCSPResponse, - OCSPResponseBuilder, - OCSPResponseStatus, -) +from cryptography.x509 import ocsp -def load_der_ocsp_request(data: bytes) -> OCSPRequest: ... -def load_der_ocsp_response(data: bytes) -> OCSPResponse: ... -def create_ocsp_request(builder: OCSPRequestBuilder) -> OCSPRequest: ... +class OCSPRequest: + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def extensions(self) -> x509.Extensions: ... + +class OCSPResponse: + @property + def responses(self) -> Iterator[OCSPSingleResponse]: ... + @property + def response_status(self) -> ocsp.OCSPResponseStatus: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_response_bytes(self) -> bytes: ... + @property + def certificates(self) -> list[x509.Certificate]: ... + @property + def responder_key_hash(self) -> bytes | None: ... + @property + def responder_name(self) -> x509.Name | None: ... + @property + def produced_at(self) -> datetime.datetime: ... + @property + def produced_at_utc(self) -> datetime.datetime: ... + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def single_extensions(self) -> x509.Extensions: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + +class OCSPSingleResponse: + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + +def load_der_ocsp_request(data: bytes) -> ocsp.OCSPRequest: ... +def load_der_ocsp_response(data: bytes) -> ocsp.OCSPResponse: ... +def create_ocsp_request( + builder: ocsp.OCSPRequestBuilder, +) -> ocsp.OCSPRequest: ... def create_ocsp_response( - status: OCSPResponseStatus, - builder: typing.Optional[OCSPResponseBuilder], - private_key: typing.Optional[PrivateKeyTypes], - hash_algorithm: typing.Optional[hashes.HashAlgorithm], -) -> OCSPResponse: ... + status: ocsp.OCSPResponseStatus, + builder: ocsp.OCSPResponseBuilder | None, + private_key: PrivateKeyTypes | None, + hash_algorithm: hashes.HashAlgorithm | None, +) -> ocsp.OCSPResponse: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi index 82f30d20..5fb3cb24 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -5,37 +5,66 @@ import typing from cryptography.hazmat.bindings._rust.openssl import ( + aead, + ciphers, + cmac, dh, dsa, + ec, ed448, ed25519, hashes, hmac, kdf, + keys, poly1305, + rsa, x448, x25519, ) __all__ = [ - "openssl_version", - "raise_openssl_error", + "aead", + "ciphers", + "cmac", "dh", "dsa", + "ec", + "ed448", + "ed25519", "hashes", "hmac", "kdf", - "ed448", - "ed25519", + "keys", + "openssl_version", + "openssl_version_text", "poly1305", + "raise_openssl_error", + "rsa", "x448", "x25519", ] +CRYPTOGRAPHY_IS_LIBRESSL: bool +CRYPTOGRAPHY_IS_BORINGSSL: bool +CRYPTOGRAPHY_IS_AWSLC: bool +CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_309_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_330_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_350_OR_GREATER: bool + +class Providers: ... + +_legacy_provider_loaded: bool +_providers: Providers + def openssl_version() -> int: ... +def openssl_version_text() -> str: ... def raise_openssl_error() -> typing.NoReturn: ... -def capture_error_stack() -> typing.List[OpenSSLError]: ... +def capture_error_stack() -> list[OpenSSLError]: ... def is_fips_enabled() -> bool: ... +def enable_fips(providers: Providers) -> None: ... class OpenSSLError: @property @@ -44,4 +73,3 @@ class OpenSSLError: def reason(self) -> int: ... @property def reason_text(self) -> bytes: ... - def _lib_reason_match(self, lib: int, reason: int) -> bool: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/aead.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/aead.pyi new file mode 100644 index 00000000..831fcd14 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/aead.pyi @@ -0,0 +1,107 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from collections.abc import Sequence + +from cryptography.utils import Buffer + +class AESGCM: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + +class ChaCha20Poly1305: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key() -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + +class AESCCM: + def __init__(self, key: Buffer, tag_length: int = 16) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + +class AESSIV: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + ) -> bytes: ... + def decrypt( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + ) -> bytes: ... + +class AESOCB3: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + +class AESGCMSIV: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi new file mode 100644 index 00000000..a48fb017 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi @@ -0,0 +1,38 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes + +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADEncryptionContext: ... +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None +) -> ciphers.CipherContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADDecryptionContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None +) -> ciphers.CipherContext: ... +def cipher_supported( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode +) -> bool: ... +def _advance( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... +def _advance_aad( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... + +class CipherContext: ... +class AEADEncryptionContext: ... +class AEADDecryptionContext: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi new file mode 100644 index 00000000..9c03508b --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers + +class CMAC: + def __init__( + self, + algorithm: ciphers.BlockCipherAlgorithm, + backend: typing.Any = None, + ) -> None: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> CMAC: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dh.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dh.pyi index bfd005d9..08733d74 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dh.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dh.pyi @@ -2,6 +2,8 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import typing + from cryptography.hazmat.primitives.asymmetric import dh MIN_MODULUS_SIZE: int @@ -10,13 +12,40 @@ class DHPrivateKey: ... class DHPublicKey: ... class DHParameters: ... -def generate_parameters(generator: int, key_size: int) -> dh.DHParameters: ... -def private_key_from_ptr(ptr: int) -> dh.DHPrivateKey: ... -def public_key_from_ptr(ptr: int) -> dh.DHPublicKey: ... -def from_pem_parameters(data: bytes) -> dh.DHParameters: ... -def from_der_parameters(data: bytes) -> dh.DHParameters: ... -def from_private_numbers(numbers: dh.DHPrivateNumbers) -> dh.DHPrivateKey: ... -def from_public_numbers(numbers: dh.DHPublicNumbers) -> dh.DHPublicKey: ... -def from_parameter_numbers( - numbers: dh.DHParameterNumbers, +class DHPrivateNumbers: + def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: ... + def private_key(self, backend: typing.Any = None) -> dh.DHPrivateKey: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DHPublicNumbers: ... + +class DHPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DHParameterNumbers + ) -> None: ... + def public_key(self, backend: typing.Any = None) -> dh.DHPublicKey: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DHParameterNumbers: ... + +class DHParameterNumbers: + def __init__(self, p: int, g: int, q: int | None = None) -> None: ... + def parameters(self, backend: typing.Any = None) -> dh.DHParameters: ... + @property + def p(self) -> int: ... + @property + def g(self) -> int: ... + @property + def q(self) -> int | None: ... + +def generate_parameters( + generator: int, key_size: int, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_pem_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_der_parameters( + data: bytes, backend: typing.Any = None ) -> dh.DHParameters: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi index 5a56f256..0922a4c4 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi @@ -2,19 +2,40 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import typing + from cryptography.hazmat.primitives.asymmetric import dsa class DSAPrivateKey: ... class DSAPublicKey: ... class DSAParameters: ... +class DSAPrivateNumbers: + def __init__(self, x: int, public_numbers: DSAPublicNumbers) -> None: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DSAPublicNumbers: ... + def private_key(self, backend: typing.Any = None) -> dsa.DSAPrivateKey: ... + +class DSAPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DSAParameterNumbers + ) -> None: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DSAParameterNumbers: ... + def public_key(self, backend: typing.Any = None) -> dsa.DSAPublicKey: ... + +class DSAParameterNumbers: + def __init__(self, p: int, q: int, g: int) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def g(self) -> int: ... + def parameters(self, backend: typing.Any = None) -> dsa.DSAParameters: ... + def generate_parameters(key_size: int) -> dsa.DSAParameters: ... -def private_key_from_ptr(ptr: int) -> dsa.DSAPrivateKey: ... -def public_key_from_ptr(ptr: int) -> dsa.DSAPublicKey: ... -def from_private_numbers( - numbers: dsa.DSAPrivateNumbers, -) -> dsa.DSAPrivateKey: ... -def from_public_numbers(numbers: dsa.DSAPublicNumbers) -> dsa.DSAPublicKey: ... -def from_parameter_numbers( - numbers: dsa.DSAParameterNumbers, -) -> dsa.DSAParameters: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ec.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ec.pyi new file mode 100644 index 00000000..5c3b7bf6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ec.pyi @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import ec + +class ECPrivateKey: ... +class ECPublicKey: ... + +class EllipticCurvePrivateNumbers: + def __init__( + self, private_value: int, public_numbers: EllipticCurvePublicNumbers + ) -> None: ... + def private_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePrivateKey: ... + @property + def private_value(self) -> int: ... + @property + def public_numbers(self) -> EllipticCurvePublicNumbers: ... + +class EllipticCurvePublicNumbers: + def __init__(self, x: int, y: int, curve: ec.EllipticCurve) -> None: ... + def public_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePublicKey: ... + @property + def x(self) -> int: ... + @property + def y(self) -> int: ... + @property + def curve(self) -> ec.EllipticCurve: ... + def __eq__(self, other: object) -> bool: ... + +def curve_supported(curve: ec.EllipticCurve) -> bool: ... +def generate_private_key( + curve: ec.EllipticCurve, backend: typing.Any = None +) -> ec.EllipticCurvePrivateKey: ... +def from_private_numbers( + numbers: ec.EllipticCurvePrivateNumbers, +) -> ec.EllipticCurvePrivateKey: ... +def from_public_numbers( + numbers: ec.EllipticCurvePublicNumbers, +) -> ec.EllipticCurvePublicKey: ... +def from_public_bytes( + curve: ec.EllipticCurve, data: bytes +) -> ec.EllipticCurvePublicKey: ... +def derive_private_key( + private_value: int, curve: ec.EllipticCurve +) -> ec.EllipticCurvePrivateKey: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi index c7f127f0..f85b3d1b 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -3,12 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.utils import Buffer class Ed25519PrivateKey: ... class Ed25519PublicKey: ... def generate_key() -> ed25519.Ed25519PrivateKey: ... -def private_key_from_ptr(ptr: int) -> ed25519.Ed25519PrivateKey: ... -def public_key_from_ptr(ptr: int) -> ed25519.Ed25519PublicKey: ... -def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> ed25519.Ed25519PrivateKey: ... def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi index 1cf5f177..c8ca0ecb 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -3,12 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import ed448 +from cryptography.utils import Buffer class Ed448PrivateKey: ... class Ed448PublicKey: ... def generate_key() -> ed448.Ed448PrivateKey: ... -def private_key_from_ptr(ptr: int) -> ed448.Ed448PrivateKey: ... -def public_key_from_ptr(ptr: int) -> ed448.Ed448PublicKey: ... -def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ... +def from_private_bytes(data: Buffer) -> ed448.Ed448PrivateKey: ... def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi index ca5f42a0..6bfd295a 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -5,6 +5,7 @@ import typing from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer class Hash(hashes.HashContext): def __init__( @@ -12,6 +13,16 @@ class Hash(hashes.HashContext): ) -> None: ... @property def algorithm(self) -> hashes.HashAlgorithm: ... - def update(self, data: bytes) -> None: ... + def update(self, data: Buffer) -> None: ... def finalize(self) -> bytes: ... def copy(self) -> Hash: ... + +def hash_supported(algorithm: hashes.HashAlgorithm) -> bool: ... + +class XOFHash: + def __init__(self, algorithm: hashes.ExtendableOutputFunction) -> None: ... + @property + def algorithm(self) -> hashes.ExtendableOutputFunction: ... + def update(self, data: Buffer) -> None: ... + def squeeze(self, length: int) -> bytes: ... + def copy(self) -> XOFHash: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi index e38d9b54..3883d1b1 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi @@ -5,17 +5,18 @@ import typing from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer class HMAC(hashes.HashContext): def __init__( self, - key: bytes, + key: Buffer, algorithm: hashes.HashAlgorithm, backend: typing.Any = None, ) -> None: ... @property def algorithm(self) -> hashes.HashAlgorithm: ... - def update(self, data: bytes) -> None: ... + def update(self, data: Buffer) -> None: ... def finalize(self) -> bytes: ... def verify(self, signature: bytes) -> None: ... def copy(self) -> HMAC: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi index 034a8fed..9e2d8d99 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi @@ -2,21 +2,71 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import typing + from cryptography.hazmat.primitives.hashes import HashAlgorithm +from cryptography.utils import Buffer def derive_pbkdf2_hmac( - key_material: bytes, + key_material: Buffer, algorithm: HashAlgorithm, salt: bytes, iterations: int, length: int, ) -> bytes: ... -def derive_scrypt( - key_material: bytes, - salt: bytes, - n: int, - r: int, - p: int, - max_mem: int, - length: int, -) -> bytes: ... + +class Scrypt: + def __init__( + self, + salt: bytes, + length: int, + n: int, + r: int, + p: int, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class Argon2id: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class HKDF: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes | None, + info: bytes | None, + backend: typing.Any = None, + ): ... + def derive(self, key_material: Buffer) -> bytes: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class HKDFExpand: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + info: bytes | None, + backend: typing.Any = None, + ): ... + def derive(self, key_material: Buffer) -> bytes: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/keys.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/keys.pyi new file mode 100644 index 00000000..404057e0 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/keys.pyi @@ -0,0 +1,34 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, +) +from cryptography.utils import Buffer + +def load_der_private_key( + data: Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_pem_private_key( + data: Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_der_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... +def load_pem_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi index 2e9b0a9e..45a2a39f 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi @@ -2,12 +2,14 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from cryptography.utils import Buffer + class Poly1305: - def __init__(self, key: bytes) -> None: ... + def __init__(self, key: Buffer) -> None: ... @staticmethod - def generate_tag(key: bytes, data: bytes) -> bytes: ... + def generate_tag(key: Buffer, data: Buffer) -> bytes: ... @staticmethod - def verify_tag(key: bytes, data: bytes, tag: bytes) -> None: ... - def update(self, data: bytes) -> None: ... + def verify_tag(key: Buffer, data: Buffer, tag: bytes) -> None: ... + def update(self, data: Buffer) -> None: ... def finalize(self) -> bytes: ... def verify(self, tag: bytes) -> None: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi new file mode 100644 index 00000000..ef7752dd --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi @@ -0,0 +1,55 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import rsa + +class RSAPrivateKey: ... +class RSAPublicKey: ... + +class RSAPrivateNumbers: + def __init__( + self, + p: int, + q: int, + d: int, + dmp1: int, + dmq1: int, + iqmp: int, + public_numbers: RSAPublicNumbers, + ) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def d(self) -> int: ... + @property + def dmp1(self) -> int: ... + @property + def dmq1(self) -> int: ... + @property + def iqmp(self) -> int: ... + @property + def public_numbers(self) -> RSAPublicNumbers: ... + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> rsa.RSAPrivateKey: ... + +class RSAPublicNumbers: + def __init__(self, e: int, n: int) -> None: ... + @property + def n(self) -> int: ... + @property + def e(self) -> int: ... + def public_key(self, backend: typing.Any = None) -> rsa.RSAPublicKey: ... + +def generate_private_key( + public_exponent: int, + key_size: int, +) -> rsa.RSAPrivateKey: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi index 90f7cbdd..38d2addd 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -3,12 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.utils import Buffer class X25519PrivateKey: ... class X25519PublicKey: ... def generate_key() -> x25519.X25519PrivateKey: ... -def private_key_from_ptr(ptr: int) -> x25519.X25519PrivateKey: ... -def public_key_from_ptr(ptr: int) -> x25519.X25519PublicKey: ... -def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> x25519.X25519PrivateKey: ... def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x448.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x448.pyi index d326c8d2..3ac09809 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x448.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -3,12 +3,11 @@ # for complete details. from cryptography.hazmat.primitives.asymmetric import x448 +from cryptography.utils import Buffer class X448PrivateKey: ... class X448PublicKey: ... def generate_key() -> x448.X448PrivateKey: ... -def private_key_from_ptr(ptr: int) -> x448.X448PrivateKey: ... -def public_key_from_ptr(ptr: int) -> x448.X448PublicKey: ... -def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ... +def from_private_bytes(data: Buffer) -> x448.X448PrivateKey: ... def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs12.pyi new file mode 100644 index 00000000..b25becb6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs12.pyi @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.hazmat.primitives.serialization import ( + KeySerializationEncryption, +) +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + PKCS12KeyAndCertificates, + PKCS12PrivateKeyTypes, +) +from cryptography.utils import Buffer + +class PKCS12Certificate: + def __init__( + self, cert: x509.Certificate, friendly_name: bytes | None + ) -> None: ... + @property + def friendly_name(self) -> bytes | None: ... + @property + def certificate(self) -> x509.Certificate: ... + +def load_key_and_certificates( + data: Buffer, + password: Buffer | None, + backend: typing.Any = None, +) -> tuple[ + PrivateKeyTypes | None, + x509.Certificate | None, + list[x509.Certificate], +]: ... +def load_pkcs12( + data: bytes, + password: bytes | None, + backend: typing.Any = None, +) -> PKCS12KeyAndCertificates: ... +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... +def serialize_key_and_certificates( + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: Iterable[x509.Certificate | PKCS12Certificate] | None, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs7.pyi index 66bd8509..358b1358 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs7.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -1,15 +1,50 @@ -import typing +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from collections.abc import Iterable from cryptography import x509 from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import pkcs7 def serialize_certificates( - certs: typing.List[x509.Certificate], + certs: list[x509.Certificate], encoding: serialization.Encoding, ) -> bytes: ... +def encrypt_and_serialize( + builder: pkcs7.PKCS7EnvelopeBuilder, + content_encryption_algorithm: pkcs7.ContentEncryptionAlgorithm, + encoding: serialization.Encoding, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... def sign_and_serialize( builder: pkcs7.PKCS7SignatureBuilder, encoding: serialization.Encoding, - options: typing.Iterable[pkcs7.PKCS7Options], + options: Iterable[pkcs7.PKCS7Options], ) -> bytes: ... +def decrypt_der( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_pem( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_smime( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def load_pem_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_der_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/test_support.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/test_support.pyi new file mode 100644 index 00000000..c6c6d0bb --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/test_support.pyi @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.utils import Buffer + +class TestCertificate: + not_after_tag: int + not_before_tag: int + issuer_value_tags: list[int] + subject_value_tags: list[int] + +def test_parse_certificate(data: bytes) -> TestCertificate: ... +def pkcs7_verify( + encoding: serialization.Encoding, + sig: bytes, + msg: Buffer | None, + certs: list[x509.Certificate], + options: list[pkcs7.PKCS7Options], +) -> None: ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/x509.pyi b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/x509.pyi index 24b2f5e3..83c3441b 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/x509.pyi +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/_rust/x509.pyi @@ -2,43 +2,300 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import datetime import typing +from collections.abc import Iterator from cryptography import x509 -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 -from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, + PrivateKeyTypes, +) +from cryptography.x509 import certificate_transparency -def load_pem_x509_certificate(data: bytes) -> x509.Certificate: ... +def load_pem_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... +def load_der_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... def load_pem_x509_certificates( data: bytes, -) -> typing.List[x509.Certificate]: ... -def load_der_x509_certificate(data: bytes) -> x509.Certificate: ... -def load_pem_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... -def load_der_x509_crl(data: bytes) -> x509.CertificateRevocationList: ... -def load_pem_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ... -def load_der_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ... +) -> list[x509.Certificate]: ... +def load_pem_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_der_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_pem_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... +def load_der_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... def encode_name_bytes(name: x509.Name) -> bytes: ... def encode_extension_value(extension: x509.ExtensionType) -> bytes: ... def create_x509_certificate( builder: x509.CertificateBuilder, private_key: PrivateKeyTypes, - hash_algorithm: typing.Optional[hashes.HashAlgorithm], - padding: typing.Optional[typing.Union[PKCS1v15, PSS]], + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, ) -> x509.Certificate: ... def create_x509_csr( builder: x509.CertificateSigningRequestBuilder, private_key: PrivateKeyTypes, - hash_algorithm: typing.Optional[hashes.HashAlgorithm], + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, ) -> x509.CertificateSigningRequest: ... def create_x509_crl( builder: x509.CertificateRevocationListBuilder, private_key: PrivateKeyTypes, - hash_algorithm: typing.Optional[hashes.HashAlgorithm], + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, ) -> x509.CertificateRevocationList: ... -class Sct: ... -class Certificate: ... +class Sct: + @property + def version(self) -> certificate_transparency.Version: ... + @property + def log_id(self) -> bytes: ... + @property + def timestamp(self) -> datetime.datetime: ... + @property + def entry_type(self) -> certificate_transparency.LogEntryType: ... + @property + def signature_hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def signature_algorithm( + self, + ) -> certificate_transparency.SignatureAlgorithm: ... + @property + def signature(self) -> bytes: ... + @property + def extension_bytes(self) -> bytes: ... + +class Certificate: + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + @property + def serial_number(self) -> int: ... + @property + def version(self) -> x509.Version: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def public_key_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def not_valid_before(self) -> datetime.datetime: ... + @property + def not_valid_before_utc(self) -> datetime.datetime: ... + @property + def not_valid_after(self) -> datetime.datetime: ... + @property + def not_valid_after_utc(self) -> datetime.datetime: ... + @property + def issuer(self) -> x509.Name: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certificate_bytes(self) -> bytes: ... + @property + def tbs_precertificate_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def verify_directly_issued_by(self, issuer: Certificate) -> None: ... + class RevokedCertificate: ... -class CertificateRevocationList: ... -class CertificateSigningRequest: ... + +class CertificateRevocationList: + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + def get_revoked_certificate_by_serial_number( + self, serial_number: int + ) -> x509.RevokedCertificate | None: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def issuer(self) -> x509.Name: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def last_update(self) -> datetime.datetime: ... + @property + def last_update_utc(self) -> datetime.datetime: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certlist_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __len__(self) -> int: ... + @typing.overload + def __getitem__(self, idx: int) -> x509.RevokedCertificate: ... + @typing.overload + def __getitem__(self, idx: slice) -> list[x509.RevokedCertificate]: ... + def __iter__(self) -> Iterator[x509.RevokedCertificate]: ... + def is_signature_valid( + self, public_key: CertificateIssuerPublicKeyTypes + ) -> bool: ... + +class CertificateSigningRequest: + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def attributes(self) -> x509.Attributes: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certrequest_bytes(self) -> bytes: ... + @property + def is_signature_valid(self) -> bool: ... + +class PolicyBuilder: + def time(self, time: datetime.datetime) -> PolicyBuilder: ... + def store(self, store: Store) -> PolicyBuilder: ... + def max_chain_depth(self, max_chain_depth: int) -> PolicyBuilder: ... + def extension_policies( + self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy + ) -> PolicyBuilder: ... + def build_client_verifier(self) -> ClientVerifier: ... + def build_server_verifier( + self, subject: x509.verification.Subject + ) -> ServerVerifier: ... + +class Policy: + @property + def max_chain_depth(self) -> int: ... + @property + def subject(self) -> x509.verification.Subject | None: ... + @property + def validation_time(self) -> datetime.datetime: ... + @property + def extended_key_usage(self) -> x509.ObjectIdentifier: ... + @property + def minimum_rsa_modulus(self) -> int: ... + +class Criticality: + CRITICAL: Criticality + AGNOSTIC: Criticality + NON_CRITICAL: Criticality + +T = typing.TypeVar("T", contravariant=True, bound=x509.ExtensionType) + +MaybeExtensionValidatorCallback = typing.Callable[ + [ + Policy, + x509.Certificate, + T | None, + ], + None, +] + +PresentExtensionValidatorCallback = typing.Callable[ + [Policy, x509.Certificate, T], + None, +] + +class ExtensionPolicy: + @staticmethod + def permit_all() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ca() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ee() -> ExtensionPolicy: ... + def require_not_present( + self, extension_type: type[x509.ExtensionType] + ) -> ExtensionPolicy: ... + def may_be_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: MaybeExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + def require_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: PresentExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + +class VerifiedClient: + @property + def subjects(self) -> list[x509.GeneralName] | None: ... + @property + def chain(self) -> list[x509.Certificate]: ... + +class ClientVerifier: + @property + def policy(self) -> Policy: ... + @property + def store(self) -> Store: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> VerifiedClient: ... + +class ServerVerifier: + @property + def policy(self) -> Policy: ... + @property + def store(self) -> Store: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> list[x509.Certificate]: ... + +class Store: + def __init__(self, certs: list[x509.Certificate]) -> None: ... + +class VerificationError(Exception): ... diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc index 72db8cd8..55aa98fe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc index ced5cc3c..561fea55 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc index 086ac340..f1ccfe09 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/_conditional.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/_conditional.py index 5e8ecd04..063bcf5b 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/_conditional.py @@ -4,17 +4,15 @@ from __future__ import annotations -import typing - -def cryptography_has_set_cert_cb() -> typing.List[str]: +def cryptography_has_set_cert_cb() -> list[str]: return [ "SSL_CTX_set_cert_cb", "SSL_set_cert_cb", ] -def cryptography_has_ssl_st() -> typing.List[str]: +def cryptography_has_ssl_st() -> list[str]: return [ "SSL_ST_BEFORE", "SSL_ST_OK", @@ -23,72 +21,20 @@ def cryptography_has_ssl_st() -> typing.List[str]: ] -def cryptography_has_tls_st() -> typing.List[str]: +def cryptography_has_tls_st() -> list[str]: return [ "TLS_ST_BEFORE", "TLS_ST_OK", ] -def cryptography_has_evp_pkey_dhx() -> typing.List[str]: - return [ - "EVP_PKEY_DHX", - ] - - -def cryptography_has_mem_functions() -> typing.List[str]: - return [ - "Cryptography_CRYPTO_set_mem_functions", - ] - - -def cryptography_has_x509_store_ctx_get_issuer() -> typing.List[str]: - return [ - "X509_STORE_set_get_issuer", - ] - - -def cryptography_has_ed448() -> typing.List[str]: - return [ - "EVP_PKEY_ED448", - "NID_ED448", - ] - - -def cryptography_has_ed25519() -> typing.List[str]: - return [ - "NID_ED25519", - "EVP_PKEY_ED25519", - ] - - -def cryptography_has_poly1305() -> typing.List[str]: - return [ - "NID_poly1305", - "EVP_PKEY_POLY1305", - ] - - -def cryptography_has_evp_digestfinal_xof() -> typing.List[str]: - return [ - "EVP_DigestFinalXOF", - ] - - -def cryptography_has_fips() -> typing.List[str]: - return [ - "FIPS_mode_set", - "FIPS_mode", - ] - - -def cryptography_has_ssl_sigalgs() -> typing.List[str]: +def cryptography_has_ssl_sigalgs() -> list[str]: return [ "SSL_CTX_set1_sigalgs_list", ] -def cryptography_has_psk() -> typing.List[str]: +def cryptography_has_psk() -> list[str]: return [ "SSL_CTX_use_psk_identity_hint", "SSL_CTX_set_psk_server_callback", @@ -96,7 +42,7 @@ def cryptography_has_psk() -> typing.List[str]: ] -def cryptography_has_psk_tlsv13() -> typing.List[str]: +def cryptography_has_psk_tlsv13() -> list[str]: return [ "SSL_CTX_set_psk_find_session_callback", "SSL_CTX_set_psk_use_session_callback", @@ -108,7 +54,7 @@ def cryptography_has_psk_tlsv13() -> typing.List[str]: ] -def cryptography_has_custom_ext() -> typing.List[str]: +def cryptography_has_custom_ext() -> list[str]: return [ "SSL_CTX_add_client_custom_ext", "SSL_CTX_add_server_custom_ext", @@ -116,10 +62,15 @@ def cryptography_has_custom_ext() -> typing.List[str]: ] -def cryptography_has_tlsv13_functions() -> typing.List[str]: +def cryptography_has_tlsv13_functions() -> list[str]: + return [ + "SSL_CTX_set_ciphersuites", + ] + + +def cryptography_has_tlsv13_hs_functions() -> list[str]: return [ "SSL_VERIFY_POST_HANDSHAKE", - "SSL_CTX_set_ciphersuites", "SSL_verify_client_post_handshake", "SSL_CTX_set_post_handshake_auth", "SSL_set_post_handshake_auth", @@ -130,16 +81,13 @@ def cryptography_has_tlsv13_functions() -> typing.List[str]: ] -def cryptography_has_raw_key() -> typing.List[str]: +def cryptography_has_ssl_verify_client_post_handshake() -> list[str]: return [ - "EVP_PKEY_new_raw_private_key", - "EVP_PKEY_new_raw_public_key", - "EVP_PKEY_get_raw_private_key", - "EVP_PKEY_get_raw_public_key", + "SSL_verify_client_post_handshake", ] -def cryptography_has_engine() -> typing.List[str]: +def cryptography_has_engine() -> list[str]: return [ "ENGINE_by_id", "ENGINE_init", @@ -158,13 +106,13 @@ def cryptography_has_engine() -> typing.List[str]: ] -def cryptography_has_verified_chain() -> typing.List[str]: +def cryptography_has_verified_chain() -> list[str]: return [ "SSL_get0_verified_chain", ] -def cryptography_has_srtp() -> typing.List[str]: +def cryptography_has_srtp() -> list[str]: return [ "SSL_CTX_set_tlsext_use_srtp", "SSL_set_tlsext_use_srtp", @@ -172,36 +120,19 @@ def cryptography_has_srtp() -> typing.List[str]: ] -def cryptography_has_providers() -> typing.List[str]: - return [ - "OSSL_PROVIDER_load", - "OSSL_PROVIDER_unload", - "ERR_LIB_PROV", - "PROV_R_WRONG_FINAL_BLOCK_LENGTH", - "PROV_R_BAD_DECRYPT", - ] - - -def cryptography_has_op_no_renegotiation() -> typing.List[str]: +def cryptography_has_op_no_renegotiation() -> list[str]: return [ "SSL_OP_NO_RENEGOTIATION", ] -def cryptography_has_dtls_get_data_mtu() -> typing.List[str]: +def cryptography_has_dtls_get_data_mtu() -> list[str]: return [ "DTLS_get_data_mtu", ] -def cryptography_has_300_fips() -> typing.List[str]: - return [ - "EVP_default_properties_is_fips_enabled", - "EVP_default_properties_enable_fips", - ] - - -def cryptography_has_ssl_cookie() -> typing.List[str]: +def cryptography_has_ssl_cookie() -> list[str]: return [ "SSL_OP_COOKIE_EXCHANGE", "DTLSv1_listen", @@ -210,67 +141,28 @@ def cryptography_has_ssl_cookie() -> typing.List[str]: ] -def cryptography_has_pkcs7_funcs() -> typing.List[str]: +def cryptography_has_prime_checks() -> list[str]: return [ - "SMIME_write_PKCS7", - "PEM_write_bio_PKCS7_stream", - "PKCS7_sign_add_signer", - "PKCS7_final", - "PKCS7_verify", - "SMIME_read_PKCS7", - "PKCS7_get0_signers", - ] - - -def cryptography_has_bn_flags() -> typing.List[str]: - return [ - "BN_FLG_CONSTTIME", - "BN_set_flags", "BN_prime_checks_for_size", ] -def cryptography_has_evp_pkey_dh() -> typing.List[str]: - return [ - "EVP_PKEY_set1_DH", - ] - - -def cryptography_has_300_evp_cipher() -> typing.List[str]: - return ["EVP_CIPHER_fetch", "EVP_CIPHER_free"] - - -def cryptography_has_unexpected_eof_while_reading() -> typing.List[str]: +def cryptography_has_unexpected_eof_while_reading() -> list[str]: return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] -def cryptography_has_pkcs12_set_mac() -> typing.List[str]: - return ["PKCS12_set_mac"] - - -def cryptography_has_ssl_op_ignore_unexpected_eof() -> typing.List[str]: +def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]: return [ "SSL_OP_IGNORE_UNEXPECTED_EOF", ] -def cryptography_has_get_extms_support() -> typing.List[str]: +def cryptography_has_get_extms_support() -> list[str]: return ["SSL_get_extms_support"] -def cryptography_has_evp_pkey_set_peer_ex() -> typing.List[str]: - return ["EVP_PKEY_derive_set_peer_ex"] - - -def cryptography_has_evp_aead() -> typing.List[str]: - return [ - "EVP_aead_chacha20_poly1305", - "EVP_AEAD_CTX_free", - "EVP_AEAD_CTX_seal", - "EVP_AEAD_CTX_open", - "EVP_AEAD_max_overhead", - "Cryptography_EVP_AEAD_CTX_new", - ] +def cryptography_has_ssl_get0_group_name() -> list[str]: + return ["SSL_get0_group_name"] # This is a mapping of @@ -282,48 +174,34 @@ CONDITIONAL_NAMES = { "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, - "Cryptography_HAS_EVP_PKEY_DHX": cryptography_has_evp_pkey_dhx, - "Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions, - "Cryptography_HAS_X509_STORE_CTX_GET_ISSUER": ( - cryptography_has_x509_store_ctx_get_issuer - ), - "Cryptography_HAS_ED448": cryptography_has_ed448, - "Cryptography_HAS_ED25519": cryptography_has_ed25519, - "Cryptography_HAS_POLY1305": cryptography_has_poly1305, - "Cryptography_HAS_FIPS": cryptography_has_fips, "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, "Cryptography_HAS_PSK": cryptography_has_psk, "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, - "Cryptography_HAS_RAW_KEY": cryptography_has_raw_key, - "Cryptography_HAS_EVP_DIGESTFINAL_XOF": ( - cryptography_has_evp_digestfinal_xof + "Cryptography_HAS_TLSv1_3_HS_FUNCTIONS": ( + cryptography_has_tlsv13_hs_functions + ), + "Cryptography_HAS_SSL_VERIFY_CLIENT_POST_HANDSHAKE": ( + cryptography_has_ssl_verify_client_post_handshake ), "Cryptography_HAS_ENGINE": cryptography_has_engine, "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, - "Cryptography_HAS_PROVIDERS": cryptography_has_providers, "Cryptography_HAS_OP_NO_RENEGOTIATION": ( cryptography_has_op_no_renegotiation ), "Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu, - "Cryptography_HAS_300_FIPS": cryptography_has_300_fips, "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, - "Cryptography_HAS_PKCS7_FUNCS": cryptography_has_pkcs7_funcs, - "Cryptography_HAS_BN_FLAGS": cryptography_has_bn_flags, - "Cryptography_HAS_EVP_PKEY_DH": cryptography_has_evp_pkey_dh, - "Cryptography_HAS_300_EVP_CIPHER": cryptography_has_300_evp_cipher, + "Cryptography_HAS_PRIME_CHECKS": cryptography_has_prime_checks, "Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": ( cryptography_has_unexpected_eof_while_reading ), - "Cryptography_HAS_PKCS12_SET_MAC": cryptography_has_pkcs12_set_mac, "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": ( cryptography_has_ssl_op_ignore_unexpected_eof ), "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support, - "Cryptography_HAS_EVP_PKEY_SET_PEER_EX": ( - cryptography_has_evp_pkey_set_peer_ex + "Cryptography_HAS_SSL_GET0_GROUP_NAME": ( + cryptography_has_ssl_get0_group_name ), - "Cryptography_HAS_EVP_AEAD": (cryptography_has_evp_aead), } diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/binding.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/binding.py index b50d6315..4494c71e 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/binding.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/binding.py @@ -10,21 +10,18 @@ import threading import types import typing import warnings +from collections.abc import Callable import cryptography from cryptography.exceptions import InternalError from cryptography.hazmat.bindings._rust import _openssl, openssl from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES +from cryptography.utils import CryptographyDeprecationWarning -def _openssl_assert( - lib, - ok: bool, - errors: typing.Optional[typing.List[openssl.OpenSSLError]] = None, -) -> None: +def _openssl_assert(ok: bool) -> None: if not ok: - if errors is None: - errors = openssl.capture_error_stack() + errors = openssl.capture_error_stack() raise InternalError( "Unknown OpenSSL error. This error is commonly encountered when " @@ -33,25 +30,14 @@ def _openssl_assert( "OpenSSL try disabling it before reporting a bug. Otherwise " "please file an issue at https://github.com/pyca/cryptography/" "issues with information on how to reproduce " - "this. ({!r})".format(errors), + f"this. ({errors!r})", errors, ) -def _legacy_provider_error(loaded: bool) -> None: - if not loaded: - raise RuntimeError( - "OpenSSL 3.0's legacy provider failed to load. This is a fatal " - "error by default, but cryptography supports running without " - "legacy algorithms by setting the environment variable " - "CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error," - " you have likely made a mistake with your OpenSSL configuration." - ) - - def build_conditional_library( lib: typing.Any, - conditional_names: typing.Dict[str, typing.Callable[[], typing.List[str]]], + conditional_names: dict[str, Callable[[], list[str]]], ) -> typing.Any: conditional_lib = types.ModuleType("lib") conditional_lib._original_lib = lib # type: ignore[attr-defined] @@ -72,33 +58,14 @@ class Binding: OpenSSL API wrapper. """ - lib: typing.ClassVar = None + lib: typing.ClassVar[typing.Any] = None ffi = _openssl.ffi _lib_loaded = False _init_lock = threading.Lock() - _legacy_provider: typing.Any = ffi.NULL - _legacy_provider_loaded = False - _default_provider: typing.Any = ffi.NULL def __init__(self) -> None: self._ensure_ffi_initialized() - def _enable_fips(self) -> None: - # This function enables FIPS mode for OpenSSL 3.0.0 on installs that - # have the FIPS provider installed properly. - _openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) - self._base_provider = self.lib.OSSL_PROVIDER_load( - self.ffi.NULL, b"base" - ) - _openssl_assert(self.lib, self._base_provider != self.ffi.NULL) - self.lib._fips_provider = self.lib.OSSL_PROVIDER_load( - self.ffi.NULL, b"fips" - ) - _openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL) - - res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1) - _openssl_assert(self.lib, res == 1) - @classmethod def _ensure_ffi_initialized(cls) -> None: with cls._init_lock: @@ -107,27 +74,6 @@ class Binding: _openssl.lib, CONDITIONAL_NAMES ) cls._lib_loaded = True - # As of OpenSSL 3.0.0 we must register a legacy cipher provider - # to get RC2 (needed for junk asymmetric private key - # serialization), RC4, Blowfish, IDEA, SEED, etc. These things - # are ugly legacy, but we aren't going to get rid of them - # any time soon. - if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - if not os.environ.get("CRYPTOGRAPHY_OPENSSL_NO_LEGACY"): - cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( - cls.ffi.NULL, b"legacy" - ) - cls._legacy_provider_loaded = ( - cls._legacy_provider != cls.ffi.NULL - ) - _legacy_provider_error(cls._legacy_provider_loaded) - - cls._default_provider = cls.lib.OSSL_PROVIDER_load( - cls.ffi.NULL, b"default" - ) - _openssl_assert( - cls.lib, cls._default_provider != cls.ffi.NULL - ) @classmethod def init_static_locks(cls) -> None: @@ -151,13 +97,11 @@ def _verify_package_version(version: str) -> None: "shared object. This can happen if you have multiple copies of " "cryptography installed in your Python path. Please try creating " "a new virtual environment to resolve this issue. " - "Loaded python version: {}, shared object version: {}".format( - version, so_package_version - ) + f"Loaded python version: {version}, " + f"shared object version: {so_package_version}" ) _openssl_assert( - _openssl.lib, _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), ) @@ -177,3 +121,17 @@ if ( UserWarning, stacklevel=2, ) + +if ( + not openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not openssl.CRYPTOGRAPHY_IS_AWSLC + and not openssl.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +): + warnings.warn( + "You are using OpenSSL < 3.0. Support for OpenSSL < 3.0 is deprecated " + "and will be removed in the next release. Please upgrade to OpenSSL " + "3.0 or later.", + CryptographyDeprecationWarning, + stacklevel=2, + ) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__init__.py new file mode 100644 index 00000000..41d73186 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..1aa532b1 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__init__.py new file mode 100644 index 00000000..41d73186 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..b02bface Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc new file mode 100644 index 00000000..a488c7d0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/algorithms.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/algorithms.py new file mode 100644 index 00000000..072a9914 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/algorithms.py @@ -0,0 +1,112 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, + _verify_key_size, +) + + +class ARC4(CipherAlgorithm): + name = "RC4" + key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class TripleDES(BlockCipherAlgorithm): + name = "3DES" + block_size = 64 + key_sizes = frozenset([64, 128, 192]) + + def __init__(self, key: bytes): + if len(key) == 8: + key += key + key + elif len(key) == 16: + key += key[:8] + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# Not actually supported, marker for tests +class _DES: + key_size = 64 + + +class Blowfish(BlockCipherAlgorithm): + name = "Blowfish" + block_size = 64 + key_sizes = frozenset(range(32, 449, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class CAST5(BlockCipherAlgorithm): + name = "CAST5" + block_size = 64 + key_sizes = frozenset(range(40, 129, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SEED(BlockCipherAlgorithm): + name = "SEED" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class IDEA(BlockCipherAlgorithm): + name = "IDEA" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# This class only allows RC2 with a 128-bit key. No support for +# effective key bits or other key sizes is provided. +class RC2(BlockCipherAlgorithm): + name = "RC2" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc index 578537ee..18159247 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc index 18677a16..61a4e173 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc index f8c22331..20ab693e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc index 4ba69f79..ae623f09 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc index fead4108..ae373a32 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc index cd363868..bcc1bc05 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc index f7c1380f..78cd2b40 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc index eebe5882..4203892b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc index 3dde4e79..e9db7759 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc index 0dce985f..7728cbcb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc index 0c2e9eff..ef94a55f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_cipheralgorithm.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_cipheralgorithm.py index 3b880b64..305a9fd3 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_cipheralgorithm.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -5,7 +5,8 @@ from __future__ import annotations import abc -import typing + +from cryptography import utils # This exists to break an import cycle. It is normally accessible from the # ciphers module. @@ -21,7 +22,7 @@ class CipherAlgorithm(metaclass=abc.ABCMeta): @property @abc.abstractmethod - def key_sizes(self) -> typing.FrozenSet[int]: + def key_sizes(self) -> frozenset[int]: """ Valid key sizes for this algorithm in bits """ @@ -35,7 +36,7 @@ class CipherAlgorithm(metaclass=abc.ABCMeta): class BlockCipherAlgorithm(CipherAlgorithm): - key: bytes + key: utils.Buffer @property @abc.abstractmethod @@ -43,3 +44,17 @@ class BlockCipherAlgorithm(CipherAlgorithm): """ The size of a block as an integer in bits (e.g. 64, 128). """ + + +def _verify_key_size( + algorithm: CipherAlgorithm, key: utils.Buffer +) -> utils.Buffer: + # Verify that the key is instance of bytes + utils._check_byteslike("key", key) + + # Verify that the key size matches the expected key size + if len(key) * 8 not in algorithm.key_sizes: + raise ValueError( + f"Invalid key size ({len(key) * 8}) for {algorithm.name}." + ) + return key diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_serialization.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_serialization.py index 34f3fbc8..e998865c 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_serialization.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/_serialization.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography import utils from cryptography.hazmat.primitives.hashes import HashAlgorithm @@ -78,9 +77,9 @@ class KeySerializationEncryptionBuilder: self, format: PrivateFormat, *, - _kdf_rounds: typing.Optional[int] = None, - _hmac_hash: typing.Optional[HashAlgorithm] = None, - _key_cert_algorithm: typing.Optional[PBES] = None, + _kdf_rounds: int | None = None, + _hmac_hash: HashAlgorithm | None = None, + _key_cert_algorithm: PBES | None = None, ) -> None: self._format = format @@ -127,8 +126,7 @@ class KeySerializationEncryptionBuilder: ) -> KeySerializationEncryptionBuilder: if self._format is not PrivateFormat.PKCS12: raise TypeError( - "key_cert_algorithm only supported with " - "PrivateFormat.PKCS12" + "key_cert_algorithm only supported with PrivateFormat.PKCS12" ) if self._key_cert_algorithm is not None: raise ValueError("key_cert_algorithm already set") @@ -158,9 +156,9 @@ class _KeySerializationEncryption(KeySerializationEncryption): format: PrivateFormat, password: bytes, *, - kdf_rounds: typing.Optional[int], - hmac_hash: typing.Optional[HashAlgorithm], - key_cert_algorithm: typing.Optional[PBES], + kdf_rounds: int | None, + hmac_hash: HashAlgorithm | None, + key_cert_algorithm: PBES | None, ): self._format = format self.password = password diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc index f6223a0a..6b850001 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc index beb70dad..907e0ab5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc index cb3a0dce..6691fe07 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc index e6fd7d05..1093e6f6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc index 348468aa..7c60b0a4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc index 7fcf055e..2243c091 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc index b8552df8..1d9cb81e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc index 7007cdf0..63a9577d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc index cce16384..ea12045e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc index 7cc3abac..737a604e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc index 5d7ef7af..0b0a29c2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc index 58f83544..cfc3cc30 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dh.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dh.py index 751bcc40..1822e99d 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dh.py @@ -5,142 +5,16 @@ from __future__ import annotations import abc -import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization - -def generate_parameters( - generator: int, key_size: int, backend: typing.Any = None -) -> DHParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.generate_dh_parameters(generator, key_size) +generate_parameters = rust_openssl.dh.generate_parameters -class DHParameterNumbers: - def __init__(self, p: int, g: int, q: typing.Optional[int] = None) -> None: - if not isinstance(p, int) or not isinstance(g, int): - raise TypeError("p and g must be integers") - if q is not None and not isinstance(q, int): - raise TypeError("q must be integer or None") - - if g < 2: - raise ValueError("DH generator must be 2 or greater") - - if p.bit_length() < rust_openssl.dh.MIN_MODULUS_SIZE: - raise ValueError( - f"p (modulus) must be at least " - f"{rust_openssl.dh.MIN_MODULUS_SIZE}-bit" - ) - - self._p = p - self._g = g - self._q = q - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DHParameterNumbers): - return NotImplemented - - return ( - self._p == other._p and self._g == other._g and self._q == other._q - ) - - def parameters(self, backend: typing.Any = None) -> DHParameters: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dh_parameter_numbers(self) - - @property - def p(self) -> int: - return self._p - - @property - def g(self) -> int: - return self._g - - @property - def q(self) -> typing.Optional[int]: - return self._q - - -class DHPublicNumbers: - def __init__(self, y: int, parameter_numbers: DHParameterNumbers) -> None: - if not isinstance(y, int): - raise TypeError("y must be an integer.") - - if not isinstance(parameter_numbers, DHParameterNumbers): - raise TypeError( - "parameters must be an instance of DHParameterNumbers." - ) - - self._y = y - self._parameter_numbers = parameter_numbers - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DHPublicNumbers): - return NotImplemented - - return ( - self._y == other._y - and self._parameter_numbers == other._parameter_numbers - ) - - def public_key(self, backend: typing.Any = None) -> DHPublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dh_public_numbers(self) - - @property - def y(self) -> int: - return self._y - - @property - def parameter_numbers(self) -> DHParameterNumbers: - return self._parameter_numbers - - -class DHPrivateNumbers: - def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: - if not isinstance(x, int): - raise TypeError("x must be an integer.") - - if not isinstance(public_numbers, DHPublicNumbers): - raise TypeError( - "public_numbers must be an instance of " "DHPublicNumbers." - ) - - self._x = x - self._public_numbers = public_numbers - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DHPrivateNumbers): - return NotImplemented - - return ( - self._x == other._x - and self._public_numbers == other._public_numbers - ) - - def private_key(self, backend: typing.Any = None) -> DHPrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dh_private_numbers(self) - - @property - def public_numbers(self) -> DHPublicNumbers: - return self._public_numbers - - @property - def x(self) -> int: - return self._x +DHPrivateNumbers = rust_openssl.dh.DHPrivateNumbers +DHPublicNumbers = rust_openssl.dh.DHPublicNumbers +DHParameterNumbers = rust_openssl.dh.DHParameterNumbers class DHParameters(metaclass=abc.ABCMeta): @@ -207,6 +81,12 @@ class DHPublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> DHPublicKey: + """ + Returns a copy. + """ + DHPublicKeyWithSerialization = DHPublicKey DHPublicKey.register(rust_openssl.dh.DHPublicKey) @@ -256,6 +136,12 @@ class DHPrivateKey(metaclass=abc.ABCMeta): Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> DHPrivateKey: + """ + Returns a copy. + """ + DHPrivateKeyWithSerialization = DHPrivateKey DHPrivateKey.register(rust_openssl.dh.DHPrivateKey) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py index a8c52de4..21d78ba9 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -10,6 +10,7 @@ import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.utils import Buffer class DSAParameters(metaclass=abc.ABCMeta): @@ -53,8 +54,8 @@ class DSAPrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod def sign( self, - data: bytes, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + data: Buffer, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ Signs the data @@ -77,6 +78,12 @@ class DSAPrivateKey(metaclass=abc.ABCMeta): Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> DSAPrivateKey: + """ + Returns a copy. + """ + DSAPrivateKeyWithSerialization = DSAPrivateKey DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) @@ -115,9 +122,9 @@ class DSAPublicKey(metaclass=abc.ABCMeta): @abc.abstractmethod def verify( self, - signature: bytes, - data: bytes, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + signature: Buffer, + data: Buffer, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ Verifies the signature of the data. @@ -129,171 +136,32 @@ class DSAPublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> DSAPublicKey: + """ + Returns a copy. + """ + DSAPublicKeyWithSerialization = DSAPublicKey DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) - -class DSAParameterNumbers: - def __init__(self, p: int, q: int, g: int): - if ( - not isinstance(p, int) - or not isinstance(q, int) - or not isinstance(g, int) - ): - raise TypeError( - "DSAParameterNumbers p, q, and g arguments must be integers." - ) - - self._p = p - self._q = q - self._g = g - - @property - def p(self) -> int: - return self._p - - @property - def q(self) -> int: - return self._q - - @property - def g(self) -> int: - return self._g - - def parameters(self, backend: typing.Any = None) -> DSAParameters: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dsa_parameter_numbers(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DSAParameterNumbers): - return NotImplemented - - return self.p == other.p and self.q == other.q and self.g == other.g - - def __repr__(self) -> str: - return ( - "".format(self=self) - ) - - -class DSAPublicNumbers: - def __init__(self, y: int, parameter_numbers: DSAParameterNumbers): - if not isinstance(y, int): - raise TypeError("DSAPublicNumbers y argument must be an integer.") - - if not isinstance(parameter_numbers, DSAParameterNumbers): - raise TypeError( - "parameter_numbers must be a DSAParameterNumbers instance." - ) - - self._y = y - self._parameter_numbers = parameter_numbers - - @property - def y(self) -> int: - return self._y - - @property - def parameter_numbers(self) -> DSAParameterNumbers: - return self._parameter_numbers - - def public_key(self, backend: typing.Any = None) -> DSAPublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dsa_public_numbers(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DSAPublicNumbers): - return NotImplemented - - return ( - self.y == other.y - and self.parameter_numbers == other.parameter_numbers - ) - - def __repr__(self) -> str: - return ( - "".format(self=self) - ) - - -class DSAPrivateNumbers: - def __init__(self, x: int, public_numbers: DSAPublicNumbers): - if not isinstance(x, int): - raise TypeError("DSAPrivateNumbers x argument must be an integer.") - - if not isinstance(public_numbers, DSAPublicNumbers): - raise TypeError( - "public_numbers must be a DSAPublicNumbers instance." - ) - self._public_numbers = public_numbers - self._x = x - - @property - def x(self) -> int: - return self._x - - @property - def public_numbers(self) -> DSAPublicNumbers: - return self._public_numbers - - def private_key(self, backend: typing.Any = None) -> DSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_dsa_private_numbers(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, DSAPrivateNumbers): - return NotImplemented - - return ( - self.x == other.x and self.public_numbers == other.public_numbers - ) +DSAPrivateNumbers = rust_openssl.dsa.DSAPrivateNumbers +DSAPublicNumbers = rust_openssl.dsa.DSAPublicNumbers +DSAParameterNumbers = rust_openssl.dsa.DSAParameterNumbers def generate_parameters( key_size: int, backend: typing.Any = None ) -> DSAParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl + if key_size not in (1024, 2048, 3072, 4096): + raise ValueError("Key size must be 1024, 2048, 3072, or 4096 bits.") - return ossl.generate_dsa_parameters(key_size) + return rust_openssl.dsa.generate_parameters(key_size) def generate_private_key( key_size: int, backend: typing.Any = None ) -> DSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.generate_dsa_private_key_and_parameters(key_size) - - -def _check_dsa_parameters(parameters: DSAParameterNumbers) -> None: - if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]: - raise ValueError( - "p must be exactly 1024, 2048, 3072, or 4096 bits long" - ) - if parameters.q.bit_length() not in [160, 224, 256]: - raise ValueError("q must be exactly 160, 224, or 256 bits long") - - if not (1 < parameters.g < parameters.p): - raise ValueError("g, p don't satisfy 1 < g < p.") - - -def _check_dsa_private_numbers(numbers: DSAPrivateNumbers) -> None: - parameters = numbers.public_numbers.parameter_numbers - _check_dsa_parameters(parameters) - if numbers.x <= 0 or numbers.x >= parameters.q: - raise ValueError("x must be > 0 and < q.") - - if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p): - raise ValueError("y must be equal to (g ** x % p).") + parameters = generate_parameters(key_size) + return parameters.generate_private_key() diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ec.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ec.py index ddfaabf4..a13d9827 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ec.py @@ -8,7 +8,9 @@ import abc import typing from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat._oid import ObjectIdentifier +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives.asymmetric import utils as asym_utils @@ -50,13 +52,20 @@ class EllipticCurve(metaclass=abc.ABCMeta): Bit size of a secret scalar for the curve. """ + @property + @abc.abstractmethod + def group_order(self) -> int: + """ + The order of the curve's group. + """ + class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): @property @abc.abstractmethod def algorithm( self, - ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: """ The digest algorithm used with this signature. """ @@ -95,7 +104,7 @@ class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): @abc.abstractmethod def sign( self, - data: bytes, + data: utils.Buffer, signature_algorithm: EllipticCurveSignatureAlgorithm, ) -> bytes: """ @@ -119,8 +128,15 @@ class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePrivateKey: + """ + Returns a copy. + """ + EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey +EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey) class EllipticCurvePublicKey(metaclass=abc.ABCMeta): @@ -157,8 +173,8 @@ class EllipticCurvePublicKey(metaclass=abc.ABCMeta): @abc.abstractmethod def verify( self, - signature: bytes, - data: bytes, + signature: utils.Buffer, + data: utils.Buffer, signature_algorithm: EllipticCurveSignatureAlgorithm, ) -> None: """ @@ -171,18 +187,13 @@ class EllipticCurvePublicKey(metaclass=abc.ABCMeta): ) -> EllipticCurvePublicKey: utils._check_bytes("data", data) - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must be an EllipticCurve instance") - if len(data) == 0: raise ValueError("data must not be an empty byte string") if data[0] not in [0x02, 0x03, 0x04]: raise ValueError("Unsupported elliptic curve point type") - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_elliptic_curve_public_bytes(curve, data) + return rust_openssl.ec.from_public_bytes(curve, data) @abc.abstractmethod def __eq__(self, other: object) -> bool: @@ -190,150 +201,199 @@ class EllipticCurvePublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePublicKey: + """ + Returns a copy. + """ + EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey +EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey) + +EllipticCurvePrivateNumbers = rust_openssl.ec.EllipticCurvePrivateNumbers +EllipticCurvePublicNumbers = rust_openssl.ec.EllipticCurvePublicNumbers class SECT571R1(EllipticCurve): name = "sect571r1" key_size = 570 + group_order = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47 # noqa: E501 class SECT409R1(EllipticCurve): name = "sect409r1" key_size = 409 + group_order = 0x10000000000000000000000000000000000000000000000000001E2AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173 # noqa: E501 class SECT283R1(EllipticCurve): name = "sect283r1" key_size = 283 + group_order = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307 # noqa: E501 class SECT233R1(EllipticCurve): name = "sect233r1" key_size = 233 + group_order = 0x1000000000000000000000000000013E974E72F8A6922031D2603CFE0D7 class SECT163R2(EllipticCurve): name = "sect163r2" key_size = 163 + group_order = 0x40000000000000000000292FE77E70C12A4234C33 class SECT571K1(EllipticCurve): name = "sect571k1" key_size = 571 + group_order = 0x20000000000000000000000000000000000000000000000000000000000000000000000131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001 # noqa: E501 class SECT409K1(EllipticCurve): name = "sect409k1" key_size = 409 + group_order = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF # noqa: E501 class SECT283K1(EllipticCurve): name = "sect283k1" key_size = 283 + group_order = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61 # noqa: E501 class SECT233K1(EllipticCurve): name = "sect233k1" key_size = 233 + group_order = 0x8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF class SECT163K1(EllipticCurve): name = "sect163k1" key_size = 163 + group_order = 0x4000000000000000000020108A2E0CC0D99F8A5EF class SECP521R1(EllipticCurve): name = "secp521r1" key_size = 521 + group_order = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409 # noqa: E501 class SECP384R1(EllipticCurve): name = "secp384r1" key_size = 384 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973 # noqa: E501 class SECP256R1(EllipticCurve): name = "secp256r1" key_size = 256 + group_order = ( + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + ) class SECP256K1(EllipticCurve): name = "secp256k1" key_size = 256 + group_order = ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + ) class SECP224R1(EllipticCurve): name = "secp224r1" key_size = 224 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D class SECP192R1(EllipticCurve): name = "secp192r1" key_size = 192 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831 class BrainpoolP256R1(EllipticCurve): name = "brainpoolP256r1" key_size = 256 + group_order = ( + 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 + ) class BrainpoolP384R1(EllipticCurve): name = "brainpoolP384r1" key_size = 384 + group_order = 0x8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565 # noqa: E501 class BrainpoolP512R1(EllipticCurve): name = "brainpoolP512r1" key_size = 512 + group_order = 0xAADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069 # noqa: E501 -_CURVE_TYPES: typing.Dict[str, typing.Type[EllipticCurve]] = { - "prime192v1": SECP192R1, - "prime256v1": SECP256R1, - "secp192r1": SECP192R1, - "secp224r1": SECP224R1, - "secp256r1": SECP256R1, - "secp384r1": SECP384R1, - "secp521r1": SECP521R1, - "secp256k1": SECP256K1, - "sect163k1": SECT163K1, - "sect233k1": SECT233K1, - "sect283k1": SECT283K1, - "sect409k1": SECT409K1, - "sect571k1": SECT571K1, - "sect163r2": SECT163R2, - "sect233r1": SECT233R1, - "sect283r1": SECT283R1, - "sect409r1": SECT409R1, - "sect571r1": SECT571R1, - "brainpoolP256r1": BrainpoolP256R1, - "brainpoolP384r1": BrainpoolP384R1, - "brainpoolP512r1": BrainpoolP512R1, +_CURVE_TYPES: dict[str, EllipticCurve] = { + "prime192v1": SECP192R1(), + "prime256v1": SECP256R1(), + "secp192r1": SECP192R1(), + "secp224r1": SECP224R1(), + "secp256r1": SECP256R1(), + "secp384r1": SECP384R1(), + "secp521r1": SECP521R1(), + "secp256k1": SECP256K1(), + "sect163k1": SECT163K1(), + "sect233k1": SECT233K1(), + "sect283k1": SECT283K1(), + "sect409k1": SECT409K1(), + "sect571k1": SECT571K1(), + "sect163r2": SECT163R2(), + "sect233r1": SECT233R1(), + "sect283r1": SECT283R1(), + "sect409r1": SECT409R1(), + "sect571r1": SECT571R1(), + "brainpoolP256r1": BrainpoolP256R1(), + "brainpoolP384r1": BrainpoolP384R1(), + "brainpoolP512r1": BrainpoolP512R1(), } class ECDSA(EllipticCurveSignatureAlgorithm): def __init__( self, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + deterministic_signing: bool = False, ): + from cryptography.hazmat.backends.openssl.backend import backend + + if ( + deterministic_signing + and not backend.ecdsa_deterministic_supported() + ): + raise UnsupportedAlgorithm( + "ECDSA with deterministic signature (RFC 6979) is not " + "supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) self._algorithm = algorithm + self._deterministic_signing = deterministic_signing @property def algorithm( self, - ) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]: + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: return self._algorithm + @property + def deterministic_signing( + self, + ) -> bool: + return self._deterministic_signing -def generate_private_key( - curve: EllipticCurve, backend: typing.Any = None -) -> EllipticCurvePrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - return ossl.generate_elliptic_curve_private_key(curve) +generate_private_key = rust_openssl.ec.generate_private_key def derive_private_key( @@ -341,116 +401,13 @@ def derive_private_key( curve: EllipticCurve, backend: typing.Any = None, ) -> EllipticCurvePrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - if not isinstance(private_value, int): raise TypeError("private_value must be an integer type.") if private_value <= 0: raise ValueError("private_value must be a positive integer.") - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must provide the EllipticCurve interface.") - - return ossl.derive_elliptic_curve_private_key(private_value, curve) - - -class EllipticCurvePublicNumbers: - def __init__(self, x: int, y: int, curve: EllipticCurve): - if not isinstance(x, int) or not isinstance(y, int): - raise TypeError("x and y must be integers.") - - if not isinstance(curve, EllipticCurve): - raise TypeError("curve must provide the EllipticCurve interface.") - - self._y = y - self._x = x - self._curve = curve - - def public_key(self, backend: typing.Any = None) -> EllipticCurvePublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_elliptic_curve_public_numbers(self) - - @property - def curve(self) -> EllipticCurve: - return self._curve - - @property - def x(self) -> int: - return self._x - - @property - def y(self) -> int: - return self._y - - def __eq__(self, other: object) -> bool: - if not isinstance(other, EllipticCurvePublicNumbers): - return NotImplemented - - return ( - self.x == other.x - and self.y == other.y - and self.curve.name == other.curve.name - and self.curve.key_size == other.curve.key_size - ) - - def __hash__(self) -> int: - return hash((self.x, self.y, self.curve.name, self.curve.key_size)) - - def __repr__(self) -> str: - return ( - "".format(self) - ) - - -class EllipticCurvePrivateNumbers: - def __init__( - self, private_value: int, public_numbers: EllipticCurvePublicNumbers - ): - if not isinstance(private_value, int): - raise TypeError("private_value must be an integer.") - - if not isinstance(public_numbers, EllipticCurvePublicNumbers): - raise TypeError( - "public_numbers must be an EllipticCurvePublicNumbers " - "instance." - ) - - self._private_value = private_value - self._public_numbers = public_numbers - - def private_key( - self, backend: typing.Any = None - ) -> EllipticCurvePrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_elliptic_curve_private_numbers(self) - - @property - def private_value(self) -> int: - return self._private_value - - @property - def public_numbers(self) -> EllipticCurvePublicNumbers: - return self._public_numbers - - def __eq__(self, other: object) -> bool: - if not isinstance(other, EllipticCurvePrivateNumbers): - return NotImplemented - - return ( - self.private_value == other.private_value - and self.public_numbers == other.public_numbers - ) - - def __hash__(self) -> int: - return hash((self.private_value, self.public_numbers)) + return rust_openssl.ec.derive_private_key(private_value, curve) class ECDH: @@ -480,7 +437,7 @@ _OID_TO_CURVE = { } -def get_curve_for_oid(oid: ObjectIdentifier) -> typing.Type[EllipticCurve]: +def get_curve_for_oid(oid: ObjectIdentifier) -> type[EllipticCurve]: try: return _OID_TO_CURVE[oid] except KeyError: diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed25519.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed25519.py index f26e54d2..e576dc9f 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed25519.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -9,6 +9,7 @@ import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class Ed25519PublicKey(metaclass=abc.ABCMeta): @@ -22,7 +23,7 @@ class Ed25519PublicKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed25519_load_public_bytes(data) + return rust_openssl.ed25519.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -42,7 +43,7 @@ class Ed25519PublicKey(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def verify(self, signature: bytes, data: bytes) -> None: + def verify(self, signature: Buffer, data: Buffer) -> None: """ Verify the signature. """ @@ -53,9 +54,14 @@ class Ed25519PublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> Ed25519PublicKey: + """ + Returns a copy. + """ -if hasattr(rust_openssl, "ed25519"): - Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) + +Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) class Ed25519PrivateKey(metaclass=abc.ABCMeta): @@ -69,10 +75,10 @@ class Ed25519PrivateKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed25519_generate_key() + return rust_openssl.ed25519.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> Ed25519PrivateKey: + def from_private_bytes(cls, data: Buffer) -> Ed25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed25519_supported(): @@ -81,7 +87,7 @@ class Ed25519PrivateKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed25519_load_private_bytes(data) + return rust_openssl.ed25519.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> Ed25519PublicKey: @@ -108,11 +114,16 @@ class Ed25519PrivateKey(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def sign(self, data: bytes) -> bytes: + def sign(self, data: Buffer) -> bytes: """ Signs the data. """ + @abc.abstractmethod + def __copy__(self) -> Ed25519PrivateKey: + """ + Returns a copy. + """ -if hasattr(rust_openssl, "x25519"): - Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) + +Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed448.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed448.py index a9a34b25..89db2098 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed448.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -9,6 +9,7 @@ import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class Ed448PublicKey(metaclass=abc.ABCMeta): @@ -22,7 +23,7 @@ class Ed448PublicKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed448_load_public_bytes(data) + return rust_openssl.ed448.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -42,7 +43,7 @@ class Ed448PublicKey(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def verify(self, signature: bytes, data: bytes) -> None: + def verify(self, signature: Buffer, data: Buffer) -> None: """ Verify the signature. """ @@ -53,6 +54,12 @@ class Ed448PublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> Ed448PublicKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "ed448"): Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey) @@ -68,10 +75,11 @@ class Ed448PrivateKey(metaclass=abc.ABCMeta): "ed448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed448_generate_key() + + return rust_openssl.ed448.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> Ed448PrivateKey: + def from_private_bytes(cls, data: Buffer) -> Ed448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.ed448_supported(): @@ -80,7 +88,7 @@ class Ed448PrivateKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, ) - return backend.ed448_load_private_bytes(data) + return rust_openssl.ed448.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> Ed448PublicKey: @@ -89,7 +97,7 @@ class Ed448PrivateKey(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def sign(self, data: bytes) -> bytes: + def sign(self, data: Buffer) -> bytes: """ Signs the data. """ @@ -112,6 +120,12 @@ class Ed448PrivateKey(metaclass=abc.ABCMeta): Equivalent to private_bytes(Raw, Raw, NoEncryption()). """ + @abc.abstractmethod + def __copy__(self) -> Ed448PrivateKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "x448"): Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/padding.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/padding.py index 7198808e..5121a288 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/padding.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives._asymmetric import ( @@ -35,12 +34,12 @@ class PSS(AsymmetricPadding): AUTO = _Auto() DIGEST_LENGTH = _DigestLength() name = "EMSA-PSS" - _salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength] + _salt_length: int | _MaxLength | _Auto | _DigestLength def __init__( self, mgf: MGF, - salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength], + salt_length: int | _MaxLength | _Auto | _DigestLength, ) -> None: self._mgf = mgf @@ -57,6 +56,10 @@ class PSS(AsymmetricPadding): self._salt_length = salt_length + @property + def mgf(self) -> MGF: + return self._mgf + class OAEP(AsymmetricPadding): name = "EME-OAEP" @@ -65,7 +68,7 @@ class OAEP(AsymmetricPadding): self, mgf: MGF, algorithm: hashes.HashAlgorithm, - label: typing.Optional[bytes], + label: bytes | None, ): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") @@ -74,14 +77,20 @@ class OAEP(AsymmetricPadding): self._algorithm = algorithm self._label = label + @property + def algorithm(self) -> hashes.HashAlgorithm: + return self._algorithm + + @property + def mgf(self) -> MGF: + return self._mgf + class MGF(metaclass=abc.ABCMeta): _algorithm: hashes.HashAlgorithm class MGF1(MGF): - MAX_LENGTH = _MaxLength() - def __init__(self, algorithm: hashes.HashAlgorithm): if not isinstance(algorithm, hashes.HashAlgorithm): raise TypeError("Expected instance of hashes.HashAlgorithm.") @@ -90,7 +99,7 @@ class MGF1(MGF): def calculate_max_pss_salt_length( - key: typing.Union[rsa.RSAPrivateKey, rsa.RSAPublicKey], + key: rsa.RSAPrivateKey | rsa.RSAPublicKey, hash_algorithm: hashes.HashAlgorithm, ) -> int: if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.py index b740f01f..f94812e1 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -5,9 +5,11 @@ from __future__ import annotations import abc +import random import typing from math import gcd +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization, hashes from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding from cryptography.hazmat.primitives.asymmetric import utils as asym_utils @@ -38,7 +40,7 @@ class RSAPrivateKey(metaclass=abc.ABCMeta): self, data: bytes, padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> bytes: """ Signs the data. @@ -61,8 +63,15 @@ class RSAPrivateKey(metaclass=abc.ABCMeta): Returns the key serialized as bytes. """ + @abc.abstractmethod + def __copy__(self) -> RSAPrivateKey: + """ + Returns a copy. + """ + RSAPrivateKeyWithSerialization = RSAPrivateKey +RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey) class RSAPublicKey(metaclass=abc.ABCMeta): @@ -101,7 +110,7 @@ class RSAPublicKey(metaclass=abc.ABCMeta): signature: bytes, data: bytes, padding: AsymmetricPadding, - algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm], + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, ) -> None: """ Verifies the signature of the data. @@ -112,7 +121,7 @@ class RSAPublicKey(metaclass=abc.ABCMeta): self, signature: bytes, padding: AsymmetricPadding, - algorithm: typing.Optional[hashes.HashAlgorithm], + algorithm: hashes.HashAlgorithm | None, ) -> bytes: """ Recovers the original data from the signature. @@ -124,8 +133,18 @@ class RSAPublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> RSAPublicKey: + """ + Returns a copy. + """ + RSAPublicKeyWithSerialization = RSAPublicKey +RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey) + +RSAPrivateNumbers = rust_openssl.rsa.RSAPrivateNumbers +RSAPublicNumbers = rust_openssl.rsa.RSAPublicNumbers def generate_private_key( @@ -133,10 +152,8 @@ def generate_private_key( key_size: int, backend: typing.Any = None, ) -> RSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - _verify_rsa_parameters(public_exponent, key_size) - return ossl.generate_rsa_private_key(public_exponent, key_size) + return rust_openssl.rsa.generate_private_key(public_exponent, key_size) def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: @@ -146,66 +163,8 @@ def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: "65537. Almost everyone should choose 65537 here!" ) - if key_size < 512: - raise ValueError("key_size must be at least 512-bits.") - - -def _check_private_key_components( - p: int, - q: int, - private_exponent: int, - dmp1: int, - dmq1: int, - iqmp: int, - public_exponent: int, - modulus: int, -) -> None: - if modulus < 3: - raise ValueError("modulus must be >= 3.") - - if p >= modulus: - raise ValueError("p must be < modulus.") - - if q >= modulus: - raise ValueError("q must be < modulus.") - - if dmp1 >= modulus: - raise ValueError("dmp1 must be < modulus.") - - if dmq1 >= modulus: - raise ValueError("dmq1 must be < modulus.") - - if iqmp >= modulus: - raise ValueError("iqmp must be < modulus.") - - if private_exponent >= modulus: - raise ValueError("private_exponent must be < modulus.") - - if public_exponent < 3 or public_exponent >= modulus: - raise ValueError("public_exponent must be >= 3 and < modulus.") - - if public_exponent & 1 == 0: - raise ValueError("public_exponent must be odd.") - - if dmp1 & 1 == 0: - raise ValueError("dmp1 must be odd.") - - if dmq1 & 1 == 0: - raise ValueError("dmq1 must be odd.") - - if p * q != modulus: - raise ValueError("p*q must equal modulus.") - - -def _check_public_key_components(e: int, n: int) -> None: - if n < 3: - raise ValueError("n must be >= 3.") - - if e < 3 or e >= n: - raise ValueError("e must be >= 3 and < n.") - - if e & 1 == 0: - raise ValueError("e must be odd.") + if key_size < 1024: + raise ValueError("key_size must be at least 1024-bits.") def _modinv(e: int, m: int) -> int: @@ -225,6 +184,8 @@ def rsa_crt_iqmp(p: int, q: int) -> int: """ Compute the CRT (q ** -1) % p value from RSA primes p and q. """ + if p <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") return _modinv(q, p) @@ -233,6 +194,8 @@ def rsa_crt_dmp1(private_exponent: int, p: int) -> int: Compute the CRT private_exponent % (p - 1) value from the RSA private_exponent (d) and p. """ + if private_exponent <= 1 or p <= 1: + raise ValueError("Values can't be <= 1") return private_exponent % (p - 1) @@ -241,22 +204,49 @@ def rsa_crt_dmq1(private_exponent: int, q: int) -> int: Compute the CRT private_exponent % (q - 1) value from the RSA private_exponent (d) and q. """ + if private_exponent <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") return private_exponent % (q - 1) +def rsa_recover_private_exponent(e: int, p: int, q: int) -> int: + """ + Compute the RSA private_exponent (d) given the public exponent (e) + and the RSA primes p and q. + + This uses the Carmichael totient function to generate the + smallest possible working value of the private exponent. + """ + # This lambda_n is the Carmichael totient function. + # The original RSA paper uses the Euler totient function + # here: phi_n = (p - 1) * (q - 1) + # Either version of the private exponent will work, but the + # one generated by the older formulation may be larger + # than necessary. (lambda_n always divides phi_n) + # + # TODO: Replace with lcm(p - 1, q - 1) once the minimum + # supported Python version is >= 3.9. + if e <= 1 or p <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + lambda_n = (p - 1) * (q - 1) // gcd(p - 1, q - 1) + return _modinv(e, lambda_n) + + # Controls the number of iterations rsa_recover_prime_factors will perform -# to obtain the prime factors. Each iteration increments by 2 so the actual -# maximum attempts is half this number. -_MAX_RECOVERY_ATTEMPTS = 1000 +# to obtain the prime factors. +_MAX_RECOVERY_ATTEMPTS = 500 -def rsa_recover_prime_factors( - n: int, e: int, d: int -) -> typing.Tuple[int, int]: +def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: """ Compute factors p and q from the private exponent d. We assume that n has no more than two factors. This function is adapted from code in PyCrypto. """ + # reject invalid values early + if d <= 1 or e <= 1: + raise ValueError("d, e can't be <= 1") + if 17 != pow(17, e * d, n): + raise ValueError("n, d, e don't match") # See 8.2.2(i) in Handbook of Applied Cryptography. ktot = d * e - 1 # The quantity d*e-1 is a multiple of phi(n), even, @@ -270,8 +260,10 @@ def rsa_recover_prime_factors( # See "Digitalized Signatures and Public Key Functions as Intractable # as Factorization", M. Rabin, 1979 spotted = False - a = 2 - while not spotted and a < _MAX_RECOVERY_ATTEMPTS: + tries = 0 + while not spotted and tries < _MAX_RECOVERY_ATTEMPTS: + a = random.randint(2, n - 1) + tries += 1 k = t # Cycle through all values a^{t*2^i}=a^k while k < ktot: @@ -284,8 +276,6 @@ def rsa_recover_prime_factors( spotted = True break k *= 2 - # This value was not any good... let's try another! - a += 2 if not spotted: raise ValueError("Unable to compute factors p and q from exponent d.") # Found ! @@ -293,147 +283,3 @@ def rsa_recover_prime_factors( assert r == 0 p, q = sorted((p, q), reverse=True) return (p, q) - - -class RSAPrivateNumbers: - def __init__( - self, - p: int, - q: int, - d: int, - dmp1: int, - dmq1: int, - iqmp: int, - public_numbers: RSAPublicNumbers, - ): - if ( - not isinstance(p, int) - or not isinstance(q, int) - or not isinstance(d, int) - or not isinstance(dmp1, int) - or not isinstance(dmq1, int) - or not isinstance(iqmp, int) - ): - raise TypeError( - "RSAPrivateNumbers p, q, d, dmp1, dmq1, iqmp arguments must" - " all be an integers." - ) - - if not isinstance(public_numbers, RSAPublicNumbers): - raise TypeError( - "RSAPrivateNumbers public_numbers must be an RSAPublicNumbers" - " instance." - ) - - self._p = p - self._q = q - self._d = d - self._dmp1 = dmp1 - self._dmq1 = dmq1 - self._iqmp = iqmp - self._public_numbers = public_numbers - - @property - def p(self) -> int: - return self._p - - @property - def q(self) -> int: - return self._q - - @property - def d(self) -> int: - return self._d - - @property - def dmp1(self) -> int: - return self._dmp1 - - @property - def dmq1(self) -> int: - return self._dmq1 - - @property - def iqmp(self) -> int: - return self._iqmp - - @property - def public_numbers(self) -> RSAPublicNumbers: - return self._public_numbers - - def private_key( - self, - backend: typing.Any = None, - *, - unsafe_skip_rsa_key_validation: bool = False, - ) -> RSAPrivateKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_rsa_private_numbers( - self, unsafe_skip_rsa_key_validation - ) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, RSAPrivateNumbers): - return NotImplemented - - return ( - self.p == other.p - and self.q == other.q - and self.d == other.d - and self.dmp1 == other.dmp1 - and self.dmq1 == other.dmq1 - and self.iqmp == other.iqmp - and self.public_numbers == other.public_numbers - ) - - def __hash__(self) -> int: - return hash( - ( - self.p, - self.q, - self.d, - self.dmp1, - self.dmq1, - self.iqmp, - self.public_numbers, - ) - ) - - -class RSAPublicNumbers: - def __init__(self, e: int, n: int): - if not isinstance(e, int) or not isinstance(n, int): - raise TypeError("RSAPublicNumbers arguments must be integers.") - - self._e = e - self._n = n - - @property - def e(self) -> int: - return self._e - - @property - def n(self) -> int: - return self._n - - def public_key(self, backend: typing.Any = None) -> RSAPublicKey: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - return ossl.load_rsa_public_numbers(self) - - def __repr__(self) -> str: - return "".format(self) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, RSAPublicNumbers): - return NotImplemented - - return self.e == other.e and self.n == other.n - - def __hash__(self) -> int: - return hash((self.e, self.n)) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x25519.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x25519.py index 699054c9..a4993766 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x25519.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -9,6 +9,7 @@ import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class X25519PublicKey(metaclass=abc.ABCMeta): @@ -22,7 +23,7 @@ class X25519PublicKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x25519_load_public_bytes(data) + return rust_openssl.x25519.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -47,10 +48,14 @@ class X25519PublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> X25519PublicKey: + """ + Returns a copy. + """ -# For LibreSSL -if hasattr(rust_openssl, "x25519"): - X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) + +X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) class X25519PrivateKey(metaclass=abc.ABCMeta): @@ -63,10 +68,10 @@ class X25519PrivateKey(metaclass=abc.ABCMeta): "X25519 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x25519_generate_key() + return rust_openssl.x25519.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> X25519PrivateKey: + def from_private_bytes(cls, data: Buffer) -> X25519PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x25519_supported(): @@ -75,12 +80,12 @@ class X25519PrivateKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x25519_load_private_bytes(data) + return rust_openssl.x25519.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> X25519PublicKey: """ - Returns the public key assosciated with this private key + Returns the public key associated with this private key """ @abc.abstractmethod @@ -107,7 +112,11 @@ class X25519PrivateKey(metaclass=abc.ABCMeta): Performs a key exchange operation using the provided peer's public key. """ + @abc.abstractmethod + def __copy__(self) -> X25519PrivateKey: + """ + Returns a copy. + """ -# For LibreSSL -if hasattr(rust_openssl, "x25519"): - X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) + +X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x448.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x448.py index abf78485..c6fd71ba 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/x448.py @@ -9,6 +9,7 @@ import abc from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer class X448PublicKey(metaclass=abc.ABCMeta): @@ -22,7 +23,7 @@ class X448PublicKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x448_load_public_bytes(data) + return rust_openssl.x448.from_public_bytes(data) @abc.abstractmethod def public_bytes( @@ -47,6 +48,12 @@ class X448PublicKey(metaclass=abc.ABCMeta): Checks equality. """ + @abc.abstractmethod + def __copy__(self) -> X448PublicKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "x448"): X448PublicKey.register(rust_openssl.x448.X448PublicKey) @@ -62,10 +69,11 @@ class X448PrivateKey(metaclass=abc.ABCMeta): "X448 is not supported by this version of OpenSSL.", _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x448_generate_key() + + return rust_openssl.x448.generate_key() @classmethod - def from_private_bytes(cls, data: bytes) -> X448PrivateKey: + def from_private_bytes(cls, data: Buffer) -> X448PrivateKey: from cryptography.hazmat.backends.openssl.backend import backend if not backend.x448_supported(): @@ -74,7 +82,7 @@ class X448PrivateKey(metaclass=abc.ABCMeta): _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) - return backend.x448_load_private_bytes(data) + return rust_openssl.x448.from_private_bytes(data) @abc.abstractmethod def public_key(self) -> X448PublicKey: @@ -106,6 +114,12 @@ class X448PrivateKey(metaclass=abc.ABCMeta): Performs a key exchange operation using the provided peer's public key. """ + @abc.abstractmethod + def __copy__(self) -> X448PrivateKey: + """ + Returns a copy. + """ + if hasattr(rust_openssl, "x448"): X448PrivateKey.register(rust_openssl.x448.X448PrivateKey) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__init__.py index cc88fbf2..10c15d0f 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__init__.py @@ -17,11 +17,11 @@ from cryptography.hazmat.primitives.ciphers.base import ( ) __all__ = [ - "Cipher", - "CipherAlgorithm", - "BlockCipherAlgorithm", - "CipherContext", "AEADCipherContext", "AEADDecryptionContext", "AEADEncryptionContext", + "BlockCipherAlgorithm", + "Cipher", + "CipherAlgorithm", + "CipherContext", ] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc index de7e90f0..ab8a0585 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc index cc1fd932..692f1fe1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc index fc95ac48..240ec045 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc index 0a8823a4..4c4188d9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc index c8f0020a..e28b8f08 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/aead.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/aead.py index 957b2d22..c8a582d7 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/aead.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/aead.py @@ -4,375 +4,20 @@ from __future__ import annotations -import os -import typing - -from cryptography import exceptions, utils -from cryptography.hazmat.backends.openssl import aead -from cryptography.hazmat.backends.openssl.backend import backend -from cryptography.hazmat.bindings._rust import FixedPool - - -class ChaCha20Poly1305: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "ChaCha20Poly1305 is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - utils._check_byteslike("key", key) - - if len(key) != 32: - raise ValueError("ChaCha20Poly1305 key must be 32 bytes.") - - self._key = key - self._pool = FixedPool(self._create_fn) - - @classmethod - def generate_key(cls) -> bytes: - return os.urandom(32) - - def _create_fn(self): - return aead._aead_create_ctx(backend, self, self._key) - - def encrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - self._check_params(nonce, data, associated_data) - with self._pool.acquire() as ctx: - return aead._encrypt( - backend, self, nonce, data, [associated_data], 16, ctx - ) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - with self._pool.acquire() as ctx: - return aead._decrypt( - backend, self, nonce, data, [associated_data], 16, ctx - ) - - def _check_params( - self, - nonce: bytes, - data: bytes, - associated_data: bytes, - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if len(nonce) != 12: - raise ValueError("Nonce must be 12 bytes") - - -class AESCCM: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes, tag_length: int = 16): - utils._check_byteslike("key", key) - if len(key) not in (16, 24, 32): - raise ValueError("AESCCM key must be 128, 192, or 256 bits.") - - self._key = key - if not isinstance(tag_length, int): - raise TypeError("tag_length must be an integer") - - if tag_length not in (4, 6, 8, 10, 12, 14, 16): - raise ValueError("Invalid tag_length") - - self._tag_length = tag_length - - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "AESCCM is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - - @classmethod - def generate_key(cls, bit_length: int) -> bytes: - if not isinstance(bit_length, int): - raise TypeError("bit_length must be an integer") - - if bit_length not in (128, 192, 256): - raise ValueError("bit_length must be 128, 192, or 256") - - return os.urandom(bit_length // 8) - - def encrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - self._check_params(nonce, data, associated_data) - self._validate_lengths(nonce, len(data)) - return aead._encrypt( - backend, self, nonce, data, [associated_data], self._tag_length - ) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - return aead._decrypt( - backend, self, nonce, data, [associated_data], self._tag_length - ) - - def _validate_lengths(self, nonce: bytes, data_len: int) -> None: - # For information about computing this, see - # https://tools.ietf.org/html/rfc3610#section-2.1 - l_val = 15 - len(nonce) - if 2 ** (8 * l_val) < data_len: - raise ValueError("Data too long for nonce") - - def _check_params( - self, nonce: bytes, data: bytes, associated_data: bytes - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if not 7 <= len(nonce) <= 13: - raise ValueError("Nonce must be between 7 and 13 bytes") - - -class AESGCM: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - utils._check_byteslike("key", key) - if len(key) not in (16, 24, 32): - raise ValueError("AESGCM key must be 128, 192, or 256 bits.") - - self._key = key - - @classmethod - def generate_key(cls, bit_length: int) -> bytes: - if not isinstance(bit_length, int): - raise TypeError("bit_length must be an integer") - - if bit_length not in (128, 192, 256): - raise ValueError("bit_length must be 128, 192, or 256") - - return os.urandom(bit_length // 8) - - def encrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - self._check_params(nonce, data, associated_data) - return aead._encrypt(backend, self, nonce, data, [associated_data], 16) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, [associated_data], 16) - - def _check_params( - self, - nonce: bytes, - data: bytes, - associated_data: bytes, - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if len(nonce) < 8 or len(nonce) > 128: - raise ValueError("Nonce must be between 8 and 128 bytes") - - -class AESOCB3: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - utils._check_byteslike("key", key) - if len(key) not in (16, 24, 32): - raise ValueError("AESOCB3 key must be 128, 192, or 256 bits.") - - self._key = key - - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "OCB3 is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - - @classmethod - def generate_key(cls, bit_length: int) -> bytes: - if not isinstance(bit_length, int): - raise TypeError("bit_length must be an integer") - - if bit_length not in (128, 192, 256): - raise ValueError("bit_length must be 128, 192, or 256") - - return os.urandom(bit_length // 8) - - def encrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - self._check_params(nonce, data, associated_data) - return aead._encrypt(backend, self, nonce, data, [associated_data], 16) - - def decrypt( - self, - nonce: bytes, - data: bytes, - associated_data: typing.Optional[bytes], - ) -> bytes: - if associated_data is None: - associated_data = b"" - - self._check_params(nonce, data, associated_data) - return aead._decrypt(backend, self, nonce, data, [associated_data], 16) - - def _check_params( - self, - nonce: bytes, - data: bytes, - associated_data: bytes, - ) -> None: - utils._check_byteslike("nonce", nonce) - utils._check_byteslike("data", data) - utils._check_byteslike("associated_data", associated_data) - if len(nonce) < 12 or len(nonce) > 15: - raise ValueError("Nonce must be between 12 and 15 bytes") - - -class AESSIV: - _MAX_SIZE = 2**31 - 1 - - def __init__(self, key: bytes): - utils._check_byteslike("key", key) - if len(key) not in (32, 48, 64): - raise ValueError("AESSIV key must be 256, 384, or 512 bits.") - - self._key = key - - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "AES-SIV is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER, - ) - - @classmethod - def generate_key(cls, bit_length: int) -> bytes: - if not isinstance(bit_length, int): - raise TypeError("bit_length must be an integer") - - if bit_length not in (256, 384, 512): - raise ValueError("bit_length must be 256, 384, or 512") - - return os.urandom(bit_length // 8) - - def encrypt( - self, - data: bytes, - associated_data: typing.Optional[typing.List[bytes]], - ) -> bytes: - if associated_data is None: - associated_data = [] - - self._check_params(data, associated_data) - - if len(data) > self._MAX_SIZE or any( - len(ad) > self._MAX_SIZE for ad in associated_data - ): - # This is OverflowError to match what cffi would raise - raise OverflowError( - "Data or associated data too long. Max 2**31 - 1 bytes" - ) - - return aead._encrypt(backend, self, b"", data, associated_data, 16) - - def decrypt( - self, - data: bytes, - associated_data: typing.Optional[typing.List[bytes]], - ) -> bytes: - if associated_data is None: - associated_data = [] - - self._check_params(data, associated_data) - - return aead._decrypt(backend, self, b"", data, associated_data, 16) - - def _check_params( - self, - data: bytes, - associated_data: typing.List[bytes], - ) -> None: - utils._check_byteslike("data", data) - if len(data) == 0: - raise ValueError("data must not be zero length") - - if not isinstance(associated_data, list): - raise TypeError( - "associated_data must be a list of bytes-like objects or None" - ) - for x in associated_data: - utils._check_byteslike("associated_data elements", x) +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = [ + "AESCCM", + "AESGCM", + "AESGCMSIV", + "AESOCB3", + "AESSIV", + "ChaCha20Poly1305", +] + +AESGCM = rust_openssl.aead.AESGCM +ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305 +AESCCM = rust_openssl.aead.AESCCM +AESSIV = rust_openssl.aead.AESSIV +AESOCB3 = rust_openssl.aead.AESOCB3 +AESGCMSIV = rust_openssl.aead.AESGCMSIV diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py index 4bfc5d84..1e402c7e 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -5,33 +5,38 @@ from __future__ import annotations from cryptography import utils +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + ARC4 as ARC4, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + CAST5 as CAST5, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + IDEA as IDEA, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + SEED as SEED, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + Blowfish as Blowfish, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + TripleDES as TripleDES, +) +from cryptography.hazmat.primitives._cipheralgorithm import _verify_key_size from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, CipherAlgorithm, ) -def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes: - # Verify that the key is instance of bytes - utils._check_byteslike("key", key) - - # Verify that the key size matches the expected key size - if len(key) * 8 not in algorithm.key_sizes: - raise ValueError( - "Invalid key size ({}) for {}.".format( - len(key) * 8, algorithm.name - ) - ) - return key - - class AES(BlockCipherAlgorithm): name = "AES" block_size = 128 # 512 added to support AES-256-XTS, which uses 512-bit keys key_sizes = frozenset([128, 192, 256, 512]) - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @property @@ -45,7 +50,7 @@ class AES128(BlockCipherAlgorithm): key_sizes = frozenset([128]) key_size = 128 - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @@ -55,7 +60,7 @@ class AES256(BlockCipherAlgorithm): key_sizes = frozenset([256]) key_size = 256 - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @@ -64,7 +69,7 @@ class Camellia(BlockCipherAlgorithm): block_size = 128 key_sizes = frozenset([128, 192, 256]) - def __init__(self, key: bytes): + def __init__(self, key: utils.Buffer): self.key = _verify_key_size(self, key) @property @@ -72,124 +77,27 @@ class Camellia(BlockCipherAlgorithm): return len(self.key) * 8 -class TripleDES(BlockCipherAlgorithm): - name = "3DES" - block_size = 64 - key_sizes = frozenset([64, 128, 192]) - - def __init__(self, key: bytes): - if len(key) == 8: - key += key + key - elif len(key) == 16: - key += key[:8] - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -class Blowfish(BlockCipherAlgorithm): - name = "Blowfish" - block_size = 64 - key_sizes = frozenset(range(32, 449, 8)) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_BlowfishInternal = Blowfish utils.deprecated( - Blowfish, + ARC4, __name__, - "Blowfish has been deprecated", - utils.DeprecatedIn37, - name="Blowfish", + "ARC4 has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="ARC4", ) -class CAST5(BlockCipherAlgorithm): - name = "CAST5" - block_size = 64 - key_sizes = frozenset(range(40, 129, 8)) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_CAST5Internal = CAST5 utils.deprecated( - CAST5, + TripleDES, __name__, - "CAST5 has been deprecated", - utils.DeprecatedIn37, - name="CAST5", -) - - -class ARC4(CipherAlgorithm): - name = "RC4" - key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -class IDEA(BlockCipherAlgorithm): - name = "IDEA" - block_size = 64 - key_sizes = frozenset([128]) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_IDEAInternal = IDEA -utils.deprecated( - IDEA, - __name__, - "IDEA has been deprecated", - utils.DeprecatedIn37, - name="IDEA", -) - - -class SEED(BlockCipherAlgorithm): - name = "SEED" - block_size = 128 - key_sizes = frozenset([128]) - - def __init__(self, key: bytes): - self.key = _verify_key_size(self, key) - - @property - def key_size(self) -> int: - return len(self.key) * 8 - - -_SEEDInternal = SEED -utils.deprecated( - SEED, - __name__, - "SEED has been deprecated", - utils.DeprecatedIn37, - name="SEED", + "TripleDES has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="TripleDES", ) @@ -197,7 +105,7 @@ class ChaCha20(CipherAlgorithm): name = "ChaCha20" key_sizes = frozenset([256]) - def __init__(self, key: bytes, nonce: bytes): + def __init__(self, key: utils.Buffer, nonce: utils.Buffer): self.key = _verify_key_size(self, key) utils._check_byteslike("nonce", nonce) @@ -207,7 +115,7 @@ class ChaCha20(CipherAlgorithm): self._nonce = nonce @property - def nonce(self) -> bytes: + def nonce(self) -> utils.Buffer: return self._nonce @property diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/base.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/base.py index 38a2ebbe..24fceea2 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/base.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/base.py @@ -7,30 +7,22 @@ from __future__ import annotations import abc import typing -from cryptography.exceptions import ( - AlreadyFinalized, - AlreadyUpdated, - NotYetFinalized, -) +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm from cryptography.hazmat.primitives.ciphers import modes - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.ciphers import ( - _CipherContext as _BackendCipherContext, - ) +from cryptography.utils import Buffer class CipherContext(metaclass=abc.ABCMeta): @abc.abstractmethod - def update(self, data: bytes) -> bytes: + def update(self, data: Buffer) -> bytes: """ Processes the provided bytes through the cipher and returns the results as bytes. """ @abc.abstractmethod - def update_into(self, data: bytes, buf: bytes) -> int: + def update_into(self, data: Buffer, buf: Buffer) -> int: """ Processes the provided bytes and writes the resulting data into the provided buffer. Returns the number of bytes written. @@ -42,10 +34,18 @@ class CipherContext(metaclass=abc.ABCMeta): Returns the results of processing the final block as bytes. """ + @abc.abstractmethod + def reset_nonce(self, nonce: bytes) -> None: + """ + Resets the nonce for the cipher context to the provided value. + Raises an exception if it does not support reset or if the + provided nonce does not have a valid length. + """ + class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): @abc.abstractmethod - def authenticate_additional_data(self, data: bytes) -> None: + def authenticate_additional_data(self, data: Buffer) -> None: """ Authenticates the provided bytes. """ @@ -97,14 +97,12 @@ class Cipher(typing.Generic[Mode]): @typing.overload def encryptor( self: Cipher[modes.ModeWithAuthenticationTag], - ) -> AEADEncryptionContext: - ... + ) -> AEADEncryptionContext: ... @typing.overload def encryptor( self: _CIPHER_TYPE, - ) -> CipherContext: - ... + ) -> CipherContext: ... def encryptor(self): if isinstance(self.mode, modes.ModeWithAuthenticationTag): @@ -112,158 +110,37 @@ class Cipher(typing.Generic[Mode]): raise ValueError( "Authentication tag must be None when encrypting." ) - from cryptography.hazmat.backends.openssl.backend import backend - ctx = backend.create_symmetric_encryption_ctx( + return rust_openssl.ciphers.create_encryption_ctx( self.algorithm, self.mode ) - return self._wrap_ctx(ctx, encrypt=True) @typing.overload def decryptor( self: Cipher[modes.ModeWithAuthenticationTag], - ) -> AEADDecryptionContext: - ... + ) -> AEADDecryptionContext: ... @typing.overload def decryptor( self: _CIPHER_TYPE, - ) -> CipherContext: - ... + ) -> CipherContext: ... def decryptor(self): - from cryptography.hazmat.backends.openssl.backend import backend - - ctx = backend.create_symmetric_decryption_ctx( + return rust_openssl.ciphers.create_decryption_ctx( self.algorithm, self.mode ) - return self._wrap_ctx(ctx, encrypt=False) - - def _wrap_ctx( - self, ctx: _BackendCipherContext, encrypt: bool - ) -> typing.Union[ - AEADEncryptionContext, AEADDecryptionContext, CipherContext - ]: - if isinstance(self.mode, modes.ModeWithAuthenticationTag): - if encrypt: - return _AEADEncryptionContext(ctx) - else: - return _AEADDecryptionContext(ctx) - else: - return _CipherContext(ctx) _CIPHER_TYPE = Cipher[ typing.Union[ modes.ModeWithNonce, modes.ModeWithTweak, - None, modes.ECB, modes.ModeWithInitializationVector, + None, ] ] - -class _CipherContext(CipherContext): - _ctx: typing.Optional[_BackendCipherContext] - - def __init__(self, ctx: _BackendCipherContext) -> None: - self._ctx = ctx - - def update(self, data: bytes) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return self._ctx.update(data) - - def update_into(self, data: bytes, buf: bytes) -> int: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return self._ctx.update_into(data, buf) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - data = self._ctx.finalize() - self._ctx = None - return data - - -class _AEADCipherContext(AEADCipherContext): - _ctx: typing.Optional[_BackendCipherContext] - _tag: typing.Optional[bytes] - - def __init__(self, ctx: _BackendCipherContext) -> None: - self._ctx = ctx - self._bytes_processed = 0 - self._aad_bytes_processed = 0 - self._tag = None - self._updated = False - - def _check_limit(self, data_size: int) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - self._updated = True - self._bytes_processed += data_size - if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES: - raise ValueError( - "{} has a maximum encrypted byte limit of {}".format( - self._ctx._mode.name, self._ctx._mode._MAX_ENCRYPTED_BYTES - ) - ) - - def update(self, data: bytes) -> bytes: - self._check_limit(len(data)) - # mypy needs this assert even though _check_limit already checked - assert self._ctx is not None - return self._ctx.update(data) - - def update_into(self, data: bytes, buf: bytes) -> int: - self._check_limit(len(data)) - # mypy needs this assert even though _check_limit already checked - assert self._ctx is not None - return self._ctx.update_into(data, buf) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - data = self._ctx.finalize() - self._tag = self._ctx.tag - self._ctx = None - return data - - def authenticate_additional_data(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - if self._updated: - raise AlreadyUpdated("Update has been called on this context.") - - self._aad_bytes_processed += len(data) - if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES: - raise ValueError( - "{} has a maximum AAD byte limit of {}".format( - self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES - ) - ) - - self._ctx.authenticate_additional_data(data) - - -class _AEADDecryptionContext(_AEADCipherContext, AEADDecryptionContext): - def finalize_with_tag(self, tag: bytes) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - data = self._ctx.finalize_with_tag(tag) - self._tag = self._ctx.tag - self._ctx = None - return data - - -class _AEADEncryptionContext(_AEADCipherContext, AEADEncryptionContext): - @property - def tag(self) -> bytes: - if self._ctx is not None: - raise NotYetFinalized( - "You must finalize encryption before " "getting the tag." - ) - assert self._tag is not None - return self._tag +CipherContext.register(rust_openssl.ciphers.CipherContext) +AEADEncryptionContext.register(rust_openssl.ciphers.AEADEncryptionContext) +AEADDecryptionContext.register(rust_openssl.ciphers.AEADDecryptionContext) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/modes.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/modes.py index d8ea1888..36c555c6 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/modes.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/modes.py @@ -5,7 +5,6 @@ from __future__ import annotations import abc -import typing from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons @@ -35,7 +34,7 @@ class Mode(metaclass=abc.ABCMeta): class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: """ The value of the initialization vector for this mode as bytes. """ @@ -44,7 +43,7 @@ class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): class ModeWithTweak(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def tweak(self) -> bytes: + def tweak(self) -> utils.Buffer: """ The value of the tweak for this mode as bytes. """ @@ -53,7 +52,7 @@ class ModeWithTweak(Mode, metaclass=abc.ABCMeta): class ModeWithNonce(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def nonce(self) -> bytes: + def nonce(self) -> utils.Buffer: """ The value of the nonce for this mode as bytes. """ @@ -62,7 +61,7 @@ class ModeWithNonce(Mode, metaclass=abc.ABCMeta): class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def tag(self) -> typing.Optional[bytes]: + def tag(self) -> bytes | None: """ The value of the tag supplied to the constructor of this mode. """ @@ -78,16 +77,13 @@ def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None: def _check_iv_length( self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm ) -> None: - if len(self.initialization_vector) * 8 != algorithm.block_size: - raise ValueError( - "Invalid IV size ({}) for {}.".format( - len(self.initialization_vector), self.name - ) - ) + iv_len = len(self.initialization_vector) + if iv_len * 8 != algorithm.block_size: + raise ValueError(f"Invalid IV size ({iv_len}) for {self.name}.") def _check_nonce_length( - nonce: bytes, name: str, algorithm: CipherAlgorithm + nonce: utils.Buffer, name: str, algorithm: CipherAlgorithm ) -> None: if not isinstance(algorithm, BlockCipherAlgorithm): raise UnsupportedAlgorithm( @@ -113,12 +109,12 @@ def _check_iv_and_key_length( class CBC(ModeWithInitializationVector): name = "CBC" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -127,7 +123,7 @@ class CBC(ModeWithInitializationVector): class XTS(ModeWithTweak): name = "XTS" - def __init__(self, tweak: bytes): + def __init__(self, tweak: utils.Buffer): utils._check_byteslike("tweak", tweak) if len(tweak) != 16: @@ -136,7 +132,7 @@ class XTS(ModeWithTweak): self._tweak = tweak @property - def tweak(self) -> bytes: + def tweak(self) -> utils.Buffer: return self._tweak def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: @@ -162,12 +158,12 @@ class ECB(Mode): class OFB(ModeWithInitializationVector): name = "OFB" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -176,12 +172,12 @@ class OFB(ModeWithInitializationVector): class CFB(ModeWithInitializationVector): name = "CFB" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -190,12 +186,12 @@ class CFB(ModeWithInitializationVector): class CFB8(ModeWithInitializationVector): name = "CFB8" - def __init__(self, initialization_vector: bytes): + def __init__(self, initialization_vector: utils.Buffer): utils._check_byteslike("initialization_vector", initialization_vector) self._initialization_vector = initialization_vector @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector validate_for_algorithm = _check_iv_and_key_length @@ -204,12 +200,12 @@ class CFB8(ModeWithInitializationVector): class CTR(ModeWithNonce): name = "CTR" - def __init__(self, nonce: bytes): + def __init__(self, nonce: utils.Buffer): utils._check_byteslike("nonce", nonce) self._nonce = nonce @property - def nonce(self) -> bytes: + def nonce(self) -> utils.Buffer: return self._nonce def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: @@ -224,8 +220,8 @@ class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): def __init__( self, - initialization_vector: bytes, - tag: typing.Optional[bytes] = None, + initialization_vector: utils.Buffer, + tag: bytes | None = None, min_tag_length: int = 16, ): # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive @@ -243,19 +239,18 @@ class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): raise ValueError("min_tag_length must be >= 4") if len(tag) < min_tag_length: raise ValueError( - "Authentication tag must be {} bytes or longer.".format( - min_tag_length - ) + f"Authentication tag must be {min_tag_length} bytes or " + "longer." ) self._tag = tag self._min_tag_length = min_tag_length @property - def tag(self) -> typing.Optional[bytes]: + def tag(self) -> bytes | None: return self._tag @property - def initialization_vector(self) -> bytes: + def initialization_vector(self) -> utils.Buffer: return self._initialization_vector def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: @@ -268,7 +263,6 @@ class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): block_size_bytes = algorithm.block_size // 8 if self._tag is not None and len(self._tag) > block_size_bytes: raise ValueError( - "Authentication tag cannot be more than {} bytes.".format( - block_size_bytes - ) + f"Authentication tag cannot be more than {block_size_bytes} " + "bytes." ) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/cmac.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/cmac.py index 8aa1d791..2c67ce22 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/cmac.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/cmac.py @@ -4,62 +4,7 @@ from __future__ import annotations -import typing +from cryptography.hazmat.bindings._rust import openssl as rust_openssl -from cryptography import utils -from cryptography.exceptions import AlreadyFinalized -from cryptography.hazmat.primitives import ciphers - -if typing.TYPE_CHECKING: - from cryptography.hazmat.backends.openssl.cmac import _CMACContext - - -class CMAC: - _ctx: typing.Optional[_CMACContext] - _algorithm: ciphers.BlockCipherAlgorithm - - def __init__( - self, - algorithm: ciphers.BlockCipherAlgorithm, - backend: typing.Any = None, - ctx: typing.Optional[_CMACContext] = None, - ) -> None: - if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): - raise TypeError("Expected instance of BlockCipherAlgorithm.") - self._algorithm = algorithm - - if ctx is None: - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - self._ctx = ossl.create_cmac_ctx(self._algorithm) - else: - self._ctx = ctx - - def update(self, data: bytes) -> None: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_bytes("data", data) - self._ctx.update(data) - - def finalize(self) -> bytes: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - digest = self._ctx.finalize() - self._ctx = None - return digest - - def verify(self, signature: bytes) -> None: - utils._check_bytes("signature", signature) - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - - ctx, self._ctx = self._ctx, None - ctx.verify(signature) - - def copy(self) -> CMAC: - if self._ctx is None: - raise AlreadyFinalized("Context was already finalized.") - return CMAC(self._algorithm, ctx=self._ctx.copy()) +__all__ = ["CMAC"] +CMAC = rust_openssl.cmac.CMAC diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/hashes.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/hashes.py index b6a7ff14..4b55ec33 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/hashes.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/hashes.py @@ -5,32 +5,33 @@ from __future__ import annotations import abc -import typing from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.utils import Buffer __all__ = [ - "HashAlgorithm", - "HashContext", - "Hash", - "ExtendableOutputFunction", + "MD5", "SHA1", - "SHA512_224", - "SHA512_256", - "SHA224", - "SHA256", - "SHA384", - "SHA512", "SHA3_224", "SHA3_256", "SHA3_384", "SHA3_512", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512_224", + "SHA512_256", "SHAKE128", "SHAKE256", - "MD5", + "SM3", "BLAKE2b", "BLAKE2s", - "SM3", + "ExtendableOutputFunction", + "Hash", + "HashAlgorithm", + "HashContext", + "XOFHash", ] @@ -51,7 +52,7 @@ class HashAlgorithm(metaclass=abc.ABCMeta): @property @abc.abstractmethod - def block_size(self) -> typing.Optional[int]: + def block_size(self) -> int | None: """ The internal block size of the hash function, or None if the hash function does not use blocks internally (e.g. SHA3). @@ -67,7 +68,7 @@ class HashContext(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def update(self, data: bytes) -> None: + def update(self, data: Buffer) -> None: """ Processes the provided bytes through the hash. """ @@ -88,6 +89,8 @@ class HashContext(metaclass=abc.ABCMeta): Hash = rust_openssl.hashes.Hash HashContext.register(Hash) +XOFHash = rust_openssl.hashes.XOFHash + class ExtendableOutputFunction(metaclass=abc.ABCMeta): """ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc index 5885aa81..cdd51588 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/argon2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/argon2.cpython-312.pyc new file mode 100644 index 00000000..0f79f0d9 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/argon2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc index e022f3ad..636a296d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc index a1eccb58..bf7c5dba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc index ec4bc1ee..9eb3985d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc index 17ea3f88..1dfcf126 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc index 192565e8..08500ef2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc index 2f1c1b94..768a80c0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py new file mode 100644 index 00000000..405fc8df --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/argon2.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +Argon2id = rust_openssl.kdf.Argon2id +KeyDerivationFunction.register(Argon2id) + +__all__ = ["Argon2id"] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py index d5ea58a9..1b928415 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -5,6 +5,7 @@ from __future__ import annotations import typing +from collections.abc import Callable from cryptography import utils from cryptography.exceptions import AlreadyFinalized, InvalidKey @@ -19,7 +20,7 @@ def _int_to_u32be(n: int) -> bytes: def _common_args_checks( algorithm: hashes.HashAlgorithm, length: int, - otherinfo: typing.Optional[bytes], + otherinfo: bytes | None, ) -> None: max_length = algorithm.digest_size * (2**32 - 1) if length > max_length: @@ -29,9 +30,9 @@ def _common_args_checks( def _concatkdf_derive( - key_material: bytes, + key_material: utils.Buffer, length: int, - auxfn: typing.Callable[[], hashes.HashContext], + auxfn: Callable[[], hashes.HashContext], otherinfo: bytes, ) -> bytes: utils._check_byteslike("key_material", key_material) @@ -56,7 +57,7 @@ class ConcatKDFHash(KeyDerivationFunction): self, algorithm: hashes.HashAlgorithm, length: int, - otherinfo: typing.Optional[bytes], + otherinfo: bytes | None, backend: typing.Any = None, ): _common_args_checks(algorithm, length, otherinfo) @@ -69,7 +70,7 @@ class ConcatKDFHash(KeyDerivationFunction): def _hash(self) -> hashes.Hash: return hashes.Hash(self._algorithm) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized self._used = True @@ -87,8 +88,8 @@ class ConcatKDFHMAC(KeyDerivationFunction): self, algorithm: hashes.HashAlgorithm, length: int, - salt: typing.Optional[bytes], - otherinfo: typing.Optional[bytes], + salt: bytes | None, + otherinfo: bytes | None, backend: typing.Any = None, ): _common_args_checks(algorithm, length, otherinfo) @@ -111,7 +112,7 @@ class ConcatKDFHMAC(KeyDerivationFunction): def _hmac(self) -> hmac.HMAC: return hmac.HMAC(self._salt, self._algorithm) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized self._used = True diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py index d4768944..1e162d9d 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py @@ -4,98 +4,13 @@ from __future__ import annotations -import typing - -from cryptography import utils -from cryptography.exceptions import AlreadyFinalized, InvalidKey -from cryptography.hazmat.primitives import constant_time, hashes, hmac +from cryptography.hazmat.bindings._rust import openssl as rust_openssl from cryptography.hazmat.primitives.kdf import KeyDerivationFunction +HKDF = rust_openssl.kdf.HKDF +HKDFExpand = rust_openssl.kdf.HKDFExpand -class HKDF(KeyDerivationFunction): - def __init__( - self, - algorithm: hashes.HashAlgorithm, - length: int, - salt: typing.Optional[bytes], - info: typing.Optional[bytes], - backend: typing.Any = None, - ): - self._algorithm = algorithm +KeyDerivationFunction.register(HKDF) +KeyDerivationFunction.register(HKDFExpand) - if salt is None: - salt = b"\x00" * self._algorithm.digest_size - else: - utils._check_bytes("salt", salt) - - self._salt = salt - - self._hkdf_expand = HKDFExpand(self._algorithm, length, info) - - def _extract(self, key_material: bytes) -> bytes: - h = hmac.HMAC(self._salt, self._algorithm) - h.update(key_material) - return h.finalize() - - def derive(self, key_material: bytes) -> bytes: - utils._check_byteslike("key_material", key_material) - return self._hkdf_expand.derive(self._extract(key_material)) - - def verify(self, key_material: bytes, expected_key: bytes) -> None: - if not constant_time.bytes_eq(self.derive(key_material), expected_key): - raise InvalidKey - - -class HKDFExpand(KeyDerivationFunction): - def __init__( - self, - algorithm: hashes.HashAlgorithm, - length: int, - info: typing.Optional[bytes], - backend: typing.Any = None, - ): - self._algorithm = algorithm - - max_length = 255 * algorithm.digest_size - - if length > max_length: - raise ValueError( - f"Cannot derive keys larger than {max_length} octets." - ) - - self._length = length - - if info is None: - info = b"" - else: - utils._check_bytes("info", info) - - self._info = info - - self._used = False - - def _expand(self, key_material: bytes) -> bytes: - output = [b""] - counter = 1 - - while self._algorithm.digest_size * (len(output) - 1) < self._length: - h = hmac.HMAC(key_material, self._algorithm) - h.update(output[-1]) - h.update(self._info) - h.update(bytes([counter])) - output.append(h.finalize()) - counter += 1 - - return b"".join(output)[: self._length] - - def derive(self, key_material: bytes) -> bytes: - utils._check_byteslike("key_material", key_material) - if self._used: - raise AlreadyFinalized - - self._used = True - return self._expand(key_material) - - def verify(self, key_material: bytes, expected_key: bytes) -> None: - if not constant_time.bytes_eq(self.derive(key_material), expected_key): - raise InvalidKey +__all__ = ["HKDF", "HKDFExpand"] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py index 96776382..5b471376 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -5,6 +5,7 @@ from __future__ import annotations import typing +from collections.abc import Callable from cryptography import utils from cryptography.exceptions import ( @@ -36,16 +37,16 @@ class CounterLocation(utils.Enum): class _KBKDFDeriver: def __init__( self, - prf: typing.Callable, + prf: Callable, mode: Mode, length: int, rlen: int, - llen: typing.Optional[int], + llen: int | None, location: CounterLocation, - break_location: typing.Optional[int], - label: typing.Optional[bytes], - context: typing.Optional[bytes], - fixed: typing.Optional[bytes], + break_location: int | None, + label: bytes | None, + context: bytes | None, + fixed: bytes | None, ): assert callable(prf) @@ -75,7 +76,7 @@ class _KBKDFDeriver: if (label or context) and fixed: raise ValueError( - "When supplying fixed data, " "label and context are ignored." + "When supplying fixed data, label and context are ignored." ) if rlen is None or not self._valid_byte_length(rlen): @@ -87,6 +88,9 @@ class _KBKDFDeriver: if llen is not None and not isinstance(llen, int): raise TypeError("llen must be an integer") + if llen == 0: + raise ValueError("llen must be non-zero") + if label is None: label = b"" @@ -113,11 +117,11 @@ class _KBKDFDeriver: raise TypeError("value must be of type int") value_bin = utils.int_to_bytes(1, value) - if not 1 <= len(value_bin) <= 4: - return False - return True + return 1 <= len(value_bin) <= 4 - def derive(self, key_material: bytes, prf_output_size: int) -> bytes: + def derive( + self, key_material: utils.Buffer, prf_output_size: int + ) -> bytes: if self._used: raise AlreadyFinalized @@ -181,14 +185,14 @@ class KBKDFHMAC(KeyDerivationFunction): mode: Mode, length: int, rlen: int, - llen: typing.Optional[int], + llen: int | None, location: CounterLocation, - label: typing.Optional[bytes], - context: typing.Optional[bytes], - fixed: typing.Optional[bytes], + label: bytes | None, + context: bytes | None, + fixed: bytes | None, backend: typing.Any = None, *, - break_location: typing.Optional[int] = None, + break_location: int | None = None, ): if not isinstance(algorithm, hashes.HashAlgorithm): raise UnsupportedAlgorithm( @@ -224,7 +228,7 @@ class KBKDFHMAC(KeyDerivationFunction): def _prf(self, key_material: bytes) -> hmac.HMAC: return hmac.HMAC(key_material, self._algorithm) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: return self._deriver.derive(key_material, self._algorithm.digest_size) def verify(self, key_material: bytes, expected_key: bytes) -> None: @@ -239,14 +243,14 @@ class KBKDFCMAC(KeyDerivationFunction): mode: Mode, length: int, rlen: int, - llen: typing.Optional[int], + llen: int | None, location: CounterLocation, - label: typing.Optional[bytes], - context: typing.Optional[bytes], - fixed: typing.Optional[bytes], + label: bytes | None, + context: bytes | None, + fixed: bytes | None, backend: typing.Any = None, *, - break_location: typing.Optional[int] = None, + break_location: int | None = None, ): if not issubclass( algorithm, ciphers.BlockCipherAlgorithm @@ -257,7 +261,7 @@ class KBKDFCMAC(KeyDerivationFunction): ) self._algorithm = algorithm - self._cipher: typing.Optional[ciphers.BlockCipherAlgorithm] = None + self._cipher: ciphers.BlockCipherAlgorithm | None = None self._deriver = _KBKDFDeriver( self._prf, @@ -277,7 +281,7 @@ class KBKDFCMAC(KeyDerivationFunction): return cmac.CMAC(self._cipher) - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: self._cipher = self._algorithm(key_material) assert self._cipher is not None diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py index 623e1ca7..d539f131 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -33,9 +33,7 @@ class PBKDF2HMAC(KeyDerivationFunction): if not ossl.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( - "{} is not supported for PBKDF2 by this backend.".format( - algorithm.name - ), + f"{algorithm.name} is not supported for PBKDF2.", _Reasons.UNSUPPORTED_HASH, ) self._used = False @@ -45,7 +43,7 @@ class PBKDF2HMAC(KeyDerivationFunction): self._salt = salt self._iterations = iterations - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized("PBKDF2 instances can only be used once.") self._used = True diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py index 05a4f675..f791ceea 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/scrypt.py @@ -5,76 +5,15 @@ from __future__ import annotations import sys -import typing -from cryptography import utils -from cryptography.exceptions import ( - AlreadyFinalized, - InvalidKey, - UnsupportedAlgorithm, -) from cryptography.hazmat.bindings._rust import openssl as rust_openssl -from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.kdf import KeyDerivationFunction # This is used by the scrypt tests to skip tests that require more memory # than the MEM_LIMIT _MEM_LIMIT = sys.maxsize // 2 +Scrypt = rust_openssl.kdf.Scrypt +KeyDerivationFunction.register(Scrypt) -class Scrypt(KeyDerivationFunction): - def __init__( - self, - salt: bytes, - length: int, - n: int, - r: int, - p: int, - backend: typing.Any = None, - ): - from cryptography.hazmat.backends.openssl.backend import ( - backend as ossl, - ) - - if not ossl.scrypt_supported(): - raise UnsupportedAlgorithm( - "This version of OpenSSL does not support scrypt" - ) - self._length = length - utils._check_bytes("salt", salt) - if n < 2 or (n & (n - 1)) != 0: - raise ValueError("n must be greater than 1 and be a power of 2.") - - if r < 1: - raise ValueError("r must be greater than or equal to 1.") - - if p < 1: - raise ValueError("p must be greater than or equal to 1.") - - self._used = False - self._salt = salt - self._n = n - self._r = r - self._p = p - - def derive(self, key_material: bytes) -> bytes: - if self._used: - raise AlreadyFinalized("Scrypt instances can only be used once.") - self._used = True - - utils._check_byteslike("key_material", key_material) - - return rust_openssl.kdf.derive_scrypt( - key_material, - self._salt, - self._n, - self._r, - self._p, - _MEM_LIMIT, - self._length, - ) - - def verify(self, key_material: bytes, expected_key: bytes) -> None: - derived_key = self.derive(key_material) - if not constant_time.bytes_eq(derived_key, expected_key): - raise InvalidKey("Keys do not match.") +__all__ = ["Scrypt"] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py index 17acc517..63870cdc 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -21,7 +21,7 @@ class X963KDF(KeyDerivationFunction): self, algorithm: hashes.HashAlgorithm, length: int, - sharedinfo: typing.Optional[bytes], + sharedinfo: bytes | None, backend: typing.Any = None, ): max_len = algorithm.digest_size * (2**32 - 1) @@ -35,7 +35,7 @@ class X963KDF(KeyDerivationFunction): self._sharedinfo = sharedinfo self._used = False - def derive(self, key_material: bytes) -> bytes: + def derive(self, key_material: utils.Buffer) -> bytes: if self._used: raise AlreadyFinalized self._used = True diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/keywrap.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/keywrap.py index 59b0326c..b93d87d3 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/keywrap.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/keywrap.py @@ -15,7 +15,7 @@ from cryptography.hazmat.primitives.constant_time import bytes_eq def _wrap_core( wrapping_key: bytes, a: bytes, - r: typing.List[bytes], + r: list[bytes], ) -> bytes: # RFC 3394 Key Wrap - 2.2.1 (index method) encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() @@ -58,8 +58,8 @@ def aes_key_wrap( def _unwrap_core( wrapping_key: bytes, a: bytes, - r: typing.List[bytes], -) -> typing.Tuple[bytes, typing.List[bytes]]: + r: list[bytes], +) -> tuple[bytes, list[bytes]]: # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() n = len(r) @@ -86,7 +86,7 @@ def aes_key_wrap_with_padding( if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") - aiv = b"\xA6\x59\x59\xA6" + len(key_to_wrap).to_bytes( + aiv = b"\xa6\x59\x59\xa6" + len(key_to_wrap).to_bytes( length=4, byteorder="big" ) # pad the key to wrap if necessary diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/padding.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/padding.py index fde3094b..f9cd1f13 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/padding.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/padding.py @@ -5,19 +5,19 @@ from __future__ import annotations import abc -import typing from cryptography import utils -from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.bindings._rust import ( - check_ansix923_padding, - check_pkcs7_padding, + ANSIX923PaddingContext, + ANSIX923UnpaddingContext, + PKCS7PaddingContext, + PKCS7UnpaddingContext, ) class PaddingContext(metaclass=abc.ABCMeta): @abc.abstractmethod - def update(self, data: bytes) -> bytes: + def update(self, data: utils.Buffer) -> bytes: """ Pads the provided bytes and returns any available data as bytes. """ @@ -37,131 +37,20 @@ def _byte_padding_check(block_size: int) -> None: raise ValueError("block_size must be a multiple of 8.") -def _byte_padding_update( - buffer_: typing.Optional[bytes], data: bytes, block_size: int -) -> typing.Tuple[bytes, bytes]: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_byteslike("data", data) - - buffer_ += bytes(data) - - finished_blocks = len(buffer_) // (block_size // 8) - - result = buffer_[: finished_blocks * (block_size // 8)] - buffer_ = buffer_[finished_blocks * (block_size // 8) :] - - return buffer_, result - - -def _byte_padding_pad( - buffer_: typing.Optional[bytes], - block_size: int, - paddingfn: typing.Callable[[int], bytes], -) -> bytes: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - pad_size = block_size // 8 - len(buffer_) - return buffer_ + paddingfn(pad_size) - - -def _byte_unpadding_update( - buffer_: typing.Optional[bytes], data: bytes, block_size: int -) -> typing.Tuple[bytes, bytes]: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - utils._check_byteslike("data", data) - - buffer_ += bytes(data) - - finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0) - - result = buffer_[: finished_blocks * (block_size // 8)] - buffer_ = buffer_[finished_blocks * (block_size // 8) :] - - return buffer_, result - - -def _byte_unpadding_check( - buffer_: typing.Optional[bytes], - block_size: int, - checkfn: typing.Callable[[bytes], int], -) -> bytes: - if buffer_ is None: - raise AlreadyFinalized("Context was already finalized.") - - if len(buffer_) != block_size // 8: - raise ValueError("Invalid padding bytes.") - - valid = checkfn(buffer_) - - if not valid: - raise ValueError("Invalid padding bytes.") - - pad_size = buffer_[-1] - return buffer_[:-pad_size] - - class PKCS7: def __init__(self, block_size: int): _byte_padding_check(block_size) self.block_size = block_size def padder(self) -> PaddingContext: - return _PKCS7PaddingContext(self.block_size) + return PKCS7PaddingContext(self.block_size) def unpadder(self) -> PaddingContext: - return _PKCS7UnpaddingContext(self.block_size) + return PKCS7UnpaddingContext(self.block_size) -class _PKCS7PaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] - - def __init__(self, block_size: int): - self.block_size = block_size - # TODO: more copies than necessary, we should use zero-buffer (#193) - self._buffer = b"" - - def update(self, data: bytes) -> bytes: - self._buffer, result = _byte_padding_update( - self._buffer, data, self.block_size - ) - return result - - def _padding(self, size: int) -> bytes: - return bytes([size]) * size - - def finalize(self) -> bytes: - result = _byte_padding_pad( - self._buffer, self.block_size, self._padding - ) - self._buffer = None - return result - - -class _PKCS7UnpaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] - - def __init__(self, block_size: int): - self.block_size = block_size - # TODO: more copies than necessary, we should use zero-buffer (#193) - self._buffer = b"" - - def update(self, data: bytes) -> bytes: - self._buffer, result = _byte_unpadding_update( - self._buffer, data, self.block_size - ) - return result - - def finalize(self) -> bytes: - result = _byte_unpadding_check( - self._buffer, self.block_size, check_pkcs7_padding - ) - self._buffer = None - return result +PaddingContext.register(PKCS7PaddingContext) +PaddingContext.register(PKCS7UnpaddingContext) class ANSIX923: @@ -170,56 +59,11 @@ class ANSIX923: self.block_size = block_size def padder(self) -> PaddingContext: - return _ANSIX923PaddingContext(self.block_size) + return ANSIX923PaddingContext(self.block_size) def unpadder(self) -> PaddingContext: - return _ANSIX923UnpaddingContext(self.block_size) + return ANSIX923UnpaddingContext(self.block_size) -class _ANSIX923PaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] - - def __init__(self, block_size: int): - self.block_size = block_size - # TODO: more copies than necessary, we should use zero-buffer (#193) - self._buffer = b"" - - def update(self, data: bytes) -> bytes: - self._buffer, result = _byte_padding_update( - self._buffer, data, self.block_size - ) - return result - - def _padding(self, size: int) -> bytes: - return bytes([0]) * (size - 1) + bytes([size]) - - def finalize(self) -> bytes: - result = _byte_padding_pad( - self._buffer, self.block_size, self._padding - ) - self._buffer = None - return result - - -class _ANSIX923UnpaddingContext(PaddingContext): - _buffer: typing.Optional[bytes] - - def __init__(self, block_size: int): - self.block_size = block_size - # TODO: more copies than necessary, we should use zero-buffer (#193) - self._buffer = b"" - - def update(self, data: bytes) -> bytes: - self._buffer, result = _byte_unpadding_update( - self._buffer, data, self.block_size - ) - return result - - def finalize(self) -> bytes: - result = _byte_unpadding_check( - self._buffer, - self.block_size, - check_ansix923_padding, - ) - self._buffer = None - return result +PaddingContext.register(ANSIX923PaddingContext) +PaddingContext.register(ANSIX923UnpaddingContext) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__init__.py index b6c9a5cd..62283cc7 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__init__.py @@ -33,9 +33,25 @@ from cryptography.hazmat.primitives.serialization.ssh import ( load_ssh_private_key, load_ssh_public_identity, load_ssh_public_key, + ssh_key_fingerprint, ) __all__ = [ + "BestAvailableEncryption", + "Encoding", + "KeySerializationEncryption", + "NoEncryption", + "ParameterFormat", + "PrivateFormat", + "PublicFormat", + "SSHCertPrivateKeyTypes", + "SSHCertPublicKeyTypes", + "SSHCertificate", + "SSHCertificateBuilder", + "SSHCertificateType", + "SSHPrivateKeyTypes", + "SSHPublicKeyTypes", + "_KeySerializationEncryption", "load_der_parameters", "load_der_private_key", "load_der_public_key", @@ -45,19 +61,5 @@ __all__ = [ "load_ssh_private_key", "load_ssh_public_identity", "load_ssh_public_key", - "Encoding", - "PrivateFormat", - "PublicFormat", - "ParameterFormat", - "KeySerializationEncryption", - "BestAvailableEncryption", - "NoEncryption", - "_KeySerializationEncryption", - "SSHCertificateBuilder", - "SSHCertificate", - "SSHCertificateType", - "SSHCertPublicKeyTypes", - "SSHCertPrivateKeyTypes", - "SSHPrivateKeyTypes", - "SSHPublicKeyTypes", + "ssh_key_fingerprint", ] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc index d9650ebc..61d9cea5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc index a6ca4b1a..9ebb9d94 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc index b5f803e8..468fa4a4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc index 9f923d0a..00e1d8ab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc index 1555abf7..3778f557 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/base.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/base.py index 18a96ccf..e7c998b7 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/base.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/base.py @@ -2,72 +2,13 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import annotations +from cryptography.hazmat.bindings._rust import openssl as rust_openssl -import typing +load_pem_private_key = rust_openssl.keys.load_pem_private_key +load_der_private_key = rust_openssl.keys.load_der_private_key -from cryptography.hazmat.primitives.asymmetric import dh -from cryptography.hazmat.primitives.asymmetric.types import ( - PrivateKeyTypes, - PublicKeyTypes, -) +load_pem_public_key = rust_openssl.keys.load_pem_public_key +load_der_public_key = rust_openssl.keys.load_der_public_key - -def load_pem_private_key( - data: bytes, - password: typing.Optional[bytes], - backend: typing.Any = None, - *, - unsafe_skip_rsa_key_validation: bool = False, -) -> PrivateKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pem_private_key( - data, password, unsafe_skip_rsa_key_validation - ) - - -def load_pem_public_key( - data: bytes, backend: typing.Any = None -) -> PublicKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pem_public_key(data) - - -def load_pem_parameters( - data: bytes, backend: typing.Any = None -) -> dh.DHParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pem_parameters(data) - - -def load_der_private_key( - data: bytes, - password: typing.Optional[bytes], - backend: typing.Any = None, - *, - unsafe_skip_rsa_key_validation: bool = False, -) -> PrivateKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_der_private_key( - data, password, unsafe_skip_rsa_key_validation - ) - - -def load_der_public_key( - data: bytes, backend: typing.Any = None -) -> PublicKeyTypes: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_der_public_key(data) - - -def load_der_parameters( - data: bytes, backend: typing.Any = None -) -> dh.DHParameters: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_der_parameters(data) +load_pem_parameters = rust_openssl.dh.from_pem_parameters +load_der_parameters = rust_openssl.dh.from_der_parameters diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs12.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs12.py index 27133a3f..58884ff6 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -5,8 +5,10 @@ from __future__ import annotations import typing +from collections.abc import Iterable from cryptography import x509 +from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives._serialization import PBES as PBES from cryptography.hazmat.primitives.asymmetric import ( @@ -20,11 +22,12 @@ from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes __all__ = [ "PBES", - "PKCS12PrivateKeyTypes", "PKCS12Certificate", "PKCS12KeyAndCertificates", + "PKCS12PrivateKeyTypes", "load_key_and_certificates", "load_pkcs12", + "serialize_java_truststore", "serialize_key_and_certificates", ] @@ -37,51 +40,15 @@ PKCS12PrivateKeyTypes = typing.Union[ ] -class PKCS12Certificate: - def __init__( - self, - cert: x509.Certificate, - friendly_name: typing.Optional[bytes], - ): - if not isinstance(cert, x509.Certificate): - raise TypeError("Expecting x509.Certificate object") - if friendly_name is not None and not isinstance(friendly_name, bytes): - raise TypeError("friendly_name must be bytes or None") - self._cert = cert - self._friendly_name = friendly_name - - @property - def friendly_name(self) -> typing.Optional[bytes]: - return self._friendly_name - - @property - def certificate(self) -> x509.Certificate: - return self._cert - - def __eq__(self, other: object) -> bool: - if not isinstance(other, PKCS12Certificate): - return NotImplemented - - return ( - self.certificate == other.certificate - and self.friendly_name == other.friendly_name - ) - - def __hash__(self) -> int: - return hash((self.certificate, self.friendly_name)) - - def __repr__(self) -> str: - return "".format( - self.certificate, self.friendly_name - ) +PKCS12Certificate = rust_pkcs12.PKCS12Certificate class PKCS12KeyAndCertificates: def __init__( self, - key: typing.Optional[PrivateKeyTypes], - cert: typing.Optional[PKCS12Certificate], - additional_certs: typing.List[PKCS12Certificate], + key: PrivateKeyTypes | None, + cert: PKCS12Certificate | None, + additional_certs: list[PKCS12Certificate], ): if key is not None and not isinstance( key, @@ -112,15 +79,15 @@ class PKCS12KeyAndCertificates: self._additional_certs = additional_certs @property - def key(self) -> typing.Optional[PrivateKeyTypes]: + def key(self) -> PrivateKeyTypes | None: return self._key @property - def cert(self) -> typing.Optional[PKCS12Certificate]: + def cert(self) -> PKCS12Certificate | None: return self._cert @property - def additional_certs(self) -> typing.List[PKCS12Certificate]: + def additional_certs(self) -> list[PKCS12Certificate]: return self._additional_certs def __eq__(self, other: object) -> bool: @@ -143,28 +110,8 @@ class PKCS12KeyAndCertificates: return fmt.format(self.key, self.cert, self.additional_certs) -def load_key_and_certificates( - data: bytes, - password: typing.Optional[bytes], - backend: typing.Any = None, -) -> typing.Tuple[ - typing.Optional[PrivateKeyTypes], - typing.Optional[x509.Certificate], - typing.List[x509.Certificate], -]: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_key_and_certificates_from_pkcs12(data, password) - - -def load_pkcs12( - data: bytes, - password: typing.Optional[bytes], - backend: typing.Any = None, -) -> PKCS12KeyAndCertificates: - from cryptography.hazmat.backends.openssl.backend import backend as ossl - - return ossl.load_pkcs12(data, password) +load_key_and_certificates = rust_pkcs12.load_key_and_certificates +load_pkcs12 = rust_pkcs12.load_pkcs12 _PKCS12CATypes = typing.Union[ @@ -173,11 +120,29 @@ _PKCS12CATypes = typing.Union[ ] +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: + if not certs: + raise ValueError("You must supply at least one cert") + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + return rust_pkcs12.serialize_java_truststore(certs, encryption_algorithm) + + def serialize_key_and_certificates( - name: typing.Optional[bytes], - key: typing.Optional[PKCS12PrivateKeyTypes], - cert: typing.Optional[x509.Certificate], - cas: typing.Optional[typing.Iterable[_PKCS12CATypes]], + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: Iterable[_PKCS12CATypes] | None, encryption_algorithm: serialization.KeySerializationEncryption, ) -> bytes: if key is not None and not isinstance( @@ -194,22 +159,6 @@ def serialize_key_and_certificates( "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" " private key, or None." ) - if cert is not None and not isinstance(cert, x509.Certificate): - raise TypeError("cert must be a certificate or None") - - if cas is not None: - cas = list(cas) - if not all( - isinstance( - val, - ( - x509.Certificate, - PKCS12Certificate, - ), - ) - for val in cas - ): - raise TypeError("all values in cas must be certificates") if not isinstance( encryption_algorithm, serialization.KeySerializationEncryption @@ -222,8 +171,6 @@ def serialize_key_and_certificates( if key is None and cert is None and not cas: raise ValueError("You must supply at least one of key, cert, or cas") - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.serialize_key_and_certificates_to_pkcs12( + return rust_pkcs12.serialize_key_and_certificates( name, key, cert, cas, encryption_algorithm ) diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs7.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs7.py index 9998bcaa..456dc5b0 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -10,32 +10,23 @@ import email.message import email.policy import io import typing +from collections.abc import Iterable from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa +from cryptography.hazmat.primitives.ciphers import ( + algorithms, +) from cryptography.utils import _check_byteslike +load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates -def load_pem_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_pem_pkcs7_certificates(data) - - -def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_der_pkcs7_certificates(data) - - -def serialize_certificates( - certs: typing.List[x509.Certificate], - encoding: serialization.Encoding, -) -> bytes: - return rust_pkcs7.serialize_certificates(certs, encoding) +load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates +serialize_certificates = rust_pkcs7.serialize_certificates PKCS7HashTypes = typing.Union[ hashes.SHA224, @@ -48,6 +39,10 @@ PKCS7PrivateKeyTypes = typing.Union[ rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey ] +ContentEncryptionAlgorithm = typing.Union[ + typing.Type[algorithms.AES128], typing.Type[algorithms.AES256] +] + class PKCS7Options(utils.Enum): Text = "Add text/plain MIME type" @@ -61,21 +56,22 @@ class PKCS7Options(utils.Enum): class PKCS7SignatureBuilder: def __init__( self, - data: typing.Optional[bytes] = None, - signers: typing.List[ - typing.Tuple[ + data: utils.Buffer | None = None, + signers: list[ + tuple[ x509.Certificate, PKCS7PrivateKeyTypes, PKCS7HashTypes, + padding.PSS | padding.PKCS1v15 | None, ] ] = [], - additional_certs: typing.List[x509.Certificate] = [], + additional_certs: list[x509.Certificate] = [], ): self._data = data self._signers = signers self._additional_certs = additional_certs - def set_data(self, data: bytes) -> PKCS7SignatureBuilder: + def set_data(self, data: utils.Buffer) -> PKCS7SignatureBuilder: _check_byteslike("data", data) if self._data is not None: raise ValueError("data may only be set once") @@ -87,6 +83,8 @@ class PKCS7SignatureBuilder: certificate: x509.Certificate, private_key: PKCS7PrivateKeyTypes, hash_algorithm: PKCS7HashTypes, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, ) -> PKCS7SignatureBuilder: if not isinstance( hash_algorithm, @@ -109,9 +107,18 @@ class PKCS7SignatureBuilder: ): raise TypeError("Only RSA & EC keys are supported at this time.") + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + return PKCS7SignatureBuilder( self._data, - self._signers + [(certificate, private_key, hash_algorithm)], + [ + *self._signers, + (certificate, private_key, hash_algorithm, rsa_padding), + ], ) def add_certificate( @@ -121,13 +128,13 @@ class PKCS7SignatureBuilder: raise TypeError("certificate must be a x509.Certificate") return PKCS7SignatureBuilder( - self._data, self._signers, self._additional_certs + [certificate] + self._data, self._signers, [*self._additional_certs, certificate] ) def sign( self, encoding: serialization.Encoding, - options: typing.Iterable[PKCS7Options], + options: Iterable[PKCS7Options], backend: typing.Any = None, ) -> bytes: if len(self._signers) == 0: @@ -179,7 +186,131 @@ class PKCS7SignatureBuilder: return rust_pkcs7.sign_and_serialize(self, encoding, options) -def _smime_encode( +class PKCS7EnvelopeBuilder: + def __init__( + self, + *, + _data: bytes | None = None, + _recipients: list[x509.Certificate] | None = None, + _content_encryption_algorithm: ContentEncryptionAlgorithm + | None = None, + ): + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.rsa_encryption_supported(padding=padding.PKCS1v15()): + raise UnsupportedAlgorithm( + "RSA with PKCS1 v1.5 padding is not supported by this version" + " of OpenSSL.", + _Reasons.UNSUPPORTED_PADDING, + ) + self._data = _data + self._recipients = _recipients if _recipients is not None else [] + self._content_encryption_algorithm = _content_encryption_algorithm + + def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder: + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7EnvelopeBuilder( + _data=data, + _recipients=self._recipients, + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def add_recipient( + self, + certificate: x509.Certificate, + ) -> PKCS7EnvelopeBuilder: + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance(certificate.public_key(), rsa.RSAPublicKey): + raise TypeError("Only RSA keys are supported at this time.") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=[ + *self._recipients, + certificate, + ], + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def set_content_encryption_algorithm( + self, content_encryption_algorithm: ContentEncryptionAlgorithm + ) -> PKCS7EnvelopeBuilder: + if self._content_encryption_algorithm is not None: + raise ValueError("Content encryption algo may only be set once") + if content_encryption_algorithm not in { + algorithms.AES128, + algorithms.AES256, + }: + raise TypeError("Only AES128 and AES256 are supported") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=self._recipients, + _content_encryption_algorithm=content_encryption_algorithm, + ) + + def encrypt( + self, + encoding: serialization.Encoding, + options: Iterable[PKCS7Options], + ) -> bytes: + if len(self._recipients) == 0: + raise ValueError("Must have at least one recipient") + if self._data is None: + raise ValueError("You must add data to encrypt") + + # The default content encryption algorithm is AES-128, which the S/MIME + # v3.2 RFC specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) + content_encryption_algorithm = ( + self._content_encryption_algorithm or algorithms.AES128 + ) + + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Only allow options that make sense for encryption + if any( + opt not in [PKCS7Options.Text, PKCS7Options.Binary] + for opt in options + ): + raise ValueError( + "Only the following options are supported for encryption: " + "Text, Binary" + ) + elif PKCS7Options.Text in options and PKCS7Options.Binary in options: + # OpenSSL accepts both options at the same time, but ignores Text. + # We fail defensively to avoid unexpected outputs. + raise ValueError( + "Cannot use Binary and Text options at the same time" + ) + + return rust_pkcs7.encrypt_and_serialize( + self, content_encryption_algorithm, encoding, options + ) + + +pkcs7_decrypt_der = rust_pkcs7.decrypt_der +pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem +pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime + + +def _smime_signed_encode( data: bytes, signature: bytes, micalg: str, text_mode: bool ) -> bytes: # This function works pretty hard to replicate what OpenSSL does @@ -227,6 +358,51 @@ def _smime_encode( return fp.getvalue() +def _smime_enveloped_encode(data: bytes) -> bytes: + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header("Content-Disposition", "attachment", filename="smime.p7m") + m.add_header( + "Content-Type", + "application/pkcs7-mime", + smime_type="enveloped-data", + name="smime.p7m", + ) + m.add_header("Content-Transfer-Encoding", "base64") + + m.set_payload(email.base64mime.body_encode(data, maxlinelen=65)) + + return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0)) + + +def _smime_enveloped_decode(data: bytes) -> bytes: + m = email.message_from_bytes(data) + if m.get_content_type() not in { + "application/x-pkcs7-mime", + "application/pkcs7-mime", + }: + raise ValueError("Not an S/MIME enveloped message") + return bytes(m.get_payload(decode=True)) + + +def _smime_remove_text_headers(data: bytes) -> bytes: + m = email.message_from_bytes(data) + # Using get() instead of get_content_type() since it has None as default, + # where the latter has "text/plain". Both methods are case-insensitive. + content_type = m.get("content-type") + if content_type is None: + raise ValueError( + "Decrypted MIME data has no 'Content-Type' header. " + "Please remove the 'Text' option to parse it manually." + ) + if "text/plain" not in content_type: + raise ValueError( + f"Decrypted MIME data content type is '{content_type}', not " + "'text/plain'. Remove the 'Text' option to parse it manually." + ) + return bytes(m.get_payload(decode=True)) + + class OpenSSLMimePart(email.message.MIMEPart): # A MIMEPart subclass that replicates OpenSSL's behavior of not including # a newline if there are no headers. diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/ssh.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/ssh.py index 35e53c10..cb10cf89 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/ssh.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/ssh.py @@ -64,6 +64,10 @@ _ECDSA_NISTP384 = b"ecdsa-sha2-nistp384" _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" _CERT_SUFFIX = b"-cert-v01@openssh.com" +# U2F application string suffixed pubkey +_SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com" +_SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com" + # These are not key types, only algorithms, so they cannot appear # as a public key type _SSH_RSA_SHA256 = b"rsa-sha2-256" @@ -87,21 +91,17 @@ _PADDING = memoryview(bytearray(range(1, 1 + 16))) @dataclass class _SSHCipher: - alg: typing.Type[algorithms.AES] + alg: type[algorithms.AES] key_len: int - mode: typing.Union[ - typing.Type[modes.CTR], - typing.Type[modes.CBC], - typing.Type[modes.GCM], - ] + mode: type[modes.CTR] | type[modes.CBC] | type[modes.GCM] block_len: int iv_len: int - tag_len: typing.Optional[int] + tag_len: int | None is_aead: bool # ciphers that are actually used in key wrapping -_SSH_CIPHERS: typing.Dict[bytes, _SSHCipher] = { +_SSH_CIPHERS: dict[bytes, _SSHCipher] = { b"aes256-ctr": _SSHCipher( alg=algorithms.AES, key_len=32, @@ -139,9 +139,7 @@ _ECDSA_KEY_TYPE = { } -def _get_ssh_key_type( - key: typing.Union[SSHPrivateKeyTypes, SSHPublicKeyTypes] -) -> bytes: +def _get_ssh_key_type(key: SSHPrivateKeyTypes | SSHPublicKeyTypes) -> bytes: if isinstance(key, ec.EllipticCurvePrivateKey): key_type = _ecdsa_key_type(key.public_key()) elif isinstance(key, ec.EllipticCurvePublicKey): @@ -171,20 +169,20 @@ def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes: def _ssh_pem_encode( - data: bytes, + data: utils.Buffer, prefix: bytes = _SK_START + b"\n", suffix: bytes = _SK_END + b"\n", ) -> bytes: return b"".join([prefix, _base64_encode(data), suffix]) -def _check_block_size(data: bytes, block_len: int) -> None: +def _check_block_size(data: utils.Buffer, block_len: int) -> None: """Require data to be full blocks""" if not data or len(data) % block_len != 0: raise ValueError("Corrupt data: missing padding") -def _check_empty(data: bytes) -> None: +def _check_empty(data: utils.Buffer) -> None: """All data should have been parsed.""" if data: raise ValueError("Corrupt data: unparsed data") @@ -192,13 +190,15 @@ def _check_empty(data: bytes) -> None: def _init_cipher( ciphername: bytes, - password: typing.Optional[bytes], + password: bytes | None, salt: bytes, rounds: int, -) -> Cipher[typing.Union[modes.CBC, modes.CTR, modes.GCM]]: +) -> Cipher[modes.CBC | modes.CTR | modes.GCM]: """Generate key + iv and return cipher.""" if not password: - raise ValueError("Key is password-protected.") + raise TypeError( + "Key is password-protected, but password was not provided." + ) ciph = _SSH_CIPHERS[ciphername] seed = _bcrypt_kdf( @@ -210,21 +210,21 @@ def _init_cipher( ) -def _get_u32(data: memoryview) -> typing.Tuple[int, memoryview]: +def _get_u32(data: memoryview) -> tuple[int, memoryview]: """Uint32""" if len(data) < 4: raise ValueError("Invalid data") return int.from_bytes(data[:4], byteorder="big"), data[4:] -def _get_u64(data: memoryview) -> typing.Tuple[int, memoryview]: +def _get_u64(data: memoryview) -> tuple[int, memoryview]: """Uint64""" if len(data) < 8: raise ValueError("Invalid data") return int.from_bytes(data[:8], byteorder="big"), data[8:] -def _get_sshstr(data: memoryview) -> typing.Tuple[memoryview, memoryview]: +def _get_sshstr(data: memoryview) -> tuple[memoryview, memoryview]: """Bytes with u32 length prefix""" n, data = _get_u32(data) if n > len(data): @@ -232,7 +232,7 @@ def _get_sshstr(data: memoryview) -> typing.Tuple[memoryview, memoryview]: return data[:n], data[n:] -def _get_mpint(data: memoryview) -> typing.Tuple[int, memoryview]: +def _get_mpint(data: memoryview) -> tuple[int, memoryview]: """Big integer.""" val, data = _get_sshstr(data) if val and val[0] > 0x7F: @@ -253,16 +253,14 @@ def _to_mpint(val: int) -> bytes: class _FragList: """Build recursive structure without data copy.""" - flist: typing.List[bytes] + flist: list[utils.Buffer] - def __init__( - self, init: typing.Optional[typing.List[bytes]] = None - ) -> None: + def __init__(self, init: list[utils.Buffer] | None = None) -> None: self.flist = [] if init: self.flist.extend(init) - def put_raw(self, val: bytes) -> None: + def put_raw(self, val: utils.Buffer) -> None: """Add plain bytes""" self.flist.append(val) @@ -274,7 +272,7 @@ class _FragList: """Big-endian uint64""" self.flist.append(val.to_bytes(length=8, byteorder="big")) - def put_sshstr(self, val: typing.Union[bytes, _FragList]) -> None: + def put_sshstr(self, val: bytes | _FragList) -> None: """Bytes prefixed with u32 length""" if isinstance(val, (bytes, memoryview, bytearray)): self.put_u32(len(val)) @@ -315,7 +313,9 @@ class _SSHFormatRSA: mpint n, e, d, iqmp, p, q """ - def get_public(self, data: memoryview): + def get_public( + self, data: memoryview + ) -> tuple[tuple[int, int], memoryview]: """RSA public fields""" e, data = _get_mpint(data) n, data = _get_mpint(data) @@ -323,7 +323,7 @@ class _SSHFormatRSA: def load_public( self, data: memoryview - ) -> typing.Tuple[rsa.RSAPublicKey, memoryview]: + ) -> tuple[rsa.RSAPublicKey, memoryview]: """Make RSA public key from data.""" (e, n), data = self.get_public(data) public_numbers = rsa.RSAPublicNumbers(e, n) @@ -331,8 +331,8 @@ class _SSHFormatRSA: return public_key, data def load_private( - self, data: memoryview, pubfields - ) -> typing.Tuple[rsa.RSAPrivateKey, memoryview]: + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[rsa.RSAPrivateKey, memoryview]: """Make RSA private key from data.""" n, data = _get_mpint(data) e, data = _get_mpint(data) @@ -349,7 +349,9 @@ class _SSHFormatRSA: private_numbers = rsa.RSAPrivateNumbers( p, q, d, dmp1, dmq1, iqmp, public_numbers ) - private_key = private_numbers.private_key() + private_key = private_numbers.private_key( + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation + ) return private_key, data def encode_public( @@ -385,9 +387,7 @@ class _SSHFormatDSA: mpint p, q, g, y, x """ - def get_public( - self, data: memoryview - ) -> typing.Tuple[typing.Tuple, memoryview]: + def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: """DSA public fields""" p, data = _get_mpint(data) q, data = _get_mpint(data) @@ -397,7 +397,7 @@ class _SSHFormatDSA: def load_public( self, data: memoryview - ) -> typing.Tuple[dsa.DSAPublicKey, memoryview]: + ) -> tuple[dsa.DSAPublicKey, memoryview]: """Make DSA public key from data.""" (p, q, g, y), data = self.get_public(data) parameter_numbers = dsa.DSAParameterNumbers(p, q, g) @@ -407,8 +407,8 @@ class _SSHFormatDSA: return public_key, data def load_private( - self, data: memoryview, pubfields - ) -> typing.Tuple[dsa.DSAPrivateKey, memoryview]: + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[dsa.DSAPrivateKey, memoryview]: """Make DSA private key from data.""" (p, q, g, y), data = self.get_public(data) x, data = _get_mpint(data) @@ -466,7 +466,7 @@ class _SSHFormatECDSA: def get_public( self, data: memoryview - ) -> typing.Tuple[typing.Tuple, memoryview]: + ) -> tuple[tuple[memoryview, memoryview], memoryview]: """ECDSA public fields""" curve, data = _get_sshstr(data) point, data = _get_sshstr(data) @@ -478,17 +478,17 @@ class _SSHFormatECDSA: def load_public( self, data: memoryview - ) -> typing.Tuple[ec.EllipticCurvePublicKey, memoryview]: + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: """Make ECDSA public key from data.""" - (curve_name, point), data = self.get_public(data) + (_, point), data = self.get_public(data) public_key = ec.EllipticCurvePublicKey.from_encoded_point( self.curve, point.tobytes() ) return public_key, data def load_private( - self, data: memoryview, pubfields - ) -> typing.Tuple[ec.EllipticCurvePrivateKey, memoryview]: + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]: """Make ECDSA private key from data.""" (curve_name, point), data = self.get_public(data) secret, data = _get_mpint(data) @@ -531,14 +531,14 @@ class _SSHFormatEd25519: def get_public( self, data: memoryview - ) -> typing.Tuple[typing.Tuple, memoryview]: + ) -> tuple[tuple[memoryview], memoryview]: """Ed25519 public fields""" point, data = _get_sshstr(data) return (point,), data def load_public( self, data: memoryview - ) -> typing.Tuple[ed25519.Ed25519PublicKey, memoryview]: + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: """Make Ed25519 public key from data.""" (point,), data = self.get_public(data) public_key = ed25519.Ed25519PublicKey.from_public_bytes( @@ -547,8 +547,8 @@ class _SSHFormatEd25519: return public_key, data def load_private( - self, data: memoryview, pubfields - ) -> typing.Tuple[ed25519.Ed25519PrivateKey, memoryview]: + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]: """Make Ed25519 private key from data.""" (point,), data = self.get_public(data) keypair, data = _get_sshstr(data) @@ -586,6 +586,70 @@ class _SSHFormatEd25519: f_priv.put_sshstr(f_keypair) +def load_application(data) -> tuple[memoryview, memoryview]: + """ + U2F application strings + """ + application, data = _get_sshstr(data) + if not application.tobytes().startswith(b"ssh:"): + raise ValueError( + "U2F application string does not start with b'ssh:' " + f"({application})" + ) + return application, data + + +class _SSHFormatSKEd25519: + """ + The format of a sk-ssh-ed25519@openssh.com public key is: + + string "sk-ssh-ed25519@openssh.com" + string public key + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data) + _, data = load_application(data) + return public_key, data + + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ssh-ed25519 private keys cannot be loaded" + ) + + +class _SSHFormatSKECDSA: + """ + The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: + + string "sk-ecdsa-sha2-nistp256@openssh.com" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make ECDSA public key from data.""" + public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data) + _, data = load_application(data) + return public_key, data + + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ecdsa-sha2-nistp256 private keys cannot be loaded" + ) + + _KEY_FORMATS = { _SSH_RSA: _SSHFormatRSA(), _SSH_DSA: _SSHFormatDSA(), @@ -593,10 +657,12 @@ _KEY_FORMATS = { _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), + _SK_SSH_ED25519: _SSHFormatSKEd25519(), + _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(), } -def _lookup_kformat(key_type: bytes): +def _lookup_kformat(key_type: utils.Buffer): """Return valid format or throw error""" if not isinstance(key_type, bytes): key_type = memoryview(key_type).tobytes() @@ -614,9 +680,11 @@ SSHPrivateKeyTypes = typing.Union[ def load_ssh_private_key( - data: bytes, - password: typing.Optional[bytes], + data: utils.Buffer, + password: bytes | None, backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, ) -> SSHPrivateKeyTypes: """Load private key from OpenSSH custom encoding.""" utils._check_byteslike("data", data) @@ -648,7 +716,7 @@ def load_ssh_private_key( pubfields, pubdata = kformat.get_public(pubdata) _check_empty(pubdata) - if (ciphername, kdfname) != (_NONE, _NONE): + if ciphername != _NONE or kdfname != _NONE: ciphername_bytes = ciphername.tobytes() if ciphername_bytes not in _SSH_CIPHERS: raise UnsupportedAlgorithm( @@ -683,6 +751,10 @@ def load_ssh_private_key( # should be no output from finalize _check_empty(dec.finalize()) else: + if password: + raise TypeError( + "Password was given but private key is not encrypted." + ) # load secret data edata, data = _get_sshstr(data) _check_empty(data) @@ -697,8 +769,13 @@ def load_ssh_private_key( key_type, edata = _get_sshstr(edata) if key_type != pub_key_type: raise ValueError("Corrupt data: key type mismatch") - private_key, edata = kformat.load_private(edata, pubfields) - comment, edata = _get_sshstr(edata) + private_key, edata = kformat.load_private( + edata, + pubfields, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) + # We don't use the comment + _, edata = _get_sshstr(edata) # yes, SSH does padding check *after* all other parsing is done. # need to follow as it writes zero-byte padding too. @@ -820,11 +897,11 @@ class SSHCertificate: _serial: int, _cctype: int, _key_id: memoryview, - _valid_principals: typing.List[bytes], + _valid_principals: list[bytes], _valid_after: int, _valid_before: int, - _critical_options: typing.Dict[bytes, bytes], - _extensions: typing.Dict[bytes, bytes], + _critical_options: dict[bytes, bytes], + _extensions: dict[bytes, bytes], _sig_type: memoryview, _sig_key: memoryview, _inner_sig_type: memoryview, @@ -876,7 +953,7 @@ class SSHCertificate: return bytes(self._key_id) @property - def valid_principals(self) -> typing.List[bytes]: + def valid_principals(self) -> list[bytes]: return self._valid_principals @property @@ -888,11 +965,11 @@ class SSHCertificate: return self._valid_after @property - def critical_options(self) -> typing.Dict[bytes, bytes]: + def critical_options(self) -> dict[bytes, bytes]: return self._critical_options @property - def extensions(self) -> typing.Dict[bytes, bytes]: + def extensions(self) -> dict[bytes, bytes]: return self._extensions def signature_key(self) -> SSHCertPublicKeyTypes: @@ -952,9 +1029,9 @@ def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: def _load_ssh_public_identity( - data: bytes, + data: utils.Buffer, _legacy_dsa_allowed=False, -) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: +) -> SSHCertificate | SSHPublicKeyTypes: utils._check_byteslike("data", data) m = _SSH_PUBKEY_RC.match(data) @@ -1047,13 +1124,13 @@ def _load_ssh_public_identity( def load_ssh_public_identity( - data: bytes, -) -> typing.Union[SSHCertificate, SSHPublicKeyTypes]: + data: utils.Buffer, +) -> SSHCertificate | SSHPublicKeyTypes: return _load_ssh_public_identity(data) -def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]: - result: typing.Dict[bytes, bytes] = {} +def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]: + result: dict[bytes, bytes] = {} last_name = None while exts_opts: name, exts_opts = _get_sshstr(exts_opts) @@ -1064,26 +1141,38 @@ def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]: raise ValueError("Fields not lexically sorted") value, exts_opts = _get_sshstr(exts_opts) if len(value) > 0: - try: - value, extra = _get_sshstr(value) - except ValueError: - warnings.warn( - "This certificate has an incorrect encoding for critical " - "options or extensions. This will be an exception in " - "cryptography 42", - utils.DeprecatedIn41, - stacklevel=4, - ) - else: - if len(extra) > 0: - raise ValueError("Unexpected extra data after value") + value, extra = _get_sshstr(value) + if len(extra) > 0: + raise ValueError("Unexpected extra data after value") result[bname] = bytes(value) last_name = bname return result +def ssh_key_fingerprint( + key: SSHPublicKeyTypes, + hash_algorithm: hashes.MD5 | hashes.SHA256, +) -> bytes: + if not isinstance(hash_algorithm, (hashes.MD5, hashes.SHA256)): + raise TypeError("hash_algorithm must be either MD5 or SHA256") + + key_type = _get_ssh_key_type(key) + kformat = _lookup_kformat(key_type) + + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(key, f_pub) + + ssh_binary_data = f_pub.tobytes() + + # Hash the binary data + hash_obj = hashes.Hash(hash_algorithm) + hash_obj.update(ssh_binary_data) + return hash_obj.finalize() + + def load_ssh_public_key( - data: bytes, backend: typing.Any = None + data: utils.Buffer, backend: typing.Any = None ) -> SSHPublicKeyTypes: cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True) public_key: SSHPublicKeyTypes @@ -1137,16 +1226,16 @@ _SSHKEY_CERT_MAX_PRINCIPALS = 256 class SSHCertificateBuilder: def __init__( self, - _public_key: typing.Optional[SSHCertPublicKeyTypes] = None, - _serial: typing.Optional[int] = None, - _type: typing.Optional[SSHCertificateType] = None, - _key_id: typing.Optional[bytes] = None, - _valid_principals: typing.List[bytes] = [], + _public_key: SSHCertPublicKeyTypes | None = None, + _serial: int | None = None, + _type: SSHCertificateType | None = None, + _key_id: bytes | None = None, + _valid_principals: list[bytes] = [], _valid_for_all_principals: bool = False, - _valid_before: typing.Optional[int] = None, - _valid_after: typing.Optional[int] = None, - _critical_options: typing.List[typing.Tuple[bytes, bytes]] = [], - _extensions: typing.List[typing.Tuple[bytes, bytes]] = [], + _valid_before: int | None = None, + _valid_after: int | None = None, + _critical_options: list[tuple[bytes, bytes]] = [], + _extensions: list[tuple[bytes, bytes]] = [], ): self._public_key = _public_key self._serial = _serial @@ -1247,7 +1336,7 @@ class SSHCertificateBuilder: ) def valid_principals( - self, valid_principals: typing.List[bytes] + self, valid_principals: list[bytes] ) -> SSHCertificateBuilder: if self._valid_for_all_principals: raise ValueError( @@ -1304,9 +1393,7 @@ class SSHCertificateBuilder: _extensions=self._extensions, ) - def valid_before( - self, valid_before: typing.Union[int, float] - ) -> SSHCertificateBuilder: + def valid_before(self, valid_before: int | float) -> SSHCertificateBuilder: if not isinstance(valid_before, (int, float)): raise TypeError("valid_before must be an int or float") valid_before = int(valid_before) @@ -1328,9 +1415,7 @@ class SSHCertificateBuilder: _extensions=self._extensions, ) - def valid_after( - self, valid_after: typing.Union[int, float] - ) -> SSHCertificateBuilder: + def valid_after(self, valid_after: int | float) -> SSHCertificateBuilder: if not isinstance(valid_after, (int, float)): raise TypeError("valid_after must be an int or float") valid_after = int(valid_after) @@ -1370,7 +1455,7 @@ class SSHCertificateBuilder: _valid_for_all_principals=self._valid_for_all_principals, _valid_before=self._valid_before, _valid_after=self._valid_after, - _critical_options=self._critical_options + [(name, value)], + _critical_options=[*self._critical_options, (name, value)], _extensions=self._extensions, ) @@ -1393,7 +1478,7 @@ class SSHCertificateBuilder: _valid_before=self._valid_before, _valid_after=self._valid_after, _critical_options=self._critical_options, - _extensions=self._extensions + [(name, value)], + _extensions=[*self._extensions, (name, value)], ) def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc index 6a31640b..48fe5748 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc index 8f0d9e17..89ad1021 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc index a4df6144..0f880205 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/hotp.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/hotp.py index 2067108a..21fb0004 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/hotp.py @@ -11,6 +11,7 @@ from urllib.parse import quote, urlencode from cryptography.hazmat.primitives import constant_time, hmac from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.utils import Buffer HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] @@ -19,8 +20,8 @@ def _generate_uri( hotp: HOTP, type_name: str, account_name: str, - issuer: typing.Optional[str], - extra_parameters: typing.List[typing.Tuple[str, int]], + issuer: str | None, + extra_parameters: list[tuple[str, int]], ) -> str: parameters = [ ("digits", hotp._length), @@ -44,7 +45,7 @@ def _generate_uri( class HOTP: def __init__( self, - key: bytes, + key: Buffer, length: int, algorithm: HOTPHashTypes, backend: typing.Any = None, @@ -67,6 +68,9 @@ class HOTP: self._algorithm = algorithm def generate(self, counter: int) -> bytes: + if not isinstance(counter, int): + raise TypeError("Counter parameter must be an integer type.") + truncated_value = self._dynamic_truncate(counter) hotp = truncated_value % (10**self._length) return "{0:0{1}}".format(hotp, self._length).encode() @@ -77,7 +81,12 @@ class HOTP: def _dynamic_truncate(self, counter: int) -> int: ctx = hmac.HMAC(self._key, self._algorithm) - ctx.update(counter.to_bytes(length=8, byteorder="big")) + + try: + ctx.update(counter.to_bytes(length=8, byteorder="big")) + except OverflowError: + raise ValueError(f"Counter must be between 0 and {2**64 - 1}.") + hmac_value = ctx.finalize() offset = hmac_value[len(hmac_value) - 1] & 0b1111 @@ -85,7 +94,7 @@ class HOTP: return int.from_bytes(p, byteorder="big") & 0x7FFFFFFF def get_provisioning_uri( - self, account_name: str, counter: int, issuer: typing.Optional[str] + self, account_name: str, counter: int, issuer: str | None ) -> str: return _generate_uri( self, "hotp", account_name, issuer, [("counter", int(counter))] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/totp.py b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/totp.py index daddcea2..10c725cc 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/totp.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/twofactor/totp.py @@ -13,12 +13,13 @@ from cryptography.hazmat.primitives.twofactor.hotp import ( HOTPHashTypes, _generate_uri, ) +from cryptography.utils import Buffer class TOTP: def __init__( self, - key: bytes, + key: Buffer, length: int, algorithm: HOTPHashTypes, time_step: int, @@ -30,7 +31,12 @@ class TOTP: key, length, algorithm, enforce_key_length=enforce_key_length ) - def generate(self, time: typing.Union[int, float]) -> bytes: + def generate(self, time: int | float) -> bytes: + if not isinstance(time, (int, float)): + raise TypeError( + "Time parameter must be an integer type or float type." + ) + counter = int(time / self._time_step) return self._hotp.generate(counter) @@ -39,7 +45,7 @@ class TOTP: raise InvalidToken("Supplied TOTP value does not match.") def get_provisioning_uri( - self, account_name: str, issuer: typing.Optional[str] + self, account_name: str, issuer: str | None ) -> str: return _generate_uri( self._hotp, diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/utils.py b/Backend/venv/lib/python3.12/site-packages/cryptography/utils.py index 71916816..a0fc3b1d 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/utils.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/utils.py @@ -9,10 +9,11 @@ import sys import types import typing import warnings +from collections.abc import Callable, Sequence # We use a UserWarning subclass, instead of DeprecationWarning, because CPython -# decided deprecation warnings should be invisble by default. +# decided deprecation warnings should be invisible by default. class CryptographyDeprecationWarning(UserWarning): pass @@ -21,9 +22,20 @@ class CryptographyDeprecationWarning(UserWarning): # ubiquity of their use. They should not be removed until we agree on when that # cycle ends. DeprecatedIn36 = CryptographyDeprecationWarning -DeprecatedIn37 = CryptographyDeprecationWarning DeprecatedIn40 = CryptographyDeprecationWarning DeprecatedIn41 = CryptographyDeprecationWarning +DeprecatedIn42 = CryptographyDeprecationWarning +DeprecatedIn43 = CryptographyDeprecationWarning + + +# If you're wondering why we don't use `Buffer`, it's because `Buffer` would +# be more accurately named: Bufferable. It means something which has an +# `__buffer__`. Which means you can't actually treat the result as a buffer +# (and do things like take a `len()`). +if sys.version_info >= (3, 9): + Buffer = typing.Union[bytes, bytearray, memoryview] +else: + Buffer = typing.ByteString def _check_bytes(name: str, value: bytes) -> None: @@ -31,26 +43,21 @@ def _check_bytes(name: str, value: bytes) -> None: raise TypeError(f"{name} must be bytes") -def _check_byteslike(name: str, value: bytes) -> None: +def _check_byteslike(name: str, value: Buffer) -> None: try: memoryview(value) except TypeError: raise TypeError(f"{name} must be bytes-like") -def int_to_bytes(integer: int, length: typing.Optional[int] = None) -> bytes: +def int_to_bytes(integer: int, length: int | None = None) -> bytes: + if length == 0: + raise ValueError("length argument can't be 0") return integer.to_bytes( length or (integer.bit_length() + 7) // 8 or 1, "big" ) -def _extract_buffer_length(obj: typing.Any) -> typing.Tuple[typing.Any, int]: - from cryptography.hazmat.bindings._rust import _openssl - - buf = _openssl.ffi.from_buffer(obj) - return buf, int(_openssl.ffi.cast("uintptr_t", buf)) - - class InterfaceNotImplemented(Exception): pass @@ -84,16 +91,16 @@ class _ModuleWithDeprecations(types.ModuleType): delattr(self._module, attr) - def __dir__(self) -> typing.Sequence[str]: - return ["_module"] + dir(self._module) + def __dir__(self) -> Sequence[str]: + return ["_module", *dir(self._module)] def deprecated( value: object, module_name: str, message: str, - warning_class: typing.Type[Warning], - name: typing.Optional[str] = None, + warning_class: type[Warning], + name: str | None = None, ) -> _DeprecatedValue: module = sys.modules[module_name] if not isinstance(module, _ModuleWithDeprecations): @@ -105,7 +112,7 @@ def deprecated( return dv -def cached_property(func: typing.Callable) -> property: +def cached_property(func: Callable) -> property: cached_name = f"_cached_{func}" sentinel = object() diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__init__.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__init__.py index d77694a2..318eecc9 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations -from cryptography.x509 import certificate_transparency +from cryptography.x509 import certificate_transparency, verification from cryptography.x509.base import ( Attribute, AttributeNotFound, @@ -30,6 +30,8 @@ from cryptography.x509.base import ( ) from cryptography.x509.extensions import ( AccessDescription, + Admission, + Admissions, AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, @@ -55,6 +57,7 @@ from cryptography.x509.extensions import ( KeyUsage, MSCertificateTemplate, NameConstraints, + NamingAuthority, NoticeReference, OCSPAcceptableResponses, OCSPNoCheck, @@ -63,6 +66,8 @@ from cryptography.x509.extensions import ( PolicyInformation, PrecertificateSignedCertificateTimestamps, PrecertPoison, + PrivateKeyUsagePeriod, + ProfessionInfo, ReasonFlags, SignedCertificateTimestamps, SubjectAlternativeName, @@ -97,6 +102,7 @@ from cryptography.x509.oid import ( ExtensionOID, NameOID, ObjectIdentifier, + PublicKeyAlgorithmOID, SignatureAlgorithmOID, ) @@ -110,6 +116,7 @@ OID_FRESHEST_CRL = ExtensionOID.FRESHEST_CRL OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME OID_KEY_USAGE = ExtensionOID.KEY_USAGE +OID_PRIVATE_KEY_USAGE_PERIOD = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS @@ -170,86 +177,94 @@ OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS OID_OCSP = AuthorityInformationAccessOID.OCSP __all__ = [ - "certificate_transparency", - "load_pem_x509_certificate", - "load_pem_x509_certificates", - "load_der_x509_certificate", - "load_pem_x509_csr", - "load_der_x509_csr", - "load_pem_x509_crl", - "load_der_x509_crl", - "random_serial_number", + "OID_CA_ISSUERS", + "OID_OCSP", + "AccessDescription", + "Admission", + "Admissions", "Attribute", "AttributeNotFound", "Attributes", - "InvalidVersion", - "DeltaCRLIndicator", - "DuplicateExtension", - "ExtensionNotFound", - "UnsupportedGeneralNameType", - "NameAttribute", - "Name", - "RelativeDistinguishedName", - "ObjectIdentifier", - "ExtensionType", - "Extensions", - "Extension", - "ExtendedKeyUsage", - "FreshestCRL", - "IssuingDistributionPoint", - "TLSFeature", - "TLSFeatureType", - "OCSPAcceptableResponses", - "OCSPNoCheck", - "BasicConstraints", - "CRLNumber", - "KeyUsage", "AuthorityInformationAccess", - "SubjectInformationAccess", - "AccessDescription", - "CertificatePolicies", - "PolicyInformation", - "UserNotice", - "NoticeReference", - "SubjectKeyIdentifier", - "NameConstraints", - "CRLDistributionPoints", - "DistributionPoint", - "ReasonFlags", - "InhibitAnyPolicy", - "SubjectAlternativeName", - "IssuerAlternativeName", "AuthorityKeyIdentifier", - "GeneralNames", - "GeneralName", - "RFC822Name", - "DNSName", - "UniformResourceIdentifier", - "RegisteredID", - "DirectoryName", - "IPAddress", - "OtherName", + "BasicConstraints", + "CRLDistributionPoints", + "CRLNumber", + "CRLReason", "Certificate", + "CertificateBuilder", + "CertificateIssuer", + "CertificatePolicies", "CertificateRevocationList", "CertificateRevocationListBuilder", "CertificateSigningRequest", + "CertificateSigningRequestBuilder", + "DNSName", + "DeltaCRLIndicator", + "DirectoryName", + "DistributionPoint", + "DuplicateExtension", + "ExtendedKeyUsage", + "Extension", + "ExtensionNotFound", + "ExtensionType", + "Extensions", + "FreshestCRL", + "GeneralName", + "GeneralNames", + "IPAddress", + "InhibitAnyPolicy", + "InvalidVersion", + "InvalidityDate", + "IssuerAlternativeName", + "IssuingDistributionPoint", + "KeyUsage", + "MSCertificateTemplate", + "Name", + "NameAttribute", + "NameConstraints", + "NameOID", + "NamingAuthority", + "NoticeReference", + "OCSPAcceptableResponses", + "OCSPNoCheck", + "OCSPNonce", + "ObjectIdentifier", + "OtherName", + "PolicyConstraints", + "PolicyInformation", + "PrecertPoison", + "PrecertificateSignedCertificateTimestamps", + "PrivateKeyUsagePeriod", + "ProfessionInfo", + "PublicKeyAlgorithmOID", + "RFC822Name", + "ReasonFlags", + "RegisteredID", + "RelativeDistinguishedName", "RevokedCertificate", "RevokedCertificateBuilder", - "CertificateSigningRequestBuilder", - "CertificateBuilder", - "Version", - "OID_CA_ISSUERS", - "OID_OCSP", - "CertificateIssuer", - "CRLReason", - "InvalidityDate", - "UnrecognizedExtension", - "PolicyConstraints", - "PrecertificateSignedCertificateTimestamps", - "PrecertPoison", - "OCSPNonce", - "SignedCertificateTimestamps", "SignatureAlgorithmOID", - "NameOID", - "MSCertificateTemplate", + "SignedCertificateTimestamps", + "SubjectAlternativeName", + "SubjectInformationAccess", + "SubjectKeyIdentifier", + "TLSFeature", + "TLSFeatureType", + "UniformResourceIdentifier", + "UnrecognizedExtension", + "UnsupportedGeneralNameType", + "UserNotice", + "Version", + "certificate_transparency", + "load_der_x509_certificate", + "load_der_x509_crl", + "load_der_x509_csr", + "load_pem_x509_certificate", + "load_pem_x509_certificates", + "load_pem_x509_crl", + "load_pem_x509_csr", + "random_serial_number", + "verification", + "verification", ] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc index 5588a22a..6d813705 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc index 63eba30f..4b88d6e7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc index 7462f9f7..3d0bddbe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc index c6427c1c..92e5e036 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc index ce969bbf..66ee8b07 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc index c469c22f..45c0d795 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/ocsp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/ocsp.cpython-312.pyc index 0292cc38..7fac15a6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/ocsp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/ocsp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc index 3ed64566..f5267caf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc new file mode 100644 index 00000000..95e235c8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/base.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/base.py index 576385e0..1be612be 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/base.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/base.py @@ -8,10 +8,12 @@ import abc import datetime import os import typing +import warnings +from collections.abc import Iterable from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, @@ -24,7 +26,6 @@ from cryptography.hazmat.primitives.asymmetric import ( ) from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, - CertificateIssuerPublicKeyTypes, CertificatePublicKeyTypes, ) from cryptography.x509.extensions import ( @@ -60,7 +61,7 @@ class AttributeNotFound(Exception): def _reject_duplicate_extension( extension: Extension[ExtensionType], - extensions: typing.List[Extension[ExtensionType]], + extensions: list[Extension[ExtensionType]], ) -> None: # This is quadratic in the number of extensions for e in extensions: @@ -70,9 +71,7 @@ def _reject_duplicate_extension( def _reject_duplicate_attribute( oid: ObjectIdentifier, - attributes: typing.List[ - typing.Tuple[ObjectIdentifier, bytes, typing.Optional[int]] - ], + attributes: list[tuple[ObjectIdentifier, bytes, int | None]], ) -> None: # This is quadratic in the number of attributes for attr_oid, _, _ in attributes: @@ -133,7 +132,7 @@ class Attribute: class Attributes: def __init__( self, - attributes: typing.Iterable[Attribute], + attributes: Iterable[Attribute], ) -> None: self._attributes = list(attributes) @@ -161,145 +160,7 @@ class InvalidVersion(Exception): self.parsed_version = parsed_version -class Certificate(metaclass=abc.ABCMeta): - @abc.abstractmethod - def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: - """ - Returns bytes using digest passed. - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - Returns certificate serial number - """ - - @property - @abc.abstractmethod - def version(self) -> Version: - """ - Returns the certificate version - """ - - @abc.abstractmethod - def public_key(self) -> CertificatePublicKeyTypes: - """ - Returns the public key - """ - - @property - @abc.abstractmethod - def not_valid_before(self) -> datetime.datetime: - """ - Not before time (represented as UTC datetime) - """ - - @property - @abc.abstractmethod - def not_valid_after(self) -> datetime.datetime: - """ - Not after time (represented as UTC datetime) - """ - - @property - @abc.abstractmethod - def issuer(self) -> Name: - """ - Returns the issuer name object. - """ - - @property - @abc.abstractmethod - def subject(self) -> Name: - """ - Returns the subject name object. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> typing.Optional[hashes.HashAlgorithm]: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - in the certificate. - """ - - @property - @abc.abstractmethod - def signature_algorithm_oid(self) -> ObjectIdentifier: - """ - Returns the ObjectIdentifier of the signature algorithm. - """ - - @property - @abc.abstractmethod - def signature_algorithm_parameters( - self, - ) -> typing.Union[None, padding.PSS, padding.PKCS1v15, ec.ECDSA]: - """ - Returns the signature algorithm parameters. - """ - - @property - @abc.abstractmethod - def extensions(self) -> Extensions: - """ - Returns an Extensions object. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature bytes. - """ - - @property - @abc.abstractmethod - def tbs_certificate_bytes(self) -> bytes: - """ - Returns the tbsCertificate payload bytes as defined in RFC 5280. - """ - - @property - @abc.abstractmethod - def tbs_precertificate_bytes(self) -> bytes: - """ - Returns the tbsCertificate payload bytes with the SCT list extension - stripped. - """ - - @abc.abstractmethod - def __eq__(self, other: object) -> bool: - """ - Checks equality. - """ - - @abc.abstractmethod - def __hash__(self) -> int: - """ - Computes a hash. - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the certificate to PEM or DER format. - """ - - @abc.abstractmethod - def verify_directly_issued_by(self, issuer: Certificate) -> None: - """ - This method verifies that certificate issuer name matches the - issuer subject name and that the certificate is signed by the - issuer's private key. No other validation is performed. - """ - - -# Runtime isinstance checks need this since the rust class is not a subclass. -Certificate.register(rust_x509.Certificate) +Certificate = rust_x509.Certificate class RevokedCertificate(metaclass=abc.ABCMeta): @@ -317,6 +178,14 @@ class RevokedCertificate(metaclass=abc.ABCMeta): Returns the date of when this certificate was revoked. """ + @property + @abc.abstractmethod + def revocation_date_utc(self) -> datetime.datetime: + """ + Returns the date of when this certificate was revoked as a non-naive + UTC datetime. + """ + @property @abc.abstractmethod def extensions(self) -> Extensions: @@ -346,290 +215,45 @@ class _RawRevokedCertificate(RevokedCertificate): @property def revocation_date(self) -> datetime.datetime: + warnings.warn( + "Properties that return a naïve datetime object have been " + "deprecated. Please switch to revocation_date_utc.", + utils.DeprecatedIn42, + stacklevel=2, + ) return self._revocation_date + @property + def revocation_date_utc(self) -> datetime.datetime: + return self._revocation_date.replace(tzinfo=datetime.timezone.utc) + @property def extensions(self) -> Extensions: return self._extensions -class CertificateRevocationList(metaclass=abc.ABCMeta): - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the CRL to PEM or DER format. - """ - - @abc.abstractmethod - def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: - """ - Returns bytes using digest passed. - """ - - @abc.abstractmethod - def get_revoked_certificate_by_serial_number( - self, serial_number: int - ) -> typing.Optional[RevokedCertificate]: - """ - Returns an instance of RevokedCertificate or None if the serial_number - is not in the CRL. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> typing.Optional[hashes.HashAlgorithm]: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - in the certificate. - """ - - @property - @abc.abstractmethod - def signature_algorithm_oid(self) -> ObjectIdentifier: - """ - Returns the ObjectIdentifier of the signature algorithm. - """ - - @property - @abc.abstractmethod - def issuer(self) -> Name: - """ - Returns the X509Name with the issuer of this CRL. - """ - - @property - @abc.abstractmethod - def next_update(self) -> typing.Optional[datetime.datetime]: - """ - Returns the date of next update for this CRL. - """ - - @property - @abc.abstractmethod - def last_update(self) -> datetime.datetime: - """ - Returns the date of last update for this CRL. - """ - - @property - @abc.abstractmethod - def extensions(self) -> Extensions: - """ - Returns an Extensions object containing a list of CRL extensions. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature bytes. - """ - - @property - @abc.abstractmethod - def tbs_certlist_bytes(self) -> bytes: - """ - Returns the tbsCertList payload bytes as defined in RFC 5280. - """ - - @abc.abstractmethod - def __eq__(self, other: object) -> bool: - """ - Checks equality. - """ - - @abc.abstractmethod - def __len__(self) -> int: - """ - Number of revoked certificates in the CRL. - """ - - @typing.overload - def __getitem__(self, idx: int) -> RevokedCertificate: - ... - - @typing.overload - def __getitem__(self, idx: slice) -> typing.List[RevokedCertificate]: - ... - - @abc.abstractmethod - def __getitem__( - self, idx: typing.Union[int, slice] - ) -> typing.Union[RevokedCertificate, typing.List[RevokedCertificate]]: - """ - Returns a revoked certificate (or slice of revoked certificates). - """ - - @abc.abstractmethod - def __iter__(self) -> typing.Iterator[RevokedCertificate]: - """ - Iterator over the revoked certificates - """ - - @abc.abstractmethod - def is_signature_valid( - self, public_key: CertificateIssuerPublicKeyTypes - ) -> bool: - """ - Verifies signature of revocation list against given public key. - """ +CertificateRevocationList = rust_x509.CertificateRevocationList +CertificateSigningRequest = rust_x509.CertificateSigningRequest -CertificateRevocationList.register(rust_x509.CertificateRevocationList) +load_pem_x509_certificate = rust_x509.load_pem_x509_certificate +load_der_x509_certificate = rust_x509.load_der_x509_certificate +load_pem_x509_certificates = rust_x509.load_pem_x509_certificates -class CertificateSigningRequest(metaclass=abc.ABCMeta): - @abc.abstractmethod - def __eq__(self, other: object) -> bool: - """ - Checks equality. - """ +load_pem_x509_csr = rust_x509.load_pem_x509_csr +load_der_x509_csr = rust_x509.load_der_x509_csr - @abc.abstractmethod - def __hash__(self) -> int: - """ - Computes a hash. - """ - - @abc.abstractmethod - def public_key(self) -> CertificatePublicKeyTypes: - """ - Returns the public key - """ - - @property - @abc.abstractmethod - def subject(self) -> Name: - """ - Returns the subject name object. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> typing.Optional[hashes.HashAlgorithm]: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - in the certificate. - """ - - @property - @abc.abstractmethod - def signature_algorithm_oid(self) -> ObjectIdentifier: - """ - Returns the ObjectIdentifier of the signature algorithm. - """ - - @property - @abc.abstractmethod - def extensions(self) -> Extensions: - """ - Returns the extensions in the signing request. - """ - - @property - @abc.abstractmethod - def attributes(self) -> Attributes: - """ - Returns an Attributes object. - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Encodes the request to PEM or DER format. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature bytes. - """ - - @property - @abc.abstractmethod - def tbs_certrequest_bytes(self) -> bytes: - """ - Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC - 2986. - """ - - @property - @abc.abstractmethod - def is_signature_valid(self) -> bool: - """ - Verifies signature of signing request. - """ - - @abc.abstractmethod - def get_attribute_for_oid(self, oid: ObjectIdentifier) -> bytes: - """ - Get the attribute value for a given OID. - """ - - -# Runtime isinstance checks need this since the rust class is not a subclass. -CertificateSigningRequest.register(rust_x509.CertificateSigningRequest) - - -# Backend argument preserved for API compatibility, but ignored. -def load_pem_x509_certificate( - data: bytes, backend: typing.Any = None -) -> Certificate: - return rust_x509.load_pem_x509_certificate(data) - - -def load_pem_x509_certificates(data: bytes) -> typing.List[Certificate]: - return rust_x509.load_pem_x509_certificates(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_der_x509_certificate( - data: bytes, backend: typing.Any = None -) -> Certificate: - return rust_x509.load_der_x509_certificate(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_pem_x509_csr( - data: bytes, backend: typing.Any = None -) -> CertificateSigningRequest: - return rust_x509.load_pem_x509_csr(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_der_x509_csr( - data: bytes, backend: typing.Any = None -) -> CertificateSigningRequest: - return rust_x509.load_der_x509_csr(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_pem_x509_crl( - data: bytes, backend: typing.Any = None -) -> CertificateRevocationList: - return rust_x509.load_pem_x509_crl(data) - - -# Backend argument preserved for API compatibility, but ignored. -def load_der_x509_crl( - data: bytes, backend: typing.Any = None -) -> CertificateRevocationList: - return rust_x509.load_der_x509_crl(data) +load_pem_x509_crl = rust_x509.load_pem_x509_crl +load_der_x509_crl = rust_x509.load_der_x509_crl class CertificateSigningRequestBuilder: def __init__( self, - subject_name: typing.Optional[Name] = None, - extensions: typing.List[Extension[ExtensionType]] = [], - attributes: typing.List[ - typing.Tuple[ObjectIdentifier, bytes, typing.Optional[int]] - ] = [], + subject_name: Name | None = None, + extensions: list[Extension[ExtensionType]] = [], + attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [], ): """ Creates an empty X.509 certificate request (v1). @@ -664,7 +288,7 @@ class CertificateSigningRequestBuilder: return CertificateSigningRequestBuilder( self._subject_name, - self._extensions + [extension], + [*self._extensions, extension], self._attributes, ) @@ -673,7 +297,7 @@ class CertificateSigningRequestBuilder: oid: ObjectIdentifier, value: bytes, *, - _tag: typing.Optional[_ASN1Type] = None, + _tag: _ASN1Type | None = None, ) -> CertificateSigningRequestBuilder: """ Adds an X.509 attribute with an OID and associated value. @@ -697,35 +321,57 @@ class CertificateSigningRequestBuilder: return CertificateSigningRequestBuilder( self._subject_name, self._extensions, - self._attributes + [(oid, value, tag)], + [*self._attributes, (oid, value, tag)], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[_AllowedHashTypes], + algorithm: _AllowedHashTypes | None, backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, ) -> CertificateSigningRequest: """ Signs the request using the requestor's private key. """ if self._subject_name is None: raise ValueError("A CertificateSigningRequest must have a subject") - return rust_x509.create_x509_csr(self, private_key, algorithm) + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_csr( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) class CertificateBuilder: - _extensions: typing.List[Extension[ExtensionType]] + _extensions: list[Extension[ExtensionType]] def __init__( self, - issuer_name: typing.Optional[Name] = None, - subject_name: typing.Optional[Name] = None, - public_key: typing.Optional[CertificatePublicKeyTypes] = None, - serial_number: typing.Optional[int] = None, - not_valid_before: typing.Optional[datetime.datetime] = None, - not_valid_after: typing.Optional[datetime.datetime] = None, - extensions: typing.List[Extension[ExtensionType]] = [], + issuer_name: Name | None = None, + subject_name: Name | None = None, + public_key: CertificatePublicKeyTypes | None = None, + serial_number: int | None = None, + not_valid_before: datetime.datetime | None = None, + not_valid_after: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], ) -> None: self._version = Version.v3 self._issuer_name = issuer_name @@ -824,7 +470,7 @@ class CertificateBuilder: # zero. if number.bit_length() >= 160: # As defined in RFC 5280 raise ValueError( - "The serial number should not be more than 159 " "bits." + "The serial number should not be more than 159 bits." ) return CertificateBuilder( self._issuer_name, @@ -876,8 +522,7 @@ class CertificateBuilder: time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( - "The not valid after date must be on or after" - " 1950 January 1." + "The not valid after date must be on or after 1950 January 1." ) if ( self._not_valid_before is not None @@ -916,18 +561,17 @@ class CertificateBuilder: self._serial_number, self._not_valid_before, self._not_valid_after, - self._extensions + [extension], + [*self._extensions, extension], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[_AllowedHashTypes], + algorithm: _AllowedHashTypes | None, backend: typing.Any = None, *, - rsa_padding: typing.Optional[ - typing.Union[padding.PSS, padding.PKCS1v15] - ] = None, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, ) -> Certificate: """ Signs the certificate using the CA's private key. @@ -956,22 +600,32 @@ class CertificateBuilder: if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Padding is only supported for RSA keys") + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + return rust_x509.create_x509_certificate( - self, private_key, algorithm, rsa_padding + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, ) class CertificateRevocationListBuilder: - _extensions: typing.List[Extension[ExtensionType]] - _revoked_certificates: typing.List[RevokedCertificate] + _extensions: list[Extension[ExtensionType]] + _revoked_certificates: list[RevokedCertificate] def __init__( self, - issuer_name: typing.Optional[Name] = None, - last_update: typing.Optional[datetime.datetime] = None, - next_update: typing.Optional[datetime.datetime] = None, - extensions: typing.List[Extension[ExtensionType]] = [], - revoked_certificates: typing.List[RevokedCertificate] = [], + issuer_name: Name | None = None, + last_update: datetime.datetime | None = None, + next_update: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + revoked_certificates: list[RevokedCertificate] = [], ): self._issuer_name = issuer_name self._last_update = last_update @@ -1004,7 +658,7 @@ class CertificateRevocationListBuilder: last_update = _convert_to_naive_utc_time(last_update) if last_update < _EARLIEST_UTC_TIME: raise ValueError( - "The last update date must be on or after" " 1950 January 1." + "The last update date must be on or after 1950 January 1." ) if self._next_update is not None and last_update > self._next_update: raise ValueError( @@ -1028,7 +682,7 @@ class CertificateRevocationListBuilder: next_update = _convert_to_naive_utc_time(next_update) if next_update < _EARLIEST_UTC_TIME: raise ValueError( - "The last update date must be on or after" " 1950 January 1." + "The last update date must be on or after 1950 January 1." ) if self._last_update is not None and next_update < self._last_update: raise ValueError( @@ -1057,7 +711,7 @@ class CertificateRevocationListBuilder: self._issuer_name, self._last_update, self._next_update, - self._extensions + [extension], + [*self._extensions, extension], self._revoked_certificates, ) @@ -1075,14 +729,17 @@ class CertificateRevocationListBuilder: self._last_update, self._next_update, self._extensions, - self._revoked_certificates + [revoked_certificate], + [*self._revoked_certificates, revoked_certificate], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[_AllowedHashTypes], + algorithm: _AllowedHashTypes | None, backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, ) -> CertificateRevocationList: if self._issuer_name is None: raise ValueError("A CRL must have an issuer name") @@ -1093,15 +750,33 @@ class CertificateRevocationListBuilder: if self._next_update is None: raise ValueError("A CRL must have a next update time") - return rust_x509.create_x509_crl(self, private_key, algorithm) + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_crl( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) class RevokedCertificateBuilder: def __init__( self, - serial_number: typing.Optional[int] = None, - revocation_date: typing.Optional[datetime.datetime] = None, - extensions: typing.List[Extension[ExtensionType]] = [], + serial_number: int | None = None, + revocation_date: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], ): self._serial_number = serial_number self._revocation_date = revocation_date @@ -1119,7 +794,7 @@ class RevokedCertificateBuilder: # zero. if number.bit_length() >= 160: # As defined in RFC 5280 raise ValueError( - "The serial number should not be more than 159 " "bits." + "The serial number should not be more than 159 bits." ) return RevokedCertificateBuilder( number, self._revocation_date, self._extensions @@ -1135,7 +810,7 @@ class RevokedCertificateBuilder: time = _convert_to_naive_utc_time(time) if time < _EARLIEST_UTC_TIME: raise ValueError( - "The revocation date must be on or after" " 1950 January 1." + "The revocation date must be on or after 1950 January 1." ) return RevokedCertificateBuilder( self._serial_number, time, self._extensions @@ -1152,7 +827,7 @@ class RevokedCertificateBuilder: return RevokedCertificateBuilder( self._serial_number, self._revocation_date, - self._extensions + [extension], + [*self._extensions, extension], ) def build(self, backend: typing.Any = None) -> RevokedCertificate: diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/certificate_transparency.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/certificate_transparency.py index 73647ee7..fb66cc60 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/certificate_transparency.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/certificate_transparency.py @@ -4,12 +4,8 @@ from __future__ import annotations -import abc -import datetime - from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 -from cryptography.hazmat.primitives.hashes import HashAlgorithm class LogEntryType(utils.Enum): @@ -36,62 +32,4 @@ class SignatureAlgorithm(utils.Enum): ECDSA = 3 -class SignedCertificateTimestamp(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def version(self) -> Version: - """ - Returns the SCT version. - """ - - @property - @abc.abstractmethod - def log_id(self) -> bytes: - """ - Returns an identifier indicating which log this SCT is for. - """ - - @property - @abc.abstractmethod - def timestamp(self) -> datetime.datetime: - """ - Returns the timestamp for this SCT. - """ - - @property - @abc.abstractmethod - def entry_type(self) -> LogEntryType: - """ - Returns whether this is an SCT for a certificate or pre-certificate. - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm(self) -> HashAlgorithm: - """ - Returns the hash algorithm used for the SCT's signature. - """ - - @property - @abc.abstractmethod - def signature_algorithm(self) -> SignatureAlgorithm: - """ - Returns the signing algorithm used for the SCT's signature. - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - Returns the signature for this SCT. - """ - - @property - @abc.abstractmethod - def extension_bytes(self) -> bytes: - """ - Returns the raw bytes of any extensions for this SCT. - """ - - -SignedCertificateTimestamp.register(rust_x509.Sct) +SignedCertificateTimestamp = rust_x509.Sct diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/extensions.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/extensions.py index ac99592f..dfa472d3 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/extensions.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/extensions.py @@ -9,6 +9,7 @@ import datetime import hashlib import ipaddress import typing +from collections.abc import Iterable, Iterator from cryptography import utils from cryptography.hazmat.bindings._rust import asn1 @@ -104,16 +105,12 @@ class ExtensionType(metaclass=abc.ABCMeta): Serializes the extension type to DER. """ raise NotImplementedError( - "public_bytes is not implemented for extension type {!r}".format( - self - ) + f"public_bytes is not implemented for extension type {self!r}" ) class Extensions: - def __init__( - self, extensions: typing.Iterable[Extension[ExtensionType]] - ) -> None: + def __init__(self, extensions: Iterable[Extension[ExtensionType]]) -> None: self._extensions = list(extensions) def get_extension_for_oid( @@ -126,7 +123,7 @@ class Extensions: raise ExtensionNotFound(f"No {oid} extension was found", oid) def get_extension_for_class( - self, extclass: typing.Type[ExtensionTypeVar] + self, extclass: type[ExtensionTypeVar] ) -> Extension[ExtensionTypeVar]: if extclass is UnrecognizedExtension: raise TypeError( @@ -183,9 +180,9 @@ class AuthorityKeyIdentifier(ExtensionType): def __init__( self, - key_identifier: typing.Optional[bytes], - authority_cert_issuer: typing.Optional[typing.Iterable[GeneralName]], - authority_cert_serial_number: typing.Optional[int], + key_identifier: bytes | None, + authority_cert_issuer: Iterable[GeneralName] | None, + authority_cert_serial_number: int | None, ) -> None: if (authority_cert_issuer is None) != ( authority_cert_serial_number is None @@ -242,10 +239,10 @@ class AuthorityKeyIdentifier(ExtensionType): def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -269,17 +266,17 @@ class AuthorityKeyIdentifier(ExtensionType): ) @property - def key_identifier(self) -> typing.Optional[bytes]: + def key_identifier(self) -> bytes | None: return self._key_identifier @property def authority_cert_issuer( self, - ) -> typing.Optional[typing.List[GeneralName]]: + ) -> list[GeneralName] | None: return self._authority_cert_issuer @property - def authority_cert_serial_number(self) -> typing.Optional[int]: + def authority_cert_serial_number(self) -> int | None: return self._authority_cert_serial_number def public_bytes(self) -> bytes: @@ -325,9 +322,7 @@ class SubjectKeyIdentifier(ExtensionType): class AuthorityInformationAccess(ExtensionType): oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS - def __init__( - self, descriptions: typing.Iterable[AccessDescription] - ) -> None: + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -358,9 +353,7 @@ class AuthorityInformationAccess(ExtensionType): class SubjectInformationAccess(ExtensionType): oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS - def __init__( - self, descriptions: typing.Iterable[AccessDescription] - ) -> None: + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: descriptions = list(descriptions) if not all(isinstance(x, AccessDescription) for x in descriptions): raise TypeError( @@ -403,8 +396,8 @@ class AccessDescription: def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -431,7 +424,7 @@ class AccessDescription: class BasicConstraints(ExtensionType): oid = ExtensionOID.BASIC_CONSTRAINTS - def __init__(self, ca: bool, path_length: typing.Optional[int]) -> None: + def __init__(self, ca: bool, path_length: int | None) -> None: if not isinstance(ca, bool): raise TypeError("ca must be a boolean value") @@ -453,13 +446,13 @@ class BasicConstraints(ExtensionType): return self._ca @property - def path_length(self) -> typing.Optional[int]: + def path_length(self) -> int | None: return self._path_length def __repr__(self) -> str: return ( - "" - ).format(self) + f"" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, BasicConstraints): @@ -507,7 +500,7 @@ class CRLDistributionPoints(ExtensionType): oid = ExtensionOID.CRL_DISTRIBUTION_POINTS def __init__( - self, distribution_points: typing.Iterable[DistributionPoint] + self, distribution_points: Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( @@ -544,7 +537,7 @@ class FreshestCRL(ExtensionType): oid = ExtensionOID.FRESHEST_CRL def __init__( - self, distribution_points: typing.Iterable[DistributionPoint] + self, distribution_points: Iterable[DistributionPoint] ) -> None: distribution_points = list(distribution_points) if not all( @@ -580,10 +573,10 @@ class FreshestCRL(ExtensionType): class DistributionPoint: def __init__( self, - full_name: typing.Optional[typing.Iterable[GeneralName]], - relative_name: typing.Optional[RelativeDistinguishedName], - reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], - crl_issuer: typing.Optional[typing.Iterable[GeneralName]], + full_name: Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, + reasons: frozenset[ReasonFlags] | None, + crl_issuer: Iterable[GeneralName] | None, ) -> None: if full_name and relative_name: raise ValueError( @@ -656,35 +649,31 @@ class DistributionPoint: def __hash__(self) -> int: if self.full_name is not None: - fn: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( - self.full_name - ) + fn: tuple[GeneralName, ...] | None = tuple(self.full_name) else: fn = None if self.crl_issuer is not None: - crl_issuer: typing.Optional[ - typing.Tuple[GeneralName, ...] - ] = tuple(self.crl_issuer) + crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer) else: crl_issuer = None return hash((fn, self.relative_name, self.reasons, crl_issuer)) @property - def full_name(self) -> typing.Optional[typing.List[GeneralName]]: + def full_name(self) -> list[GeneralName] | None: return self._full_name @property - def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: + def relative_name(self) -> RelativeDistinguishedName | None: return self._relative_name @property - def reasons(self) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: + def reasons(self) -> frozenset[ReasonFlags] | None: return self._reasons @property - def crl_issuer(self) -> typing.Optional[typing.List[GeneralName]]: + def crl_issuer(self) -> list[GeneralName] | None: return self._crl_issuer @@ -735,14 +724,39 @@ _CRLREASONFLAGS = { ReasonFlags.aa_compromise: 8, } +# CRLReason ::= ENUMERATED { +# unspecified (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# -- value 7 is not used +# removeFromCRL (8), +# privilegeWithdrawn (9), +# aACompromise (10) } +_CRL_ENTRY_REASON_ENUM_TO_CODE = { + ReasonFlags.unspecified: 0, + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.remove_from_crl: 8, + ReasonFlags.privilege_withdrawn: 9, + ReasonFlags.aa_compromise: 10, +} + class PolicyConstraints(ExtensionType): oid = ExtensionOID.POLICY_CONSTRAINTS def __init__( self, - require_explicit_policy: typing.Optional[int], - inhibit_policy_mapping: typing.Optional[int], + require_explicit_policy: int | None, + inhibit_policy_mapping: int | None, ) -> None: if require_explicit_policy is not None and not isinstance( require_explicit_policy, int @@ -790,11 +804,11 @@ class PolicyConstraints(ExtensionType): ) @property - def require_explicit_policy(self) -> typing.Optional[int]: + def require_explicit_policy(self) -> int | None: return self._require_explicit_policy @property - def inhibit_policy_mapping(self) -> typing.Optional[int]: + def inhibit_policy_mapping(self) -> int | None: return self._inhibit_policy_mapping def public_bytes(self) -> bytes: @@ -804,12 +818,11 @@ class PolicyConstraints(ExtensionType): class CertificatePolicies(ExtensionType): oid = ExtensionOID.CERTIFICATE_POLICIES - def __init__(self, policies: typing.Iterable[PolicyInformation]) -> None: + def __init__(self, policies: Iterable[PolicyInformation]) -> None: policies = list(policies) if not all(isinstance(x, PolicyInformation) for x in policies): raise TypeError( - "Every item in the policies list must be a " - "PolicyInformation" + "Every item in the policies list must be a PolicyInformation" ) self._policies = policies @@ -836,9 +849,7 @@ class PolicyInformation: def __init__( self, policy_identifier: ObjectIdentifier, - policy_qualifiers: typing.Optional[ - typing.Iterable[typing.Union[str, UserNotice]] - ], + policy_qualifiers: Iterable[str | UserNotice] | None, ) -> None: if not isinstance(policy_identifier, ObjectIdentifier): raise TypeError("policy_identifier must be an ObjectIdentifier") @@ -859,8 +870,8 @@ class PolicyInformation: def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -874,9 +885,7 @@ class PolicyInformation: def __hash__(self) -> int: if self.policy_qualifiers is not None: - pq: typing.Optional[ - typing.Tuple[typing.Union[str, UserNotice], ...] - ] = tuple(self.policy_qualifiers) + pq = tuple(self.policy_qualifiers) else: pq = None @@ -889,15 +898,15 @@ class PolicyInformation: @property def policy_qualifiers( self, - ) -> typing.Optional[typing.List[typing.Union[str, UserNotice]]]: + ) -> list[str | UserNotice] | None: return self._policy_qualifiers class UserNotice: def __init__( self, - notice_reference: typing.Optional[NoticeReference], - explicit_text: typing.Optional[str], + notice_reference: NoticeReference | None, + explicit_text: str | None, ) -> None: if notice_reference and not isinstance( notice_reference, NoticeReference @@ -911,8 +920,8 @@ class UserNotice: def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -928,19 +937,19 @@ class UserNotice: return hash((self.notice_reference, self.explicit_text)) @property - def notice_reference(self) -> typing.Optional[NoticeReference]: + def notice_reference(self) -> NoticeReference | None: return self._notice_reference @property - def explicit_text(self) -> typing.Optional[str]: + def explicit_text(self) -> str | None: return self._explicit_text class NoticeReference: def __init__( self, - organization: typing.Optional[str], - notice_numbers: typing.Iterable[int], + organization: str | None, + notice_numbers: Iterable[int], ) -> None: self._organization = organization notice_numbers = list(notice_numbers) @@ -951,8 +960,8 @@ class NoticeReference: def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __eq__(self, other: object) -> bool: @@ -968,18 +977,18 @@ class NoticeReference: return hash((self.organization, tuple(self.notice_numbers))) @property - def organization(self) -> typing.Optional[str]: + def organization(self) -> str | None: return self._organization @property - def notice_numbers(self) -> typing.List[int]: + def notice_numbers(self) -> list[int]: return self._notice_numbers class ExtendedKeyUsage(ExtensionType): oid = ExtensionOID.EXTENDED_KEY_USAGE - def __init__(self, usages: typing.Iterable[ObjectIdentifier]) -> None: + def __init__(self, usages: Iterable[ObjectIdentifier]) -> None: usages = list(usages) if not all(isinstance(x, ObjectIdentifier) for x in usages): raise TypeError( @@ -1047,7 +1056,7 @@ class PrecertPoison(ExtensionType): class TLSFeature(ExtensionType): oid = ExtensionOID.TLS_FEATURE - def __init__(self, features: typing.Iterable[TLSFeatureType]) -> None: + def __init__(self, features: Iterable[TLSFeatureType]) -> None: features = list(features) if ( not all(isinstance(x, TLSFeatureType) for x in features) @@ -1213,14 +1222,14 @@ class KeyUsage(ExtensionType): decipher_only = False return ( - "" - ).format(self, encipher_only, decipher_only) + f"" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, KeyUsage): @@ -1257,13 +1266,78 @@ class KeyUsage(ExtensionType): return rust_x509.encode_extension_value(self) +class PrivateKeyUsagePeriod(ExtensionType): + oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD + + def __init__( + self, + not_before: datetime.datetime | None, + not_after: datetime.datetime | None, + ) -> None: + if ( + not isinstance(not_before, datetime.datetime) + and not_before is not None + ): + raise TypeError("not_before must be a datetime.datetime or None") + + if ( + not isinstance(not_after, datetime.datetime) + and not_after is not None + ): + raise TypeError("not_after must be a datetime.datetime or None") + + if not_before is None and not_after is None: + raise ValueError( + "At least one of not_before and not_after must not be None" + ) + + if ( + not_before is not None + and not_after is not None + and not_before > not_after + ): + raise ValueError("not_before must be before not_after") + + self._not_before = not_before + self._not_after = not_after + + @property + def not_before(self) -> datetime.datetime | None: + return self._not_before + + @property + def not_after(self) -> datetime.datetime | None: + return self._not_after + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrivateKeyUsagePeriod): + return NotImplemented + + return ( + self.not_before == other.not_before + and self.not_after == other.not_after + ) + + def __hash__(self) -> int: + return hash((self.not_before, self.not_after)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + class NameConstraints(ExtensionType): oid = ExtensionOID.NAME_CONSTRAINTS def __init__( self, - permitted_subtrees: typing.Optional[typing.Iterable[GeneralName]], - excluded_subtrees: typing.Optional[typing.Iterable[GeneralName]], + permitted_subtrees: Iterable[GeneralName] | None, + excluded_subtrees: Iterable[GeneralName] | None, ) -> None: if permitted_subtrees is not None: permitted_subtrees = list(permitted_subtrees) @@ -1311,11 +1385,11 @@ class NameConstraints(ExtensionType): and self.permitted_subtrees == other.permitted_subtrees ) - def _validate_tree(self, tree: typing.Iterable[GeneralName]) -> None: + def _validate_tree(self, tree: Iterable[GeneralName]) -> None: self._validate_ip_name(tree) self._validate_dns_name(tree) - def _validate_ip_name(self, tree: typing.Iterable[GeneralName]) -> None: + def _validate_ip_name(self, tree: Iterable[GeneralName]) -> None: if any( isinstance(name, IPAddress) and not isinstance( @@ -1328,7 +1402,7 @@ class NameConstraints(ExtensionType): " IPv6Network object" ) - def _validate_dns_name(self, tree: typing.Iterable[GeneralName]) -> None: + def _validate_dns_name(self, tree: Iterable[GeneralName]) -> None: if any( isinstance(name, DNSName) and "*" in name.value for name in tree ): @@ -1339,22 +1413,18 @@ class NameConstraints(ExtensionType): def __repr__(self) -> str: return ( - "".format(self) + f"" ) def __hash__(self) -> int: if self.permitted_subtrees is not None: - ps: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( - self.permitted_subtrees - ) + ps: tuple[GeneralName, ...] | None = tuple(self.permitted_subtrees) else: ps = None if self.excluded_subtrees is not None: - es: typing.Optional[typing.Tuple[GeneralName, ...]] = tuple( - self.excluded_subtrees - ) + es: tuple[GeneralName, ...] | None = tuple(self.excluded_subtrees) else: es = None @@ -1363,13 +1433,13 @@ class NameConstraints(ExtensionType): @property def permitted_subtrees( self, - ) -> typing.Optional[typing.List[GeneralName]]: + ) -> list[GeneralName] | None: return self._permitted_subtrees @property def excluded_subtrees( self, - ) -> typing.Optional[typing.List[GeneralName]]: + ) -> list[GeneralName] | None: return self._excluded_subtrees def public_bytes(self) -> bytes: @@ -1406,9 +1476,9 @@ class Extension(typing.Generic[ExtensionTypeVar]): def __repr__(self) -> str: return ( - "" - ).format(self) + f"" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, Extension): @@ -1425,7 +1495,7 @@ class Extension(typing.Generic[ExtensionTypeVar]): class GeneralNames: - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: general_names = list(general_names) if not all(isinstance(x, GeneralName) for x in general_names): raise TypeError( @@ -1440,58 +1510,49 @@ class GeneralNames: @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: - ... + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: - ... + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: - ... + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: - ... + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: - ... + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): # Return the value of each GeneralName, except for OtherName instances # which we return directly because it has two important properties not # just one value. @@ -1516,7 +1577,7 @@ class GeneralNames: class SubjectAlternativeName(ExtensionType): oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @@ -1524,58 +1585,49 @@ class SubjectAlternativeName(ExtensionType): @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: - ... + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: - ... + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: - ... + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: - ... + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: - ... + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: @@ -1597,7 +1649,7 @@ class SubjectAlternativeName(ExtensionType): class IssuerAlternativeName(ExtensionType): oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @@ -1605,58 +1657,49 @@ class IssuerAlternativeName(ExtensionType): @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: - ... + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: - ... + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: - ... + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: - ... + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: - ... + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: @@ -1678,7 +1721,7 @@ class IssuerAlternativeName(ExtensionType): class CertificateIssuer(ExtensionType): oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER - def __init__(self, general_names: typing.Iterable[GeneralName]) -> None: + def __init__(self, general_names: Iterable[GeneralName]) -> None: self._general_names = GeneralNames(general_names) __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") @@ -1686,58 +1729,49 @@ class CertificateIssuer(ExtensionType): @typing.overload def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[UniformResourceIdentifier], - typing.Type[RFC822Name], - ], - ) -> typing.List[str]: - ... + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[DirectoryName], - ) -> typing.List[Name]: - ... + type: type[DirectoryName], + ) -> list[Name]: ... @typing.overload def get_values_for_type( self, - type: typing.Type[RegisteredID], - ) -> typing.List[ObjectIdentifier]: - ... + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[IPAddress] - ) -> typing.List[_IPAddressTypes]: - ... + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... @typing.overload def get_values_for_type( - self, type: typing.Type[OtherName] - ) -> typing.List[OtherName]: - ... + self, type: type[OtherName] + ) -> list[OtherName]: ... def get_values_for_type( self, - type: typing.Union[ - typing.Type[DNSName], - typing.Type[DirectoryName], - typing.Type[IPAddress], - typing.Type[OtherName], - typing.Type[RFC822Name], - typing.Type[RegisteredID], - typing.Type[UniformResourceIdentifier], - ], - ) -> typing.Union[ - typing.List[_IPAddressTypes], - typing.List[str], - typing.List[OtherName], - typing.List[Name], - typing.List[ObjectIdentifier], - ]: + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): return self._general_names.get_values_for_type(type) def __repr__(self) -> str: @@ -1795,9 +1829,7 @@ class InvalidityDate(ExtensionType): self._invalidity_date = invalidity_date def __repr__(self) -> str: - return "".format( - self._invalidity_date - ) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, InvalidityDate): @@ -1812,6 +1844,13 @@ class InvalidityDate(ExtensionType): def invalidity_date(self) -> datetime.datetime: return self._invalidity_date + @property + def invalidity_date_utc(self) -> datetime.datetime: + if self._invalidity_date.tzinfo is None: + return self._invalidity_date.replace(tzinfo=datetime.timezone.utc) + else: + return self._invalidity_date.astimezone(tz=datetime.timezone.utc) + def public_bytes(self) -> bytes: return rust_x509.encode_extension_value(self) @@ -1821,9 +1860,7 @@ class PrecertificateSignedCertificateTimestamps(ExtensionType): def __init__( self, - signed_certificate_timestamps: typing.Iterable[ - SignedCertificateTimestamp - ], + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( @@ -1841,9 +1878,7 @@ class PrecertificateSignedCertificateTimestamps(ExtensionType): ) def __repr__(self) -> str: - return "".format( - list(self) - ) + return f"" def __hash__(self) -> int: return hash(tuple(self._signed_certificate_timestamps)) @@ -1866,9 +1901,7 @@ class SignedCertificateTimestamps(ExtensionType): def __init__( self, - signed_certificate_timestamps: typing.Iterable[ - SignedCertificateTimestamp - ], + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], ) -> None: signed_certificate_timestamps = list(signed_certificate_timestamps) if not all( @@ -1936,7 +1969,7 @@ class OCSPNonce(ExtensionType): class OCSPAcceptableResponses(ExtensionType): oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES - def __init__(self, responses: typing.Iterable[ObjectIdentifier]) -> None: + def __init__(self, responses: Iterable[ObjectIdentifier]) -> None: responses = list(responses) if any(not isinstance(r, ObjectIdentifier) for r in responses): raise TypeError("All responses must be ObjectIdentifiers") @@ -1955,7 +1988,7 @@ class OCSPAcceptableResponses(ExtensionType): def __repr__(self) -> str: return f"" - def __iter__(self) -> typing.Iterator[ObjectIdentifier]: + def __iter__(self) -> Iterator[ObjectIdentifier]: return iter(self._responses) def public_bytes(self) -> bytes: @@ -1967,11 +2000,11 @@ class IssuingDistributionPoint(ExtensionType): def __init__( self, - full_name: typing.Optional[typing.Iterable[GeneralName]], - relative_name: typing.Optional[RelativeDistinguishedName], + full_name: Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, only_contains_user_certs: bool, only_contains_ca_certs: bool, - only_some_reasons: typing.Optional[typing.FrozenSet[ReasonFlags]], + only_some_reasons: frozenset[ReasonFlags] | None, indirect_crl: bool, only_contains_attribute_certs: bool, ) -> None: @@ -2007,10 +2040,12 @@ class IssuingDistributionPoint(ExtensionType): "must all be boolean." ) + # Per RFC5280 Section 5.2.5, the Issuing Distribution Point extension + # in a CRL can have only one of onlyContainsUserCerts, + # onlyContainsCACerts, onlyContainsAttributeCerts set to TRUE. crl_constraints = [ only_contains_user_certs, only_contains_ca_certs, - indirect_crl, only_contains_attribute_certs, ] @@ -2018,7 +2053,7 @@ class IssuingDistributionPoint(ExtensionType): raise ValueError( "Only one of the following can be set to True: " "only_contains_user_certs, only_contains_ca_certs, " - "indirect_crl, only_contains_attribute_certs" + "only_contains_attribute_certs" ) if not any( @@ -2050,14 +2085,14 @@ class IssuingDistributionPoint(ExtensionType): def __repr__(self) -> str: return ( - "".format(self) + f"{self.only_contains_attribute_certs})>" ) def __eq__(self, other: object) -> bool: @@ -2089,11 +2124,11 @@ class IssuingDistributionPoint(ExtensionType): ) @property - def full_name(self) -> typing.Optional[typing.List[GeneralName]]: + def full_name(self) -> list[GeneralName] | None: return self._full_name @property - def relative_name(self) -> typing.Optional[RelativeDistinguishedName]: + def relative_name(self) -> RelativeDistinguishedName | None: return self._relative_name @property @@ -2107,7 +2142,7 @@ class IssuingDistributionPoint(ExtensionType): @property def only_some_reasons( self, - ) -> typing.Optional[typing.FrozenSet[ReasonFlags]]: + ) -> frozenset[ReasonFlags] | None: return self._only_some_reasons @property @@ -2128,8 +2163,8 @@ class MSCertificateTemplate(ExtensionType): def __init__( self, template_id: ObjectIdentifier, - major_version: typing.Optional[int], - minor_version: typing.Optional[int], + major_version: int | None, + minor_version: int | None, ) -> None: if not isinstance(template_id, ObjectIdentifier): raise TypeError("oid must be an ObjectIdentifier") @@ -2150,11 +2185,11 @@ class MSCertificateTemplate(ExtensionType): return self._template_id @property - def major_version(self) -> typing.Optional[int]: + def major_version(self) -> int | None: return self._major_version @property - def minor_version(self) -> typing.Optional[int]: + def minor_version(self) -> int | None: return self._minor_version def __repr__(self) -> str: @@ -2181,6 +2216,287 @@ class MSCertificateTemplate(ExtensionType): return rust_x509.encode_extension_value(self) +class NamingAuthority: + def __init__( + self, + id: ObjectIdentifier | None, + url: str | None, + text: str | None, + ) -> None: + if id is not None and not isinstance(id, ObjectIdentifier): + raise TypeError("id must be an ObjectIdentifier") + + if url is not None and not isinstance(url, str): + raise TypeError("url must be a str") + + if text is not None and not isinstance(text, str): + raise TypeError("text must be a str") + + self._id = id + self._url = url + self._text = text + + @property + def id(self) -> ObjectIdentifier | None: + return self._id + + @property + def url(self) -> str | None: + return self._url + + @property + def text(self) -> str | None: + return self._text + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamingAuthority): + return NotImplemented + + return ( + self.id == other.id + and self.url == other.url + and self.text == other.text + ) + + def __hash__(self) -> int: + return hash( + ( + self.id, + self.url, + self.text, + ) + ) + + +class ProfessionInfo: + def __init__( + self, + naming_authority: NamingAuthority | None, + profession_items: Iterable[str], + profession_oids: Iterable[ObjectIdentifier] | None, + registration_number: str | None, + add_profession_info: bytes | None, + ) -> None: + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_items = list(profession_items) + if not all(isinstance(item, str) for item in profession_items): + raise TypeError( + "Every item in the profession_items list must be a str" + ) + + if profession_oids is not None: + profession_oids = list(profession_oids) + if not all( + isinstance(oid, ObjectIdentifier) for oid in profession_oids + ): + raise TypeError( + "Every item in the profession_oids list must be an " + "ObjectIdentifier" + ) + + if registration_number is not None and not isinstance( + registration_number, str + ): + raise TypeError("registration_number must be a str") + + if add_profession_info is not None and not isinstance( + add_profession_info, bytes + ): + raise TypeError("add_profession_info must be bytes") + + self._naming_authority = naming_authority + self._profession_items = profession_items + self._profession_oids = profession_oids + self._registration_number = registration_number + self._add_profession_info = add_profession_info + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_items(self) -> list[str]: + return self._profession_items + + @property + def profession_oids(self) -> list[ObjectIdentifier] | None: + return self._profession_oids + + @property + def registration_number(self) -> str | None: + return self._registration_number + + @property + def add_profession_info(self) -> bytes | None: + return self._add_profession_info + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ProfessionInfo): + return NotImplemented + + return ( + self.naming_authority == other.naming_authority + and self.profession_items == other.profession_items + and self.profession_oids == other.profession_oids + and self.registration_number == other.registration_number + and self.add_profession_info == other.add_profession_info + ) + + def __hash__(self) -> int: + if self.profession_oids is not None: + profession_oids = tuple(self.profession_oids) + else: + profession_oids = None + return hash( + ( + self.naming_authority, + tuple(self.profession_items), + profession_oids, + self.registration_number, + self.add_profession_info, + ) + ) + + +class Admission: + def __init__( + self, + admission_authority: GeneralName | None, + naming_authority: NamingAuthority | None, + profession_infos: Iterable[ProfessionInfo], + ) -> None: + if admission_authority is not None and not isinstance( + admission_authority, GeneralName + ): + raise TypeError("admission_authority must be a GeneralName") + + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_infos = list(profession_infos) + if not all( + isinstance(info, ProfessionInfo) for info in profession_infos + ): + raise TypeError( + "Every item in the profession_infos list must be a " + "ProfessionInfo" + ) + + self._admission_authority = admission_authority + self._naming_authority = naming_authority + self._profession_infos = profession_infos + + @property + def admission_authority(self) -> GeneralName | None: + return self._admission_authority + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_infos(self) -> list[ProfessionInfo]: + return self._profession_infos + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admission): + return NotImplemented + + return ( + self.admission_authority == other.admission_authority + and self.naming_authority == other.naming_authority + and self.profession_infos == other.profession_infos + ) + + def __hash__(self) -> int: + return hash( + ( + self.admission_authority, + self.naming_authority, + tuple(self.profession_infos), + ) + ) + + +class Admissions(ExtensionType): + oid = ExtensionOID.ADMISSIONS + + def __init__( + self, + authority: GeneralName | None, + admissions: Iterable[Admission], + ) -> None: + if authority is not None and not isinstance(authority, GeneralName): + raise TypeError("authority must be a GeneralName") + + admissions = list(admissions) + if not all( + isinstance(admission, Admission) for admission in admissions + ): + raise TypeError( + "Every item in the contents_of_admissions list must be an " + "Admission" + ) + + self._authority = authority + self._admissions = admissions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_admissions") + + @property + def authority(self) -> GeneralName | None: + return self._authority + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admissions): + return NotImplemented + + return ( + self.authority == other.authority + and self._admissions == other._admissions + ) + + def __hash__(self) -> int: + return hash((self.authority, tuple(self._admissions))) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + class UnrecognizedExtension(ExtensionType): def __init__(self, oid: ObjectIdentifier, value: bytes) -> None: if not isinstance(oid, ObjectIdentifier): @@ -2197,10 +2513,7 @@ class UnrecognizedExtension(ExtensionType): return self._value def __repr__(self) -> str: - return ( - "".format(self) - ) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, UnrecognizedExtension): diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/general_name.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/general_name.py index 79271afb..672f2875 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/general_name.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/general_name.py @@ -269,9 +269,7 @@ class OtherName(GeneralName): return self._value def __repr__(self) -> str: - return "".format( - self.type_id, self.value - ) + return f"" def __eq__(self, other: object) -> bool: if not isinstance(other, OtherName): diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/name.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/name.py index ff98e872..685f921d 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/name.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/name.py @@ -9,6 +9,7 @@ import re import sys import typing import warnings +from collections.abc import Iterable, Iterator from cryptography import utils from cryptography.hazmat.bindings._rust import x509 as rust_x509 @@ -31,7 +32,7 @@ class _ASN1Type(utils.Enum): _ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type} -_NAMEOID_DEFAULT_TYPE: typing.Dict[ObjectIdentifier, _ASN1Type] = { +_NAMEOID_DEFAULT_TYPE: dict[ObjectIdentifier, _ASN1Type] = { NameOID.COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString, NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString, @@ -59,8 +60,14 @@ _NAMEOID_TO_NAME: _OidNameMap = { } _NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} +_NAMEOID_LENGTH_LIMIT = { + NameOID.COUNTRY_NAME: (2, 2), + NameOID.JURISDICTION_COUNTRY_NAME: (2, 2), + NameOID.COMMON_NAME: (1, 64), +} -def _escape_dn_value(val: typing.Union[str, bytes]) -> str: + +def _escape_dn_value(val: str | bytes) -> str: """Escape special characters in RFC4514 Distinguished Name value.""" if not val: @@ -108,12 +115,21 @@ def _unescape_dn_value(val: str) -> str: return _RFC4514NameParser._PAIR_RE.sub(sub, val) -class NameAttribute: +NameAttributeValueType = typing.TypeVar( + "NameAttributeValueType", + typing.Union[str, bytes], + str, + bytes, + covariant=True, +) + + +class NameAttribute(typing.Generic[NameAttributeValueType]): def __init__( self, oid: ObjectIdentifier, - value: typing.Union[str, bytes], - _type: typing.Optional[_ASN1Type] = None, + value: NameAttributeValueType, + _type: _ASN1Type | None = None, *, _validate: bool = True, ) -> None: @@ -128,26 +144,23 @@ class NameAttribute: ) if not isinstance(value, bytes): raise TypeError("value must be bytes for BitString") - else: - if not isinstance(value, str): - raise TypeError("value argument must be a str") + elif not isinstance(value, str): + raise TypeError("value argument must be a str") - if ( - oid == NameOID.COUNTRY_NAME - or oid == NameOID.JURISDICTION_COUNTRY_NAME - ): + length_limits = _NAMEOID_LENGTH_LIMIT.get(oid) + if length_limits is not None: + min_length, max_length = length_limits assert isinstance(value, str) c_len = len(value.encode("utf8")) - if c_len != 2 and _validate is True: - raise ValueError( - "Country name must be a 2 character country code" - ) - elif c_len != 2: - warnings.warn( - "Country names should be two characters, but the " - "attribute is {} characters in length.".format(c_len), - stacklevel=2, + if c_len < min_length or c_len > max_length: + msg = ( + f"Attribute's length must be >= {min_length} and " + f"<= {max_length}, but it was {c_len}" ) + if _validate is True: + raise ValueError(msg) + else: + warnings.warn(msg, stacklevel=2) # The appropriate ASN1 string type varies by OID and is defined across # multiple RFCs including 2459, 3280, and 5280. In general UTF8String @@ -162,15 +175,15 @@ class NameAttribute: raise TypeError("_type must be from the _ASN1Type enum") self._oid = oid - self._value = value - self._type = _type + self._value: NameAttributeValueType = value + self._type: _ASN1Type = _type @property def oid(self) -> ObjectIdentifier: return self._oid @property - def value(self) -> typing.Union[str, bytes]: + def value(self) -> NameAttributeValueType: return self._value @property @@ -182,7 +195,7 @@ class NameAttribute: return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) def rfc4514_string( - self, attr_name_overrides: typing.Optional[_OidNameMap] = None + self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. @@ -208,11 +221,11 @@ class NameAttribute: return hash((self.oid, self.value)) def __repr__(self) -> str: - return "".format(self) + return f"" class RelativeDistinguishedName: - def __init__(self, attributes: typing.Iterable[NameAttribute]): + def __init__(self, attributes: Iterable[NameAttribute]): attributes = list(attributes) if not attributes: raise ValueError("a relative distinguished name cannot be empty") @@ -227,12 +240,13 @@ class RelativeDistinguishedName: raise ValueError("duplicate attributes are not allowed") def get_attributes_for_oid( - self, oid: ObjectIdentifier - ) -> typing.List[NameAttribute]: + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: return [i for i in self if i.oid == oid] def rfc4514_string( - self, attr_name_overrides: typing.Optional[_OidNameMap] = None + self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. @@ -254,7 +268,7 @@ class RelativeDistinguishedName: def __hash__(self) -> int: return hash(self._attribute_set) - def __iter__(self) -> typing.Iterator[NameAttribute]: + def __iter__(self) -> Iterator[NameAttribute]: return iter(self._attributes) def __len__(self) -> int: @@ -266,20 +280,16 @@ class RelativeDistinguishedName: class Name: @typing.overload - def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None: - ... + def __init__(self, attributes: Iterable[NameAttribute]) -> None: ... @typing.overload def __init__( - self, attributes: typing.Iterable[RelativeDistinguishedName] - ) -> None: - ... + self, attributes: Iterable[RelativeDistinguishedName] + ) -> None: ... def __init__( self, - attributes: typing.Iterable[ - typing.Union[NameAttribute, RelativeDistinguishedName] - ], + attributes: Iterable[NameAttribute | RelativeDistinguishedName], ) -> None: attributes = list(attributes) if all(isinstance(x, NameAttribute) for x in attributes): @@ -301,12 +311,12 @@ class Name: def from_rfc4514_string( cls, data: str, - attr_name_overrides: typing.Optional[_NameOidMap] = None, + attr_name_overrides: _NameOidMap | None = None, ) -> Name: return _RFC4514NameParser(data, attr_name_overrides or {}).parse() def rfc4514_string( - self, attr_name_overrides: typing.Optional[_OidNameMap] = None + self, attr_name_overrides: _OidNameMap | None = None ) -> str: """ Format as RFC4514 Distinguished Name string. @@ -324,12 +334,13 @@ class Name: ) def get_attributes_for_oid( - self, oid: ObjectIdentifier - ) -> typing.List[NameAttribute]: + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: return [i for i in self if i.oid == oid] @property - def rdns(self) -> typing.List[RelativeDistinguishedName]: + def rdns(self) -> list[RelativeDistinguishedName]: return self._attributes def public_bytes(self, backend: typing.Any = None) -> bytes: @@ -346,10 +357,9 @@ class Name: # for you, consider optimizing! return hash(tuple(self._attributes)) - def __iter__(self) -> typing.Iterator[NameAttribute]: + def __iter__(self) -> Iterator[NameAttribute]: for rdn in self._attributes: - for ava in rdn: - yield ava + yield from rdn def __len__(self) -> int: return sum(len(rdn) for rdn in self._attributes) @@ -395,7 +405,7 @@ class _RFC4514NameParser: def _has_data(self) -> bool: return self._idx < len(self._data) - def _peek(self) -> typing.Optional[str]: + def _peek(self) -> str | None: if self._has_data(): return self._data[self._idx] return None @@ -422,6 +432,10 @@ class _RFC4514NameParser: we parse it, we need to reverse again to get the RDNs on the correct order. """ + + if not self._has_data(): + return Name([]) + rdns = [self._parse_rdn()] while self._has_data(): diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/ocsp.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/ocsp.py index 7054795f..f61ed80b 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/ocsp.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/ocsp.py @@ -4,21 +4,16 @@ from __future__ import annotations -import abc import datetime -import typing +from collections.abc import Iterable from cryptography import utils, x509 from cryptography.hazmat.bindings._rust import ocsp -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.types import ( CertificateIssuerPrivateKeyTypes, ) -from cryptography.x509.base import ( - _EARLIEST_UTC_TIME, - _convert_to_naive_utc_time, - _reject_duplicate_extension, -) +from cryptography.x509.base import _reject_duplicate_extension class OCSPResponderEncoding(utils.Enum): @@ -60,20 +55,15 @@ class OCSPCertStatus(utils.Enum): class _SingleResponse: def __init__( self, - cert: x509.Certificate, - issuer: x509.Certificate, + resp: tuple[x509.Certificate, x509.Certificate] | None, + resp_hash: tuple[bytes, bytes, int] | None, algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, - next_update: typing.Optional[datetime.datetime], - revocation_time: typing.Optional[datetime.datetime], - revocation_reason: typing.Optional[x509.ReasonFlags], + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, ): - if not isinstance(cert, x509.Certificate) or not isinstance( - issuer, x509.Certificate - ): - raise TypeError("cert and issuer must be a Certificate") - _verify_algorithm(algorithm) if not isinstance(this_update, datetime.datetime): raise TypeError("this_update must be a datetime object") @@ -82,8 +72,8 @@ class _SingleResponse: ): raise TypeError("next_update must be a datetime object or None") - self._cert = cert - self._issuer = issuer + self._resp = resp + self._resp_hash = resp_hash self._algorithm = algorithm self._this_update = this_update self._next_update = next_update @@ -107,13 +97,6 @@ class _SingleResponse: if not isinstance(revocation_time, datetime.datetime): raise TypeError("revocation_time must be a datetime object") - revocation_time = _convert_to_naive_utc_time(revocation_time) - if revocation_time < _EARLIEST_UTC_TIME: - raise ValueError( - "The revocation_time must be on or after" - " 1950 January 1." - ) - if revocation_reason is not None and not isinstance( revocation_reason, x509.ReasonFlags ): @@ -127,293 +110,21 @@ class _SingleResponse: self._revocation_reason = revocation_reason -class OCSPRequest(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def issuer_key_hash(self) -> bytes: - """ - The hash of the issuer public key - """ - - @property - @abc.abstractmethod - def issuer_name_hash(self) -> bytes: - """ - The hash of the issuer name - """ - - @property - @abc.abstractmethod - def hash_algorithm(self) -> hashes.HashAlgorithm: - """ - The hash algorithm used in the issuer name and key hashes - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - The serial number of the cert whose status is being checked - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the request to DER - """ - - @property - @abc.abstractmethod - def extensions(self) -> x509.Extensions: - """ - The list of request extensions. Not single request extensions. - """ - - -class OCSPSingleResponse(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def certificate_status(self) -> OCSPCertStatus: - """ - The status of the certificate (an element from the OCSPCertStatus enum) - """ - - @property - @abc.abstractmethod - def revocation_time(self) -> typing.Optional[datetime.datetime]: - """ - The date of when the certificate was revoked or None if not - revoked. - """ - - @property - @abc.abstractmethod - def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: - """ - The reason the certificate was revoked or None if not specified or - not revoked. - """ - - @property - @abc.abstractmethod - def this_update(self) -> datetime.datetime: - """ - The most recent time at which the status being indicated is known by - the responder to have been correct - """ - - @property - @abc.abstractmethod - def next_update(self) -> typing.Optional[datetime.datetime]: - """ - The time when newer information will be available - """ - - @property - @abc.abstractmethod - def issuer_key_hash(self) -> bytes: - """ - The hash of the issuer public key - """ - - @property - @abc.abstractmethod - def issuer_name_hash(self) -> bytes: - """ - The hash of the issuer name - """ - - @property - @abc.abstractmethod - def hash_algorithm(self) -> hashes.HashAlgorithm: - """ - The hash algorithm used in the issuer name and key hashes - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - The serial number of the cert whose status is being checked - """ - - -class OCSPResponse(metaclass=abc.ABCMeta): - @property - @abc.abstractmethod - def responses(self) -> typing.Iterator[OCSPSingleResponse]: - """ - An iterator over the individual SINGLERESP structures in the - response - """ - - @property - @abc.abstractmethod - def response_status(self) -> OCSPResponseStatus: - """ - The status of the response. This is a value from the OCSPResponseStatus - enumeration - """ - - @property - @abc.abstractmethod - def signature_algorithm_oid(self) -> x509.ObjectIdentifier: - """ - The ObjectIdentifier of the signature algorithm - """ - - @property - @abc.abstractmethod - def signature_hash_algorithm( - self, - ) -> typing.Optional[hashes.HashAlgorithm]: - """ - Returns a HashAlgorithm corresponding to the type of the digest signed - """ - - @property - @abc.abstractmethod - def signature(self) -> bytes: - """ - The signature bytes - """ - - @property - @abc.abstractmethod - def tbs_response_bytes(self) -> bytes: - """ - The tbsResponseData bytes - """ - - @property - @abc.abstractmethod - def certificates(self) -> typing.List[x509.Certificate]: - """ - A list of certificates used to help build a chain to verify the OCSP - response. This situation occurs when the OCSP responder uses a delegate - certificate. - """ - - @property - @abc.abstractmethod - def responder_key_hash(self) -> typing.Optional[bytes]: - """ - The responder's key hash or None - """ - - @property - @abc.abstractmethod - def responder_name(self) -> typing.Optional[x509.Name]: - """ - The responder's Name or None - """ - - @property - @abc.abstractmethod - def produced_at(self) -> datetime.datetime: - """ - The time the response was produced - """ - - @property - @abc.abstractmethod - def certificate_status(self) -> OCSPCertStatus: - """ - The status of the certificate (an element from the OCSPCertStatus enum) - """ - - @property - @abc.abstractmethod - def revocation_time(self) -> typing.Optional[datetime.datetime]: - """ - The date of when the certificate was revoked or None if not - revoked. - """ - - @property - @abc.abstractmethod - def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]: - """ - The reason the certificate was revoked or None if not specified or - not revoked. - """ - - @property - @abc.abstractmethod - def this_update(self) -> datetime.datetime: - """ - The most recent time at which the status being indicated is known by - the responder to have been correct - """ - - @property - @abc.abstractmethod - def next_update(self) -> typing.Optional[datetime.datetime]: - """ - The time when newer information will be available - """ - - @property - @abc.abstractmethod - def issuer_key_hash(self) -> bytes: - """ - The hash of the issuer public key - """ - - @property - @abc.abstractmethod - def issuer_name_hash(self) -> bytes: - """ - The hash of the issuer name - """ - - @property - @abc.abstractmethod - def hash_algorithm(self) -> hashes.HashAlgorithm: - """ - The hash algorithm used in the issuer name and key hashes - """ - - @property - @abc.abstractmethod - def serial_number(self) -> int: - """ - The serial number of the cert whose status is being checked - """ - - @property - @abc.abstractmethod - def extensions(self) -> x509.Extensions: - """ - The list of response extensions. Not single response extensions. - """ - - @property - @abc.abstractmethod - def single_extensions(self) -> x509.Extensions: - """ - The list of single response extensions. Not response extensions. - """ - - @abc.abstractmethod - def public_bytes(self, encoding: serialization.Encoding) -> bytes: - """ - Serializes the response to DER - """ +OCSPRequest = ocsp.OCSPRequest +OCSPResponse = ocsp.OCSPResponse +OCSPSingleResponse = ocsp.OCSPSingleResponse class OCSPRequestBuilder: def __init__( self, - request: typing.Optional[ - typing.Tuple[ - x509.Certificate, x509.Certificate, hashes.HashAlgorithm - ] - ] = None, - request_hash: typing.Optional[ - typing.Tuple[bytes, bytes, int, hashes.HashAlgorithm] - ] = None, - extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], + request: tuple[ + x509.Certificate, x509.Certificate, hashes.HashAlgorithm + ] + | None = None, + request_hash: tuple[bytes, bytes, int, hashes.HashAlgorithm] + | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], ) -> None: self._request = request self._request_hash = request_hash @@ -478,7 +189,7 @@ class OCSPRequestBuilder: _reject_duplicate_extension(extension, self._extensions) return OCSPRequestBuilder( - self._request, self._request_hash, self._extensions + [extension] + self._request, self._request_hash, [*self._extensions, extension] ) def build(self) -> OCSPRequest: @@ -491,12 +202,11 @@ class OCSPRequestBuilder: class OCSPResponseBuilder: def __init__( self, - response: typing.Optional[_SingleResponse] = None, - responder_id: typing.Optional[ - typing.Tuple[x509.Certificate, OCSPResponderEncoding] - ] = None, - certs: typing.Optional[typing.List[x509.Certificate]] = None, - extensions: typing.List[x509.Extension[x509.ExtensionType]] = [], + response: _SingleResponse | None = None, + responder_id: tuple[x509.Certificate, OCSPResponderEncoding] + | None = None, + certs: list[x509.Certificate] | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], ): self._response = response self._responder_id = responder_id @@ -510,16 +220,67 @@ class OCSPResponseBuilder: algorithm: hashes.HashAlgorithm, cert_status: OCSPCertStatus, this_update: datetime.datetime, - next_update: typing.Optional[datetime.datetime], - revocation_time: typing.Optional[datetime.datetime], - revocation_reason: typing.Optional[x509.ReasonFlags], + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, ) -> OCSPResponseBuilder: if self._response is not None: raise ValueError("Only one response per OCSPResponse.") + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + singleresp = _SingleResponse( - cert, - issuer, + (cert, issuer), + None, + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def add_response_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + _verify_algorithm(algorithm) + if algorithm.digest_size != len( + issuer_name_hash + ) or algorithm.digest_size != len(issuer_key_hash): + raise ValueError( + "issuer_name_hash and issuer_key_hash must be the same length " + "as the digest size of the algorithm" + ) + + singleresp = _SingleResponse( + None, + (issuer_name_hash, issuer_key_hash, serial_number), algorithm, cert_status, this_update, @@ -554,7 +315,7 @@ class OCSPResponseBuilder: ) def certificates( - self, certs: typing.Iterable[x509.Certificate] + self, certs: Iterable[x509.Certificate] ) -> OCSPResponseBuilder: if self._certs is not None: raise ValueError("certificates may only be set once") @@ -583,13 +344,13 @@ class OCSPResponseBuilder: self._response, self._responder_id, self._certs, - self._extensions + [extension], + [*self._extensions, extension], ) def sign( self, private_key: CertificateIssuerPrivateKeyTypes, - algorithm: typing.Optional[hashes.HashAlgorithm], + algorithm: hashes.HashAlgorithm | None, ) -> OCSPResponse: if self._response is None: raise ValueError("You must add a response before signing") @@ -614,9 +375,5 @@ class OCSPResponseBuilder: return ocsp.create_ocsp_response(response_status, None, None, None) -def load_der_ocsp_request(data: bytes) -> OCSPRequest: - return ocsp.load_der_ocsp_request(data) - - -def load_der_ocsp_response(data: bytes) -> OCSPResponse: - return ocsp.load_der_ocsp_response(data) +load_der_ocsp_request = ocsp.load_der_ocsp_request +load_der_ocsp_response = ocsp.load_der_ocsp_response diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/oid.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/oid.py index cda50cce..520fc7ab 100644 --- a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/oid.py +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/oid.py @@ -14,6 +14,8 @@ from cryptography.hazmat._oid import ( NameOID, ObjectIdentifier, OCSPExtensionOID, + OtherNameFormOID, + PublicKeyAlgorithmOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) @@ -28,6 +30,8 @@ __all__ = [ "NameOID", "OCSPExtensionOID", "ObjectIdentifier", + "OtherNameFormOID", + "PublicKeyAlgorithmOID", "SignatureAlgorithmOID", "SubjectInformationAccessOID", ] diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/verification.py b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/verification.py new file mode 100644 index 00000000..2db4324d --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/verification.py @@ -0,0 +1,34 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.x509.general_name import DNSName, IPAddress + +__all__ = [ + "ClientVerifier", + "Criticality", + "ExtensionPolicy", + "Policy", + "PolicyBuilder", + "ServerVerifier", + "Store", + "Subject", + "VerificationError", + "VerifiedClient", +] + +Store = rust_x509.Store +Subject = typing.Union[DNSName, IPAddress] +VerifiedClient = rust_x509.VerifiedClient +ClientVerifier = rust_x509.ClientVerifier +ServerVerifier = rust_x509.ServerVerifier +PolicyBuilder = rust_x509.PolicyBuilder +Policy = rust_x509.Policy +ExtensionPolicy = rust_x509.ExtensionPolicy +Criticality = rust_x509.Criticality +VerificationError = rust_x509.VerificationError diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/LICENSE b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/LICENSE new file mode 100644 index 00000000..a20bbb15 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 PayPal + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/METADATA new file mode 100644 index 00000000..6f313948 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/METADATA @@ -0,0 +1,16 @@ +Metadata-Version: 2.1 +Name: paypal-checkout-serversdk +Version: 1.0.3 +Summary: Deprecated +Home-page: https://github.com/paypal/Checkout-Python-SDK/ +Author: https://developer.paypal.com +Author-email: dl-paypal-checkout-api@paypal.com +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: paypalhttp + +Deprecated diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/RECORD new file mode 100644 index 00000000..6acdae0c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/RECORD @@ -0,0 +1,55 @@ +paypal_checkout_serversdk-1.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +paypal_checkout_serversdk-1.0.3.dist-info/LICENSE,sha256=VyR49H2QQibVqvcdDrlaJt-h5rZc5DRXstuWwkKfyCY,11336 +paypal_checkout_serversdk-1.0.3.dist-info/METADATA,sha256=iNTFyle6jiRLTiVYrrG9ATfPPU_P2_9TTMka12D8FSA,538 +paypal_checkout_serversdk-1.0.3.dist-info/RECORD,, +paypal_checkout_serversdk-1.0.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +paypal_checkout_serversdk-1.0.3.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92 +paypal_checkout_serversdk-1.0.3.dist-info/top_level.txt,sha256=1rT_nPhCuypnIoX5kRfLoD-UMXUKLMS0T_LkAeh-RIA,18 +paypalcheckoutsdk/__init__.py,sha256=eRQdx4Ts_sDuPTwWdXdbhv1BpQnGbt8qHPUPTBswnPQ,67 +paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc,, +paypalcheckoutsdk/__pycache__/config.cpython-312.pyc,, +paypalcheckoutsdk/config.py,sha256=BUg004WKbNj5GCBCGb9LpiZkCen9nu6z6bsAcvRnoVM,416 +paypalcheckoutsdk/core/__init__.py,sha256=X0TMjvsqihtDdubqldNL-1D3vTWRh74hdbDZSeNWPmM,315 +paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc,, +paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc,, +paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc,, +paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc,, +paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc,, +paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc,, +paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc,, +paypalcheckoutsdk/core/access_token.py,sha256=_ixfEvDYGqZ_hqhLXjb-J1YQm0KjzEhnopZ39GFi3cY,453 +paypalcheckoutsdk/core/access_token_request.py,sha256=yr-iB7ynzn7h2qQds0Nc5xk3CNipVLf5yE1IBkqypkg,576 +paypalcheckoutsdk/core/environment.py,sha256=313MO-gy1er9i7g7EfHgHdpJRdTQGh2X3C-HckaBcN4,1242 +paypalcheckoutsdk/core/paypal_http_client.py,sha256=ryVxt_YHTwBA3Y59BJEuXtkyoTGwejqfdDSd0z5u8KY,1949 +paypalcheckoutsdk/core/refresh_token_request.py,sha256=8heix9DLsHneRUyDaUfiwXvEhc2qwnWXVkl1SewMch0,503 +paypalcheckoutsdk/core/util.py,sha256=hzpU79Xz9dL30hcnW8SqFjOdaXOgjpMWZ6OvhA9UIsc,96 +paypalcheckoutsdk/orders/__init__.py,sha256=iZ_Vf346VXWOtupqhJYz6UzJAr5_hVsc7RaK2-Ya6ME,368 +paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc,, +paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc,, +paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc,, +paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc,, +paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc,, +paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc,, +paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc,, +paypalcheckoutsdk/orders/orders_authorize_request.py,sha256=qRznpA7Jzk0cKwRrSyQA-Yoc28ZnuBMHxXMM01BWQ_M,16203 +paypalcheckoutsdk/orders/orders_capture_request.py,sha256=anC5ofg1_654qvKzVWUP_8BBmzgHgPbOb0HxwlAydog,16131 +paypalcheckoutsdk/orders/orders_create_request.py,sha256=WPx-qtQpzBynOmQyLNDvRi_b53y9NSOet_e0sy6O408,21313 +paypalcheckoutsdk/orders/orders_get_request.py,sha256=ncdfZiO9oWenozyjIxFcmyUeWLAU-2DJ2cKHSgbbmfU,14738 +paypalcheckoutsdk/orders/orders_patch_request.py,sha256=Ix6Pu8uY2hRSwSHprAipFeea_oaN4jG0RJo1pNrAANw,2959 +paypalcheckoutsdk/orders/orders_validate_request.py,sha256=7pMVZ61JGiYo4kQPLo8rC_Ppgwcf2Cc2sI1XZCH95UM,15753 +paypalcheckoutsdk/payments/__init__.py,sha256=a7y_CrjngULAwt-wJBvZRzVTHTGPZ39vtY8aK2UOC9g,473 +paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc,, +paypalcheckoutsdk/payments/authorizations_capture_request.py,sha256=tGZiYGjs9-GpXLKXew9Pl8D1-NeGvBkJ3CLmwtnBdIs,5759 +paypalcheckoutsdk/payments/authorizations_get_request.py,sha256=QJG2K1SUupZAE5dI_9ZdCZCXC6BYp5x-2uIP8rCB1qg,3539 +paypalcheckoutsdk/payments/authorizations_reauthorize_request.py,sha256=qv-BqREEWczTy_jRAFZqI_nIQFBRNbpojyad_3gsWL0,5215 +paypalcheckoutsdk/payments/authorizations_void_request.py,sha256=vMabLYVdqk5pCCQpmjY-CkRFjWF1tb0VPkHw3uho65w,1332 +paypalcheckoutsdk/payments/captures_get_request.py,sha256=hrTSDTTl_9gRkSjiUIbncIE5HTetsh9BQImIFp57Qnc,4742 +paypalcheckoutsdk/payments/captures_refund_request.py,sha256=2DUqFg-QRALKvKiaAP57cvZ21ROXIEZddRsSL384z_w,5413 +paypalcheckoutsdk/payments/refunds_get_request.py,sha256=WCqE1lkRlzH2NCBmCXF2FYRokFX5BwaxX69ydCj-sCU,4465 diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/WHEEL new file mode 100644 index 00000000..7e688737 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/top_level.txt new file mode 100644 index 00000000..9347a09c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypal_checkout_serversdk-1.0.3.dist-info/top_level.txt @@ -0,0 +1 @@ +paypalcheckoutsdk diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__init__.py new file mode 100644 index 00000000..2d20a0bd --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__init__.py @@ -0,0 +1,3 @@ +name = "paypalcheckoutsdk" +from paypalcheckoutsdk.config import * + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..bdf3dc1b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc new file mode 100644 index 00000000..003f6265 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/config.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/config.py new file mode 100644 index 00000000..c2ee6706 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/config.py @@ -0,0 +1,16 @@ +__version__ = "1.0.0" +__pypi_username__ = "paypal" +__pypi_packagename__ = "paypal-checkoutserversdk" +__github_username__ = "paypal" +__github_reponame__ = "Checkout-Python-SDK" + +import re +import os + +def find_packages(): + path = "." + ret = [] + for root, dirs, files in os.walk(path): + if '__init__.py' in files: + ret.append(re.sub('^[^A-z0-9_]+', '', root.replace('/', '.'))) + return ret diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__init__.py new file mode 100644 index 00000000..d3d68d89 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__init__.py @@ -0,0 +1,7 @@ + +from paypalcheckoutsdk.core.access_token import * +from paypalcheckoutsdk.core.access_token_request import * +from paypalcheckoutsdk.core.refresh_token_request import * +from paypalcheckoutsdk.core.environment import * +from paypalcheckoutsdk.core.paypal_http_client import * +from paypalcheckoutsdk.core.util import * diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..16ea71a8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc new file mode 100644 index 00000000..4d2ec5bf Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc new file mode 100644 index 00000000..7a830065 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc new file mode 100644 index 00000000..de0d1528 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc new file mode 100644 index 00000000..97267bba Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc new file mode 100644 index 00000000..70d5edd8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc new file mode 100644 index 00000000..6858be62 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/access_token.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/access_token.py new file mode 100644 index 00000000..0d6a5889 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/access_token.py @@ -0,0 +1,16 @@ +import time + + +class AccessToken(object): + + def __init__(self, access_token, expires_in, token_type): + self.access_token = access_token + self.expires_in = expires_in + self.token_type = token_type + self.created_at = time.time() + + def is_expired(self): + return self.created_at + self.expires_in <= time.time() + + def authorization_string(self): + return "{0} {1}".format(self.token_type, self.access_token) diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/access_token_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/access_token_request.py new file mode 100644 index 00000000..970ac249 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/access_token_request.py @@ -0,0 +1,16 @@ +class AccessTokenRequest: + + def __init__(self, paypal_environment, refresh_token=None): + self.path = "/v1/oauth2/token" + self.verb = "POST" + self.body = {} + if refresh_token: + self.body['grant_type'] = 'refresh_token' + self.body['refresh_token'] = refresh_token + else: + self.body['grant_type'] = 'client_credentials' + + self.headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": paypal_environment.authorization_string() + } diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/environment.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/environment.py new file mode 100644 index 00000000..04ccfc6c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/environment.py @@ -0,0 +1,38 @@ +import base64 + +from paypalhttp import Environment + + +class PayPalEnvironment(Environment): + + LIVE_API_URL = 'https://api.paypal.com' + LIVE_WEB_URL = 'https://www.paypal.com' + SANDBOX_API_URL = 'https://api.sandbox.paypal.com' + SANDBOX_WEB_URL = 'https://www.sandbox.paypal.com' + + def __init__(self, client_id, client_secret, apiUrl, webUrl): + super(PayPalEnvironment, self).__init__(apiUrl) + self.client_id = client_id + self.client_secret = client_secret + self.web_url = webUrl + + def authorization_string(self): + return "Basic {0}".format(base64.b64encode((self.client_id + ":" + self.client_secret).encode()).decode()) + + +class SandboxEnvironment(PayPalEnvironment): + + def __init__(self, client_id, client_secret): + super(SandboxEnvironment, self).__init__(client_id, + client_secret, + PayPalEnvironment.SANDBOX_API_URL, + PayPalEnvironment.SANDBOX_WEB_URL) + + +class LiveEnvironment(PayPalEnvironment): + + def __init__(self, client_id, client_secret): + super(LiveEnvironment, self).__init__(client_id, + client_secret, + PayPalEnvironment.LIVE_API_URL, + PayPalEnvironment.LIVE_WEB_URL) diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/paypal_http_client.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/paypal_http_client.py new file mode 100644 index 00000000..3f14a793 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/paypal_http_client.py @@ -0,0 +1,45 @@ +import ssl +import platform +import requests + +from paypalhttp import HttpClient +from paypalcheckoutsdk.config import __version__ +from paypalcheckoutsdk.core.util import older_than_27 +from paypalcheckoutsdk.core import AccessTokenRequest, AccessToken, RefreshTokenRequest + + +USER_AGENT = "PayPalSDK/Checkout-Python-SDK %s (%s)" % \ + (__version__, "requests %s; python %s; %s" % + (requests.__version__, platform.python_version(), "" if older_than_27() else ssl.OPENSSL_VERSION)) + + +class PayPalHttpClient(HttpClient): + def __init__(self, environment, refresh_token=None): + HttpClient.__init__(self, environment) + self._refresh_token = refresh_token + self._access_token = None + self.environment = environment + + self.add_injector(injector=self) + + def get_user_agent(self): + return USER_AGENT + + def __call__(self, request): + request.headers["sdk_name"] = "Checkout SDK" + request.headers["sdk_version"] = "1.0.1" + request.headers["sdk_tech_stack"] = "Python" + platform.python_version() + request.headers["api_integration_type"] = "PAYPALSDK" + + if "Accept-Encoding" not in request.headers: + request.headers["Accept-Encoding"] = "gzip" + + if "Authorization" not in request.headers and not isinstance(request, AccessTokenRequest) and not isinstance(request, RefreshTokenRequest): + if not self._access_token or self._access_token.is_expired(): + accesstokenresult = self.execute(AccessTokenRequest(self.environment, self._refresh_token)).result + self._access_token = AccessToken(access_token=accesstokenresult.access_token, + expires_in=accesstokenresult.expires_in, + token_type=accesstokenresult.token_type) + + request.headers["Authorization"] = self._access_token.authorization_string() + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/refresh_token_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/refresh_token_request.py new file mode 100644 index 00000000..78e944e8 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/refresh_token_request.py @@ -0,0 +1,13 @@ +class RefreshTokenRequest: + + def __init__(self, paypal_environment, authorization_code): + self.path = "/v1/identity/openidconnect/tokenservice" + self.verb = "POST" + self.body = { + 'grant_type': 'authorization_code', + 'code': authorization_code + } + self.headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": paypal_environment.authorization_string() + } diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/util.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/util.py new file mode 100644 index 00000000..4ab4aaa4 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/util.py @@ -0,0 +1,4 @@ + +def older_than_27(): + import sys + return True if sys.version_info[:2] < (2, 7) else False \ No newline at end of file diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__init__.py new file mode 100644 index 00000000..2ef0ace4 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__init__.py @@ -0,0 +1,7 @@ + +from paypalcheckoutsdk.orders.orders_authorize_request import * +from paypalcheckoutsdk.orders.orders_capture_request import * +from paypalcheckoutsdk.orders.orders_create_request import * +from paypalcheckoutsdk.orders.orders_get_request import * +from paypalcheckoutsdk.orders.orders_patch_request import * +from paypalcheckoutsdk.orders.orders_validate_request import * \ No newline at end of file diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..fd2e7163 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc new file mode 100644 index 00000000..73e31e8e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc new file mode 100644 index 00000000..ae112801 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc new file mode 100644 index 00000000..439d9e5c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc new file mode 100644 index 00000000..7608afca Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc new file mode 100644 index 00000000..f62a5c71 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc new file mode 100644 index 00000000..f84c4bab Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_authorize_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_authorize_request.py new file mode 100644 index 00000000..b631da6d --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_authorize_request.py @@ -0,0 +1,38 @@ +# This class was generated on Mon, 02 Jul 2018 17:09:03 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# orders_authorize_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class OrdersAuthorizeRequest: + """ + Authorizes payment for an order. The response shows authorization details. + """ + def __init__(self, order_id): + self.verb = "POST" + self.path = "/v2/checkout/orders/{order_id}/authorize?".replace("{order_id}", quote(str(order_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + def pay_pal_client_metadata_id(self, pay_pal_client_metadata_id): + self.headers["PayPal-Client-Metadata-Id"] = str(pay_pal_client_metadata_id) + + def pay_pal_request_id(self, pay_pal_request_id): + self.headers["PayPal-Request-Id"] = str(pay_pal_request_id) + + def prefer(self, prefer): + self.headers["Prefer"] = str(prefer) + + + + def request_body(self, order_action_request): + self.body = order_action_request + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_capture_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_capture_request.py new file mode 100644 index 00000000..205488dc --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_capture_request.py @@ -0,0 +1,38 @@ +# This class was generated on Mon, 02 Jul 2018 17:09:03 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# orders_capture_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class OrdersCaptureRequest: + """ + Captures a payment for an order. + """ + def __init__(self, order_id): + self.verb = "POST" + self.path = "/v2/checkout/orders/{order_id}/capture?".replace("{order_id}", quote(str(order_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + def pay_pal_client_metadata_id(self, pay_pal_client_metadata_id): + self.headers["PayPal-Client-Metadata-Id"] = str(pay_pal_client_metadata_id) + + def pay_pal_request_id(self, pay_pal_request_id): + self.headers["PayPal-Request-Id"] = str(pay_pal_request_id) + + def prefer(self, prefer): + self.headers["Prefer"] = str(prefer) + + + + def request_body(self, order_action_request): + self.body = order_action_request + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_create_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_create_request.py new file mode 100644 index 00000000..b4661dec --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_create_request.py @@ -0,0 +1,36 @@ +# This class was generated on Mon, 02 Jul 2018 17:09:03 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# orders_create_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class OrdersCreateRequest: + """ + Creates an order. + """ + def __init__(self): + self.verb = "POST" + self.path = "/v2/checkout/orders?" + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + + def pay_pal_partner_attribution_id(self, pay_pal_partner_attribution_id): + self.headers["PayPal-Partner-Attribution-Id"] = str(pay_pal_partner_attribution_id) + + def prefer(self, prefer): + self.headers["Prefer"] = str(prefer) + + + + def request_body(self, order): + self.body = order + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_get_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_get_request.py new file mode 100644 index 00000000..7acbbdf1 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_get_request.py @@ -0,0 +1,25 @@ +# This class was generated on Mon, 02 Jul 2018 17:09:03 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# orders_get_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data H4sIAAAAAAAC/+y9fW8bt7Iw/v/vUxA+F2gMSHJsJzm9AX7A49hu4tMm8eOXHlzkFhK1O5J4zCW3JNeyenC++wMOyX2X7TSy2jT8I61FcndnyOG8cWb4750PNIOd1ztSpaD0aA5mZ7BzAjpRLDdMip3XO5cLudQkBUMZ12QmFaGC4PgBma7I2cloZ7DzfwtQq3OqaAYGlN55/emXwc47oCmodusPUmXttnNqFo22f+9crXILmDaKifnOYOdnqhidcqgDPGbpzmDnR1j5xg7sVwsgZydEzohZgAMaMVguWLIgRhK9kMuAm8XjSCm6cp9+Pti5AJp+FHy183pGuQbb8GvBFKQ7r40qYLBzrmQOyjDQO69Fwfl/fnFjQBv3Ettom3QuhQbXVuL20cLTRe1hlBwivwds31CH++G5ThRQA2PDMmjA1mzvgplSA4SKlNgRA8IE+XQmDCgBptln1ySj5pdnC2Ny/Xpvz0jJ9YiBmY2kmu8tTMb31Cw5PDz8779pSOw3hi9Hr3ZH5BISKVJNqAKiPJp2eTmQmaI4knKia6Nk7hpH/1s8f36YTLlMbn4tpAH87f6baKOkmLuWD9LAa9e8V28nFkcF84JTReAuV6A1k4LkSt6yFDSZFyylIgEyLQxJJWgipCEK/gWJIZRzwsQt5SzFydAenr02QF+4vEiWg3KN30sBq+4Sz5XUekwzWQjTWONWR3eRk0IpEMkKF9ONc0yCzJigImGUE6Oo0G4xBkQXyYJQTSiZUo7TIxXJ6SoDYUhawBbo2YM8TmTaouhWTxfdT2ahAIbJglrqAkXOLj8OXxzs/72aCPvsL8/2UpnoPSYMzBW1L9hLmYLE7CnQZi8MHtrBem+XmAU1hKUgDJsx0MivwqBNMKbBg7NyS3nRnI3Q0p0F7Bl4Npqx+cKQadgjBa9vJM7cryNBcC48A/aoWUw5uwEy+cf5/0zcJNg9aveJWeUsoZyvahs57JHw1tY3SAoJyygvn+j/1tWHk9q3dDFNmd2wqYVQErOQhaYiNQvd/7m9gOEPUuE6lXxHFNkUlBU3AZCc0wS81GxSyIBoAPLpOLQdW0L4XLLZiMh6BG205Cz+fFDSjjbLudbCJgy0mFbZ1IXRdVnRD8wsQJGE5qZQUHIglmWQMmqAryxjooVZSMV+A0LLIXUdiNCZ5QLubxSITIoNY/4TEzekjkpnDjgTN7oxBaGlOQNHglALl10n5fSUoQJODaTk07ujq9OPR5cEHw2USHO2J29B3TJY7v1tQQ1Iqoc4ZHdEriRJZJZzMJZbaSMzOyO5lYCUD0ihAclh4ppggu8mS2YWrv3t6dWEZGAWMkX5kUrc+uE5HI0bVaMSis9cnJ6cXZwelw+2JvvV5uUFiARH1Oe3auvSGBKQ5V+W3Gq6ZjHNmCk5BmjUguiWtslCwayBgW/okehhRQ1VczDk+uInXOmM3oCH3lGM5c4DO3zKhOvxS+kXmGny6frijFxBltsnhk7NM5A+qOm9evn357u4/iNi2WyuYJgrmVgVS8ytLpnwIvXU9V+TAZk8mwyQhCa7E1JKZz1CJW1icZ0Q5ijoBlYk0LrFVQrkCJbMkK4tp/ZT4HB0+FC7gNounDDYvKWFQ2rq0F+99T4KHFhVK4UZE5Bac+3TxQ/H5OD5i1fVEiyXy2oB1Cyx/+yIkbkzuyPPeKZeJ3FbEglja/hbmmoh75u6mL+7ujoPZFjKZbOGeLeEgQLeAN/97tEtcXIRQKu72OV7cKO8/O/vvy9Nohe7QSfToG5Bo5otrFx2CkjFTwtBsymbF7LQfEXSxhJryKgwLNFBmrtteGnVFRRFFx5C3aIhKijCRrVmc2FFpd6zzw4DSu2fozuLxu5TiMvLZAEZ7a6FDu3VcpRN3RWp82mrDWyQ+iuxI6fWIOyCStOUObX3zEDWFO/dvibwm53RI87Jxxmxn+oBk/OPTckSWtbrHrqYDt20ew6NE5wV2hC0iNE5MKdMaGco18d/obhvoyZW96AmVm3UfMtmUJMCjd9MOjPkiVBcR18oFZCKmrTVbH9KuloPWQ4idTZbC7RGx1PCto6ZzxSdW9Z2AVrywqvjFYS93X/EHLIOz2C9nMIa5gayJ+DBmzFTcMSGd8R7qx6t0bS6WtZDOr7VOhMpDNyZIYhEpkzMCW7lLfiypkxQtTr1n20A3+nqU/XRHO6A7YT9+4IblhcqlxpI6bl9Txknp3cGhLYsgjx7f/b+dJecU2XIRwGvrb6eUWPXrnoGtKZzIG9kykA/qNQcPH/xcndLyplpa9bmYaX6d8/P1VK+Jkh9xIL1qJl4tZmZ+OURPEPIph/F/X5K7vVRwHrxKwW0xG9o2Zz4rcZvmMeso7ecmsWloao50/XWFuVJQvOcr5w97UAl6KYEYrGgIgH9Hbm+ONMDou0rsMv+rtnh6LAdbUfy5NbEV6L2ZAvTTu8fIR/zNeA9LVy/PNZOcUbGZddaaXVEmyXaLNFmiTZLtFmizRJtlmizRJsl2izRZok2yxPZLGs5EjO8xZJ8S5cnOYPEdm+cSRz7GIS+BV5hc31RXUtfSJuPZFgupI9mAI0iM6crF8xTBbmQxhNME8q1JDdCLgWh7kANv7Q5sXoulbGYkXOpDeXkKE0VaMu0IWVFNnyrKBOQ7vZaZ3Zk2yrzbd2JyMOXGAoK6qMpc/dd/+SIvKe5ttv7k4fkZ8e0mBTvwVDLzCqZMWdmUUxHicz25lLOObD978UeZ1P/Nibywuwt2Q3bW/u2XVyLd1fvfyIvR/vk01Fh5IxxbpWBmVQZqghKcv0ap58WRpYxDdQYxaaFgebZ5fIQRdjVBUqxl/t7GhIMV9Aj2/A3Wn0Cm4fhE0OzgGH9C8PyC7ubW/KwxCcu5Hftyo7TckBnhWt93ZUWUgzL1a4M9bDGZfh3FTUnMzAsA00EQAqpi7Szc8Asdx8QxfTNwFqKEsOsdAKCKiY1WS5AAZkxAcO5pdTyG0y4+BAmRRlT6N8+Ih+kqcgRZUQis0wKKyqU3ZjKuBBUmYPQslAJjMgFpIVIqTDhCfww8FS7qBK4o3bRBmQS5ih8YhQaOBMw3sfYkUIXGI5IfciLg1TOqqdDRLY2CsCMXTjgxIWlhDaaQbPFKmeTLSjdBeNWXUYImjp3q6elqBDbnBIuE3do72hAQa5AgzCexSnImIYRuS4nKbwWn7eEEKIjLZ1w7kIW7PSFgTrE2fiVbT7XWjEXipnI1EdJHyt6C4K8k4UOYZpV55bU8hQ4uwW1GmtQtyyBlhug09kTMe8HET9ohDyeyNmMJUCm8m5ApnTuZwR3V17rtxO2JVRr1NyMpWi0dxF0/Q5S8g+r4U5OVKFWE8KE/5P8RMWXb4jPxAMntBeT0LMeF0eeW4W3YwA229fC6gzBxkay26aAAZnKgsMtVemAKElTJC6f1LCkq22hV0zHgR008Wt2dBGcMaXN0IXhgjDMrMgUuFwS6vlXyY6kKnlZLyuz1lUPO9NMzDlUr7G8ygqvz2BmI3JMhRVqlMw4Ndb0kWo1IDMupbLTLjOcdmqlWQbCjLalvjelXa/2UHatmXur1kOIGCtVwwapVQLAkWNbCO///ZDUWQAJyKFygUYxCKNW3nsVdA38adUNkiwgubEKqWUsVkOz6oWFZ1bwSl/dDik35u1g/ZQe9O9XTFh6xJzqghloEM12GVIDl8P1aB72oumUtx4sB4TNSt2vya8CL3MKdxmQ/0bR3xgfEJcugZsb7kyVbjQRcIdx//+kPKPKTNxWI5yKNKPqxgogKsiZSBkVW6eVjIkxVUA7m6/R0Z3ABZsvwG4+uAV35JSyW4ZpaJ45FXbHhLDQhhaLeWqoq2tDDeB8nF1+HB7uv3o1PAh5MfZdo+AfnXmNg/JSVWlv4uMjp94KacjkmHI2k0owOhmRn13S0HRVQcX0vUlD1z+OyJEbvbo/9ef60o5EPO4feEwFTakdHNC/f/w/aE6FGw4zSEyhHnjgcsnMb6AsWdnHbqgwUjyQSLR1KjtYR2UHPfp/wsxqQIxcCiSRW8Y5ncOIXGaUc1BWiAprA5UvQWIc70+2v3sO1+F12IOX3S1WE+CIny6mhZoOiAA2X0ylWkjplKCU2Q8n5kGEDwKNP0TWjlWNyKX/5JQypSR+rP71++kMGRW+o0SiBW2lajdMa+bSUClf0pUm9JYyjkb1tDAux6H3fSQJqouzT+xUEIv8n420X6wjgRf9jo/Gci9R//2cRT+c2OETq5+GaXssGTgPGzJTLZWxSqU1WX0qqOXUmJJI3haghAan9mRUrMgPCkSyIAaUYkYqBrqSc77vbYHh+PeSkNPdgh8GUoLYWcypYbfghIq2IBwvmPhzrLQXHT2pu82OvszdpWzm7RKUdPuPS9h1r78/X9eNsQShYM5Ktv+FKebhtUgcVgK/VUANeaMY6rdMd5whb990XCClTG6Pvf6xZ2xFfBYvI/OhUzBSmdlPWp4S/ODUBACtteQzso4PJl2wkYjIUiqeLplvs0ocVch/CuF9qhxSkiuWAHl2fH2+67N5LJsUNyTBHYoGgJJaD6fO5KtlmG8kkf53Z1G7Dd0lz2Z7n6sdGYHLDC6VNTuZv7HczaFVs34t2C3l1jQkV2V+tKqbSW7aLUl6A7T2ZuISV+tvwUPh2pDKLQ5itGQ3LAcrZ6SaO6f8eYXH7tbM0ylTZjFOqWk5LuvNfT4PKtIh5VK4UhRPVIHiSlYeBKJzwIoHHOaUuxNMXS95gB+VM4KgD8hKFkQvZMFTzHnFOhBu3YQkVGuZMExWQxDtdmMZDH/zCNER+ecCBNwCKuWaTa0CHnJnEXuqUjJJQ5GOiff/XC2Y7itcUZaoqE6dOffFKZpa/g8wVQVVK3K4T9xJLUsDh1lYQcZ0OAKbysKaJzQnK6BqWxYVZJTxcd95V7unP0W8POxiv0FK8JnSJvxipn6dWzv01YtaniqeqVDO5RJSMoWZVI42D16+XDfKJZ7bpW6z9P/T5eiazcWIvJNLSy0DfGoOApQzBZMEcktmGb1jWZERDmJuFiHPvYG9XdmDly86Kbb+iNxqh0EgUmOfLgROUvpYKAncMW22VA1lLfncIQnwdoGhZnuXdEI/Xw19FZi0VhUhnBJveA98cM72dlxM2zW/1ifvzllm/qhamdXTH0ZR7rYYjGcF591jhP7+Jugnp+cXp8dHV6cngQKVWX2nSfls2+c6LTQTloRt+4AIlty4v5DMV/6gEmfDqTVUWGY4BaJzzoyrDoK+zgHhVIdTjIaTsvYVdyS5xTOZ/qlcP4E4aaC0FOXcoZ90iyDP2S2ILsyN5geBxtG4in5xtohAxtKUQxeDZvuDKLjhnqCsFo1BJEbiCQGQDIP2ONTHaV8DwUpbt3OtiFllLHFTQS01f6cHjXdvZ1JyBTN21wpR8k09U4FdA6feGKtd+EixrW8gXczacJdNPeoldpXRQGEptwqv6h7Alm0PEp0f6qglY9zPNTnqBDD1cTtWExgY9uRffy/9+k+2aTdr0m4mA+368VszNDBSqy30a43dOT2nq3PKXYgXOTup2W6UZFTfQGpVIu1PB031BE3QUguHYliXKMTRWB0+xEH64BYGXtFqP2c1efzClK8IiEStUJXDamwkVzJXDIxV1W8tqgJjnN9QDYcHpY/JSILB1KFAhi74pot7nC+k6FFSct9cm2zf0kO9tqfuydxgRN090PXFBrQ61sEawjSYIMxoq09IYU32Vjjdp9PR/qsXfrTdEjmnomOQYoUNU4yYMHsKkr2r4cXp8RAf3QPxpZXAPj2h4610BFEXooeU9uz4eHdLM+OdaFilKA22jd+Mx8fOBYBqsP+q31kYf+0VwDlWuvRO4P2XJGVzZvyGbD+XSKGtBWM/QaveFLQJMWNuBj6cHLtIRl1MXf2VMkbq2eWH3e1U/YMQ9N9H5z2d/TYPDtruwXOY2D64u319Vk9j2f4EtLh2n/ylSfRRYhn5bSfoqdG8jgmjy2vT+sMVvSNnlSTqS9+9G1tR1UrdLRt7QiHsK7uugu2Hl68N8bfQp12E0ntj6L/THrMRuSzyXCrjdRzTUJ4wjMOX7ZKCr2rubeet8tPDNNnfr3vEuHRB34QJrOJZUO5C9fdf9A4L5jlsyxPp5qhLvM32R0xgyOL6nFncDN3XKf+8UMmCaiDXgvVlqPjucSGYaWWptLvWJ0SFoQSHjsgpTRbNRgLa0ClnegGuXp8wdqnJFMwSACNfynKUIiUZ2GeF6X1VLf7P1wSlzhGCdYmVYZT7+p7oFjKNDYlqO2Y/lC+dKZn5aoj+qxurUHnk6iujbfBGAb1J5bKH+/TUcb6ngrORpsSQ1j5ARVkrm0zDx9wclCWufcR7dcRhyhcyA5l/38A3WUIOLXrB8hxrOS6oSDn+NWczQ5aK5lYa60K5TAIsDMo0ikcXGEZFCAeaqj0fDjFzhyk5JGy2IhP3mVEJ98SxEY8f/Ip8YmJhHCNsE5LzQpOJ3ZaNhgBn+B2gDb8tzGMLc2goIZ+QjIn6K8YBi0kb+qsKNJTp6K/MpWZ4Du8lLi5KWVhYQeLivJyl55jAUJfcwX2BkoWC2f//vztBQUnhFrglrVFOVznlmInzOTWH/3fHgRyavZ+ebibSvkPq91D5tNZVOxSstXZpvaLjEG/o6KT6Toe2K9LeBllvbP7WFXoP5NpyuFatscR7LPEeS7z/ZUu8r+ELgVs1S0RXjZErRK4QucK3xhVKdaV1vULVGvlC5AuRL3xzfKG0mzvVusrmyBkiZ4ic4VvjDMH/0QxKqRojV4hcIXKFb5UrlB7wXvZQ7418IvKJyCe+NT5Rnr51jsqjVRH5QuQLf1G+8JgYsLhn4p6Je+ZzoiZdkFI7Zq/e2qWRo/MzjD8FVSUwhqRGDOK79gkDChIpEsYbj+AVDC5huZaP7wJXfJRaI1GfHOU5UIUlJmodLuIUjPHlnRTkUhm9tSqO9YuW6wUc6+094aYh/qs2cGtXYN9KlkB7qRvNn7nW/tlwdygW5HYFjzeMU38Z9c+4CaKMGsQRfTF6OqwNFgh+uui8tRuRGphLtWruw6qxJwXdQEbCiM2FMD8d8btYKcuNLeTb3wGPz3ZGAEPe2+aqkD8M4q8FxbqUDTBrjWtADSN8VUOMDFwuJIctFx69KZo+G/zdV3RDJjfkBiC3mw3ja59d/ni9W0YrP8HFLeuNybYZGQ3IqAxHZfgbdCxZRjTuCYtvtkfeEHlD5A3fnKFcqKa/2f3u0sX1xU/W9EW1rJ6H7gpq2V5as2dbOVw183bBsPD3xrMSz+kKoL+oAXQqGqy7L8wZRXj1jYIE2K3frrNCpC6zblbwGeNcty/BKZ9dm6W4Qe53wnTO6QorB74PH743JTN1T4xTapoX5rU67pmU1qI/DuXSyeHLmtRyx1zBclmYQcMvMvAlt3D+c6NdrsgTUNAjbg1RVKQ9V4bUm++vKFWl3j1qHoKo/EPm45GyJOSQjhGs1l0qra5YXu4bLi+3pq5ISSTd8iedrq+utsjv01ljcZFYXCQWF4nFRTZeXOSXWCw2SvMnrgHpNdz28VuzPZaO+xK7Fm2CM6GNKpJ+884bDmPWGNSweXv626eJq/rll8EWqT3inB1+HdyfygjL9bw1o0fkVFiwNJkBNYXy/pHcjdP+Egd1A8Z5TyqXYQqcrgCLAkwLpd3Zt7vqqn7DGLXCweA9pzNvXnK7h9JCuevNUguOZdili4jmFpA0IKTxhgs33PfhiwuzkAp5WBj49JpfHdlx1lb/+np77h9D10Tp8loAT4kUZAoLymcdO3TDKn5Yix96XS++dzyD9p3NrZ71x9p288nCkpMllkRmGcNy7XpADMu1u65EOhGkN3eYvcaR/nmlZaL7PLrPo/v8r3K0FpydVgf4MjdzKK0TggK+GrdzNCGiCRFNiK/FhPil14w4LtXptVaE7jMd9BoHZF05D0NdOfvGGYZj883Sg+7OUc/MmtecdpXxQVeRdychCpBLbrD2mv/4mpM0Wu9uTlWna71e24Nh3xzh3ZuNSUuoIAt6C+Q3UNLVi7ds6kHrJSrDURmOynBUhp826QI95XjdWnO/NNp7NOL6bXRPdFPdJV4q7rS6ciWWC8ahRjj+6nE3KtR/3cxFlj1XzpXFNucFS5HFTQtTXUSn4F+QGLyGjgl3y5y7ju4Pvh0sZ47kuuvc7Ytr/TWvdTul5j49fOhMHOMufQu2bVcx+VNnBQWTY5jCDM8Ra6iEpKAyI+jJcfuJiRty0kg+aWHJmbhp6qChZb3qqYDjKn16d3R1+vHokuAjQaTQnO3JW1C3DJZ7f1tQA5LqIQ7ZffpkIRDJVbtWfNXWt14po5giZBmJ0zOMtLI6Y6YUuqCNuzJ0O6S3UDBrVhJ1DX3mk7V3DBBD1RwMub74Ca9UzehNMAjdWlkFZxBOt73TCAvdB1NVk0/XF2fkCrLcPjF0vNNA+iD7fPXy7893kQacEZYrGOZKJpZvifnAX1blrzb+r8mATJ5NnOU12Z10PBMTi+sk3N57AysSqMziKgVGt1mVCikKi9e7KXA4hmt7dTHVduGEweZt3WdnqalDf/XW+yhw4I6xHN+Yrsinix+OycHzF69aMQxhAdQssf/siJG5M7sjv9WnXq23M+QJY2v4W5pqIe+bupi/u7o6D2RYClmzhni3hIGCZkSi+91jnuHkIoBWONvle3CjvPzv778v9YwXu8Gs0aBuQaOlKoK4oH7xLKEXgmZTNi9kofnKJweGJdaQUWFYooODym1DvJYamf+Fh1C3aIgKdys11ZrNBdr7e/bZYUCp/XN0Z9HYfQoBdZksIKM9SXOhvZY3F5q6K1Ln09bS3yD1V2JHTq2W1ePKKI+9zzqpt92+JvCbndEjzsnHGenPC6acf5y1rop1LeulvS6mQzftnkPjBGN4U3XR9ZwyoZ32WR//heK+jZpY3YOaWLVR8y2bQU0KKJ1lT4fiOvpCqcC6DsNm+1PS1XrIchCpc3u0QGt0PCVsa6/0VXRuWdsFaMmLTkxLb/cfMYefka7/BPm/lgdvxjDAERveEe+terRG0+pqWQ/p+C4eSBi4M0M8NmFiTnArb8EdPGWCqtWp/2wzcL3d1afqCwOiC7YT9u/xjthC5VIDKd0h7ynj5DQEAWvy7P3Z+9NdjLoiHwW8tvp6RvFYqXoGtKZzIG9kykA/qNQcPH/xcndbl4F1bgF7WKn+3fNztZSvCVIfsWA9aiZe7W48em8dzxCyeXLifj8l9/ooYL34lQJa4je0bE78VuM3zGPW32dsFpeGKtM6z6xaW5QnCc1zvnL2tAM15GtZLKhIQH9Hri/O9IBo+wrssr9rdjieeYy2I3l8HEDtyRamnd4/Qj7ma8B7Wrh+eayd4oyMy6610uqINku0WaLNEm2WaLNEmyXaLNFmiTZLtFmizRJtlieyWdZyJGZ4iyX5li5PcgbJxso81sG7BM5BkXMlzbpwYo1Dxnl9SO0sqKe3BwO4BY6xxeU4ImczUJC2j1t9cHcHMDyRC+kbrYO02jXQhd5bwpTmud7L8nxPQ1IoZlZ7Ds5h9f3dreQF5oWBsS+C2tFz+7rXM71ECmcO1tIEE3mLcxiCW2qht1vidNpQUzTxKpuauJyJlFlUNVkuAK+ob0FMmCbA2ZxNuQvPcWtWo5nRtnbn47Fy9UJt1x8fLFXkaW/cZrM9xvL9eWP56rR57BIl+koxY4dulWIuG+9hIZ0k6i9IVui8LKYqxFSFmKoQUxViqkIUeRut5/+Vl9eYSsmB9lhWVg7xcVLK+ZqDudXzkCZdK/jSlfFWfE+BZDStnbeIPjWV/GzZJ2H6Xo7ZTkq227eTlzwi/yML+2lLVxhL2gUsALMuDJ/8cwGCCGkpmLOEmZ5BPvV5YG1IWnDMoH8cgPdy7PYrcJXXI+kwvHcVHkL2fo7+tSSKtPH+6tJEPCsIWSJO+9GEai0Tdy1Q5W97YmRj3kjMG4l5IzFvJOaNxLyRmDcSY7BiDFaMwYoxWDEGK8ZgxRisGIMVY7BiDFaMwYoxWDFvJNos0WaJNku0WaLNEm2WaLNEmyXaLNFmiTZLzBuJeSMxbyTmjTxJ3kh5jcwF3vOCN3i/UUBvUrlcvxVUOXg8rQ3ubIo149ZdQWM3QBhWXpW54dCkimhP7yzmcyAX1EBfXWnXPVauu15VutnTd1Gqf7Ud4Yg1BQMqY8KfmPswfiMtVd+CMmSmZIayuoxhNpJQIZFQflec+u/asloWKoFx+GBzVTt9X2H0/hfo0OgR65+abt83NTWfk9ngY8SqrBbcCiNy+mvBbikHty3sTsCcHc8HHO1VeHn7yzj5GRIfpCrzAhAApxvadxlZXZUbFEuFaRX+A2U+gWRi87dmrkkEmiup9bgnHajVEZOCYlJQTAr6617m188dBJg+3tBojpwhcobIGb41zuDs/fGse8dn1Rw5Q+QMkTP8ZTnD0991Xt5sX92Y33fvuVRkquQNKDoH7Pfr1Uk0e9CZEwstRP4Y+WPkj/GC9HhBerwgPV6Q/s1dkP6g3Vc7UOtRbfp6o5YTtZyo5fzFtJwnq/z4RIfuLYK+dB87cWrVOtDHadnfRqHWtV5bW4OLR/XpmZYCqluxQmVTXw6G7SLLxeo+qK0Ympyffjg5+/B2Ytnw5OT0w9npyWRrBUVjac6/UmnOC7AWSx/toiXTIt7Qdl9NHmcBfUE1Tv+K6BqKSlNUmqLSFGtwRtn256oM6ATU114P0GGDHpEnQCdW/IsV/2LFv1jxL1b8ixX/YsW/WD0jVs+I1TNi9YxYPSNWz4jVM2L1jFg9I1bPiNUzYvWMWPEv2izRZok2S7RZos0SbZZos0SbJdos0WaJNkus+PeUFf/WgSekgbGRY0xPabOLRs/a6N0nDREpE9rO6eqRddpyN/L+Im19g7oYdgqzbQbHT7FCUgzZjCGbMWQzVkiKnCFyhsgZPpszfABDjtweLhWiNXZXxRbWaERrBqy3yiqtCMmrKhFgDeZQ2PICTKEEhmuDaOySpi5FmCYpw6LQwteD7RvcyoleLkBBsyzBQvIUtyNTZDvJNL6ILaR93LinM/LkyJMjT/7L8uRYWjuW1o6ltWNp7T+ytHZwa/UoJJ2uqI5EdSSqI3/hIimxjG7kDZE3RN7wVZXRvfc0MRZEiUwwMsHIBGOt3FgrN9bKjbVyY63cjk9RGsrHTnnqP5haNyJqO1HbidpOrJn7e2rmfs2lcn0YwldRINfDuvDKYLMo7g9HZz+dnkw2hEmsjfsN1cb95VG1mjBGp1uWr9XRXemj8zMsKuUuYnf8GO7QSOD1GoOtyrkBcOzPMDuHtwZpF120koVLCnE0V1lUxCyULOYLMjk/ujp+N9k0n1qwPGdifg+n8iOaPKpq7OGv4Z2eTW2OI51LZTCE/lxqQzk58hbJs/eQsiIbvlWUCUh3e3OXO3bgfRZgHr7UMAVJ7r4b7EDynuZ4dPrJQ/KzS+lhUrwHQ1NqaLXn58wsiine9T+Xcs6B7X8v9jib+rcxkRdmb8lu2N7at+0iS3l39f4n8nK0Tz4dFUZaE9zONvoqEymMkly/diEVhZFlxT9qjGLTwkCzstfyEFnQ1QVyoZf7exoSLOanR7bhb7T6BDYPwyeGZgHD+heG5Rd2N7fkYYnX0qefvV5R2u3rrrSQYliudpXGXlq7QdZWKpnMwDJ1TQRAsNBwDphlhgOimL5Bv7ALVNEJCKqY1D7kb8YEDOeWUiuLWjgBYblrUFj920fkgzQVOaIVmMgsk6JmDDr7RuYgXEyA5TxpIVL037gn8MPAU+1qLsIdtYs2IJMwR+ETo9DAmYDx/sRZhYVzE/iCkA5SOaueDltdGwVgxk7XnLiijaGNZtBsMascJltISS0YT5mYIwTNjNRWT+swgNjmlHCZuJJ2jgYUWEkIwnh3loKMaRiR63KSwmvxeUsIQfW2dMK5k+8YhuoH6lCF0q9s87nWirW9KMeK3oIg72ShoeNQ2VKwSwqc3YJajTWoW5ZAK0m+09mn0rpBxA8aIY8ncjZjVsGQdwMypXM/I7i78lq/nbAtoVqj5pbaXm/vszpsv4OU/MOK+smJKtRqYvVG9yf5iQqYbBkPnNBeTELPelwceW4V3k56dLN9LawuTbqxkey2KWBAprLgcEtVOiBK0hSJy6u7S7qtcDFdTMeBHTTxa3Z0EZwxpc3QKYsgDDMrMgUul4R6/lWyI6lKXtbLynQxHfawM83EnEP1GsurrPD6DGY2IsdUWKFGyYxTMyDaSLUakBmXUtlplxlOO7XSbGM31zzGJmhKu17toexaM/fEDgi2eKkaNkitEgCOHNtCeP/vh6TOApqWA6aMgzBq5Ws7BF0Df1p1gyQLSG6sQmoZi9XQrHrhTmt4pa9uh5Qb83awfkoP+vcrmrKPmFNdMAMNotkuQ2rgcrgezcP+kE9U3nqwHBA2K3W/Jr8KvMwp3IixJY83iv7G+IA4XxxubrgzlS97IuAOA0j/SXlGlZm4rUY4FWlG1Y0VQFSQM5EyKrZOKxkTY6qAdjZfo6M7gQs2X4DdfHALriBTym4ZOig8cyrsjqkdwFRaLNrwqKtrQw3gfJxdfhwe7r96NTwITlf7rhBUixPtrb+gqrQ38fGRU2+FNGRyTDmbSSUYnYzIz84jPV1VUDF9r0f6+scROXKjV/f7la8v7UjE4/6Bx1TQlOJ1Ph79+8f/g+ZUuOEwgwSdpPc+cLlk5jdQlqzsYzdUGCke8FJvncoO1lHZQY/+nzCzGhAjlwJJ5JZxTucwIpcZ+oKsEBXWBipfgsQ43p9sf/ccrsPrsAcvu1usJsARP11MCzUdEAFsvphKtZDSKUEpsx9OzIMIHwQaf4isHasakUv/ySllSkn8WP3r99MZMip8R4lEC9pK1W6Y1sw5KClf0pUm9JYyjkb1tDDuBoDe9+F9V6i6OPvETgWxyP/ZSPvFOhJ40e/4aCz3EvXfz1n0Q+ezt/ppmLbHkoHzsCEz1VIZq1T642ZqylNoJsjbApTQ4NSejIoV+UGBSBbEgFLMSMVAV3LO970tsFj9vSTkdLfgh4GUIHYWc2rYLTihoi0Ixwsm/hwr7UVHz7lws6PvWHgpm4fCBCXd/uNOg93r7z8MdmMsQSiYs5Ltf+HhQ3gtEoeVwG8VUEPeKIb6LdMdZ8jbN92YkiCT22Ovf+wZWxGfxcvIfOgUjFRm9pOWp4QgKWoCgNZa8veVHB9MumAjEZGlVDxdMt9mlTiqkP8UwvtUOaQkVywB8uz4+nzX33Vh2aS4IQnuUDQAlNR6OPXnA1X4wkaOWH73Eb3b0F3ybLb3udqREbhj51JZs5P5G8vdHFo1q0yFGpGr8vBd1c0kN+2WJL0BWnsz8YFMtbdgybTakMotDmK0ZDcsBytnpJo7p/x5hcfu1szTz7sgpTx5cWPaCur15fnlOVUJ8E0rKB+c16udt9/2ka11jjmH58zHByqzhaxOyt3BDoytidz15/X3N0E/OT2/OD0+ujo9CSFwyqy+06R8tu38mBaaCdAa2wdEsOTG/YXkufInBjgbjr9QYTnXFIjOOTMuBgSdDgPCqQ7uxIa3oPYVdzawRedo/1Sun0CcNFBainLu0GGxRZDn7BZEF+ZG84NA42hcRb84W0QgY2nKoYtBs/1BFNxwT1BWnGHkrpHoqoPq9Lo2TvuruizHcTvXCvBVxhI3FdRS83d60Hj3dibF2qzsrlVJzzf1TAV2IdBYu24Q0mG3voF0MWvDXTb18HvsqqIP/FJuFV7VPQkp2x4kOj/UUUvGuJ9rctSJGu/jdqwmMFyErXv9vfTrP9mm3axJu5kMtOvHj7YXpqLlzIxTP3WydTbT6euZ4xCuWg4jMtSv0UZmFi+SKEjxLmflAsecO1HOynjihl65LXXn8wL5nAK8wWCxGCr2TVyjvnPsykz7taZ5zpk7Ftv7lwtjfGdM/t7p2a933p5e7Qx2zqlZ7Lze2bs92MPDFlmYPSRAvfdv/P+Ypf/ZGexc3rC8/PrpXQ6JgdSFgx5bE+j1wfPn//n//h8AAAD//w== +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class OrdersGetRequest: + """ + Shows details for an order, by ID. + """ + def __init__(self, order_id): + self.verb = "GET" + self.path = "/v2/checkout/orders/{order_id}?".replace("{order_id}", quote(str(order_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_patch_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_patch_request.py new file mode 100644 index 00000000..631da3e6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_patch_request.py @@ -0,0 +1,29 @@ +# This class was generated on Mon, 02 Jul 2018 17:09:03 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# orders_patch_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data H4sIAAAAAAAC/+RXTW8jNwy991cIOg/sYFv0MDcjSZF+7MZN3AWKbWAzEp3R7sxQpaikRuD/Xmg0/ho33W67TVHkZOuJ1LwnUiL1qN9Ag7rUxBY5jDyIqXShzzAYdl4ctbrUP3kLgkFBqzq7kfqZojLQqtjNbCfUg5NKLU6vziez87OFIlaLyXR6dfk2jYKAxLB1bkme8r98Pf3hfLbvM6tQLamu6cG1dwpE2N3GzMkqun2PRoICRtUpgNsay1/iycmXJtbdL+ZR7fZHhmz+h64VbCWj4x08UtfRe2JBq8gjQ9oQ5YIa+jP6GgweL9Ajh999ioWPbCoIOI+tk/DX2GTVw5XA2iP3bqueg/i7m5GJQaiZO/ufinjCuKH7zy7Y7g7MS5HsYYU4wgZc/X9O1VA57117NwdrGUN4KeELtJT5Jm2JX4psaCg+300/3pQfXegfI/JqCgwNCnLQ5bubQl8gWOQh+g1xM8SmINUB9qhnK59qdxB27Z0u9Ftgl0rffk2fO6sL/T2uevCouKfK+u2ZoqWSCvsyLNRX5pEu9IQZVvlTJ4W+QrCXbb3S5RLqgAn4NTpGq0vhiIWecto/cRh02ca6Xt9kGwySF9nynvbNxoB2V8HnnF0OuA9nDoVMWgWJapLy3fXlm9wKbJsDIQXe1yvlgcVB3SvsJhgDRTYYBnq//lO9PbAv+ONBWTI1B6J64DgoWQOl1qSLSAqPAN+hKEsmNtiKqsnkTE2rqIfKmSqZpoPT2d9DHXGkNpzVkrjDF8liscv0T4rzH+juAl18PCP9gfRueCx8d/6ElKHG1/iJmfj3GXqQaphz1T8PD8guOBtF+bz9SxHIWX+sr0uIA4Eb5FhhN7M9N7kHX+RbeS91lCUMKjXznPkp6NPucyi6WSer4KkNmNdJcKFPqevYe7WJoMtbPX4fqNWFvhDxr1EqsummmcxOL3S+Q3Wpx/evxqZC84GijPPLZ/y4uS3XutDXH5zfcjr/zaMRtNfdU+SULOry1clX6y9+BwAA//8= +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class OrdersPatchRequest: + """ + Updates an order. You can update an order with `CREATED` or `APPROVED` status. You cannot update an order with `COMPLETED` status. The following attributes and objects are patchable:
  • intent. Supported operation is replace.
  • purchase_units. Supported operations are add and replace.
  • purchase_units[].custom_id. Supported operations are add and replace and remove.
  • purchase_units[].description. Supported operations are add and replace and remove.
  • purchase_units[].payee.email. Supported operations are add and replace.
  • purchase_units[].shipping_address. Supported operations are add and replace and remove.
  • purchase_units[].soft_descriptor. Supported operations are add and replace and remove.
  • purchase_units[].amount. Supported operation is replace.
+ """ + def __init__(self, order_id): + self.verb = "PATCH" + self.path = "/v2/checkout/orders/{order_id}?".replace("{order_id}", quote(str(order_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + + + def request_body(self, patch_request): + self.body = patch_request + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_validate_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_validate_request.py new file mode 100644 index 00000000..a429c997 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/orders_validate_request.py @@ -0,0 +1,32 @@ +# This class was generated on Mon, 02 Jul 2018 17:09:03 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# orders_validate_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class OrdersValidateRequest: + """ + Validates a payment method and checks it for contingencies. + """ + def __init__(self, order_id): + self.verb = "POST" + self.path = "/v2/checkout/orders/{order_id}/validate-payment-method?".replace("{order_id}", quote(str(order_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + def pay_pal_client_metadata_id(self, pay_pal_client_metadata_id): + self.headers["PayPal-Client-Metadata-Id"] = str(pay_pal_client_metadata_id) + + + + def request_body(self, order_action_request): + self.body = order_action_request + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__init__.py new file mode 100644 index 00000000..1291fc47 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__init__.py @@ -0,0 +1,8 @@ + +from paypalcheckoutsdk.payments.authorizations_capture_request import * +from paypalcheckoutsdk.payments.authorizations_get_request import * +from paypalcheckoutsdk.payments.authorizations_reauthorize_request import * +from paypalcheckoutsdk.payments.authorizations_void_request import * +from paypalcheckoutsdk.payments.captures_get_request import * +from paypalcheckoutsdk.payments.captures_refund_request import * +from paypalcheckoutsdk.payments.refunds_get_request import * \ No newline at end of file diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..19009c6a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc new file mode 100644 index 00000000..42c2f779 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc new file mode 100644 index 00000000..b57c8867 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc new file mode 100644 index 00000000..6f13bb59 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc new file mode 100644 index 00000000..1c51c441 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc new file mode 100644 index 00000000..e15f96e3 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc new file mode 100644 index 00000000..a758fc96 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc new file mode 100644 index 00000000..520f352f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_capture_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_capture_request.py new file mode 100644 index 00000000..909e7766 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_capture_request.py @@ -0,0 +1,36 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# authorizations_capture_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class AuthorizationsCaptureRequest: + """ + Captures an authorized payment, by ID. + """ + def __init__(self, authorization_id): + self.verb = "POST" + self.path = "/v2/payments/authorizations/{authorization_id}/capture?".replace("{authorization_id}", quote(str(authorization_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + + def pay_pal_request_id(self, pay_pal_request_id): + self.headers["PayPal-Request-Id"] = str(pay_pal_request_id) + + def prefer(self, prefer): + self.headers["Prefer"] = str(prefer) + + + + def request_body(self, capture): + self.body = capture + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_get_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_get_request.py new file mode 100644 index 00000000..cc6b887c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_get_request.py @@ -0,0 +1,25 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# authorizations_get_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class AuthorizationsGetRequest: + """ + Shows details for an authorized payment, by ID. + """ + def __init__(self, authorization_id): + self.verb = "GET" + self.path = "/v2/payments/authorizations/{authorization_id}?".replace("{authorization_id}", quote(str(authorization_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_reauthorize_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_reauthorize_request.py new file mode 100644 index 00000000..6287337f --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_reauthorize_request.py @@ -0,0 +1,35 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# authorizations_reauthorize_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class AuthorizationsReauthorizeRequest: + """ + Reauthorizes an authorized PayPal account payment, by ID. To ensure that funds are still available, reauthorize an authorized payment after its initial three-day honor period expires.

After the three-day honor period expires, you can reauthorize an authorized payment only once from days four to 29. If 30 days have passed since the date of the authorized payment, you must create an authorized payment instead.

A reauthorized payment itself has a new three-day honor period. You can reauthorize an authorized payment once for up to 115% of the original authorized amount and not to exceed an increase of $75 USD.

Supports the amount request parameter only. + """ + def __init__(self, authorization_id): + self.verb = "POST" + self.path = "/v2/payments/authorizations/{authorization_id}/reauthorize?".replace("{authorization_id}", quote(str(authorization_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + def pay_pal_request_id(self, pay_pal_request_id): + self.headers["PayPal-Request-Id"] = str(pay_pal_request_id) + + def prefer(self, prefer): + self.headers["Prefer"] = str(prefer) + + + + def request_body(self, reauthorize_request): + self.body = reauthorize_request + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_void_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_void_request.py new file mode 100644 index 00000000..7e68959c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/authorizations_void_request.py @@ -0,0 +1,25 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# authorizations_void_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data H4sIAAAAAAAC/2yS0YpTMRCG732KYa5jz7J4lTvZKruI7tGWBZFFpifTPdE0iZPJYix9dzltFVt7++XPMP/HbPEDbRgtUtUxif9F6lMss+fkHRqccxnE54mhxYfkXTGQBAaKA4digCL8+ckOMrUNRzWwanA3n8HnVKdoTArTwMtp0JEURiqwYo6wriE0GChrFXYzNPixsrSehDasLAXtl0eDt0yO5Zy+TbI5Zz3peMK2uGx56lxUfHxCgw8knlaBL7n4uhfxjtvx8T8ry5Ghp9ZTePnEkYWUHdzNYZ0EdOSLjdPex1TutQi1wz5XBj8xufsYGto1hcIT+FG9sEOrUtlgLymzqOeCNtYQdo+HDBc9DJnghEpOsfC/7CZF5XiMIeUc/LBv2H0rKaLBW9X8nnVMDi3294slHuShxe75ujsuX7rTU+m257p23fF6Ft99/tvkzc/Mg7JbKGktN8kx2uurV7sXvwEAAP// +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class AuthorizationsVoidRequest: + """ + Voids, or cancels, an authorized payment, by ID. You cannot void an authorized payment that has been fully captured. + """ + def __init__(self, authorization_id): + self.verb = "POST" + self.path = "/v2/payments/authorizations/{authorization_id}/void?".replace("{authorization_id}", quote(str(authorization_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/captures_get_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/captures_get_request.py new file mode 100644 index 00000000..614faf2e --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/captures_get_request.py @@ -0,0 +1,25 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# captures_get_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data H4sIAAAAAAAC/+xcbVMjN/J///8UXZN/1S1VxibsQhJeHVnYLHdh4YDNVYqj7PZM26OgkSaSxsaX2u9+JWnGnicDydrkLpkXFDXdkqdb3fp1tx7ml+ADJhQcBSGmJlOk+1MyQS84IR0qlhomRXAUXMdyriEig4xrmEgFCHmHCFJcJCRMD8YLODvpB73gHxmpxSUqTMiQ0sHR7V0veE8YkapT30mV1GmXaOIK7ZfgZpFaIbVRTEyDXvADKoZjTlXhhywKesHfaZGTG3rcxASXuLhEvjslQQoNRXB24jQyMTV0cox5zMIYjAQdy3kxCFbNY6Vw4SXb6wVXhNGF4IvgaIJckyX8nDFFUXBkVEa94FLJlJRhpIMjkXH+6c63IW38j1iiJelUCk2etlT9rRetqfvjGh83dPpVkueEsugra5xLQYumQJjITJiKWEtS0xxhphSJcAEoIvDtcgebMIEiZMjBKBQaQ9urBzoLY0ANCGPkKEICqZb2ijLanH5rvS0XeRjKiCp61jlNdW9NrIh2wxgVhoYUnF1f7L7Z//Kr1UDYvnevBpEM9YAJQ1OF9gcGEVMUmoEibQZF413bWA92wMRogEUkDJsw0t6b80ab8NXek6MyQ55VR6OgNEfBcXr5zErYNDYwpqN/ZXt7r8OMu//knzjzT8cC3FiQct6Rq2Y15eyeYPS3yx9HfhBQEQhpwCxSFiLnC5go7zvI+/5HB8Wv1t4BEYUsQb7s0f6umw8npXfpbByxGYsoshJKMLHMNIrIxLr9dYNCw3c56qh88EFkyZgUyMlSkJRjSEvELXtIDzQR3L4taG+tI/xat9kIij3DN0JFaGhoWFKbLxV6008iNOSAwbboARNweyYMKUGmyrMjlKC5exUbk+qjwcBIyXWfkZn0pZoOYpPwgZqEr1+//uYLTc64uwf9w50+XFMoRaSdLZeWmMeMU8lxQJdaybTiTWMuw/ufM2mobGVtlBRTT/kgTeHdgzIdbpz1pxlHBfSQKtLael2qpHUoDdOMRQ7ixpmBSJJ2nq3oJwoNIOfAxAw5i9xgLN2tLtBnAuIz53/E9DhTmiwOD5M6MrZxm/aeZHaMl1MrJh6BFDCmGPnEzgs7XRJSYYyfH8hqeo2l5ISiqZiNQ3wYLuPvSqk6p6rQmYhYaO0C85hMTAowiljuT/WorCFEqygkGBHgFJnQBlAAZiaWiv27FMDhBwufwPSjiOmf7ET3z3b65g6yIvbhR5nZV1u/SvCeWgQrhDEx063i/DMmAUJaD+YsZKalEdAD00b3IKIJZtwA0/A8AR9F7PpPOCuvV9Jr+KgVnlL2cUR/mZlWy3Ld4+fluf0XElzMJAsbaXqF3FSkmO+7EU2Y8IrkUJD3zLMfDai1DJnTds5M7K24ZWW/Z+IeylI31OZM3OuKxgWllqsLQCuX1U4Rd2rcvj++Ob04vgbXpYjvmLKBnJGaMZoPvojRkES965rUY/rh5vNfEqFrUdZoRWszYMTQ5mPWYKVyKhsnzCwzINIupuML+WKsaFLRICe0VCgySTkZAoNqSgY+Xn3fhxvpwcRL721ls82ebT5mgvJQZWJZ9sXbj1dncENJanvs+qTFUPRk3nJ48NXejvOBPti0MVW0myoZ2oRBTG1mFPIs8i8d/f+oB6NXo57LjkY7I1hWG7rvUo6R1XVkJ4xtf08LKLzM6iqFBWw3pZxH2cwzHwKvo9cHrQG1NZwwjvxChnPe1PC/MvUxD+zZ0rEAkvECbq/evYX9vTeHKxPM5/OVAdQktH+2Rd88mJ1+PtXHeY1lRyh3jBfT3/pUTfmc1NT8/c3NZeGGy+zWrHHeF9JAEa+I759bamU3uE5AmxVb8z05UQ6++frrZYL/ZqeoMTWpGWm3bCCKQIi58ayjZwKTMZtmMtN8AVHFxJoSFIaFuog6fhpe2/LLgf9VLqGu+RAKdLKh1mwqXH4xsH13C5Xqj/0Hq8bONgLUdRhTgk1b6IK+MseS1LRIGadBqk16/yrsyLEtb1rWlZZ525mhpBpQm7yq8Jsd0WPO4WIC9lUtYnJ+UY0sBWV9tNfZeNcPe47QboCTTBtw9Z0rdYuagPNK+88M93XVxOIR1cSirlpO2YxqUrjFvET6ZZUtqbjOv1xUcF5U9a0qfZt+tV6ylETk16BqolUY25RtHZhPFE4ttF2RljzLE+BSidzG/j3GkDUwg7UixbEAy9gCBm+mMHAtNjwjzm16tCbTamZZT+X4NusMpTD0YHZJhDJiYgpuKr/A2vyYCVSL0/y1FeEbrLZUXxgSTbF9sD/PuGFpplKpCZbrkOfIOJw+GBLaQgS8Oj87P92BS1QGLgQd2Xw9QWNtt+pDWuOU4FsZMdJPJjX7e28Odl4oOTP1zNo8nVT/5vG5mcsjcN4HVqxnjcThZkbi7hmYIWR1G8s/bxO9LgStD79SUC38FpTNhd9V+w1jzDp/S9HE1wZVdaTL1JrnScA05QtfT3tRwW27EFgtUISk/wIfr850D7T9Cceyz6U63G1A9V8m8qS2xFei1LOmaYP7e8THdI1425Xr7rl1ii8yrpvVSo3R1SxdzdLVLF3N0tUsXc3S1SxdzdLVLF3N0tUsXc2ypZplLSIxw2uQlFOamOQLEsveOEhcE+ek4FJJ47fEWjaAXJNhWm5S2gtq4bZoQDPiduau2oGcTEhRVN9u9adSoCGY25E7z0961DbSUlykyPuhTAaZHsxpjGmqB0maDjSFmWJmMfBy7q7ev7P9sB0xnWaGhiEamkrVyHPb2OtBL5TCl4Ol82+hnLkxLI7tlM5BvxDSaYMmq+q1JD112q0mMTANxNmUjbk7rgneZiWf2bj7F/4EVxQSm1m94FtFeB/J+fqpoJaNh+NS48akWNOu5RSruzJgJ0DRrNhJ3t7Z/NMHq/mU4ApNy6UBytlD5dmlgzw1TlOdogXYFt5ZIzKkEibyHfP8GL+R1qtnpAxMlExcrF6eYTYSUEjnKL/pnPpvmrJaZiqkYfHCqlUbvP/B0/ufkUO7FbH2oWny/lRD82tuNuRnxFa3WtxU6MPpzxmbISc/LexMyAQzBQ5431vplddfxsfP4uKDVMt7AU4Anxva3zISvjyAiE2Z0UViqdy1ivwFy/sEkm3oAGQFZ9svAk2V1HrYch2oxuguBXWXgrpLQX/YS0Fr0EGQacOGCrlDhg4ZOmT4syGDr/eHE6LaqlOJ3CFDhwwdMvxhkeGSo5lIlcA7alm9SHOuxYLa0m+Ns369rWjpJ70ywjoEkXYXeBLmLuLqnuWOlbwnhVNy/NxejYtmTy7mHHYfWujwscPHDh83kjkVK9vfoqbWDKqZPK3Jm8pf9Slfr4d5LMGvcedu7m/nW/iYZHzCOPdkqSJSfqN22ZdpQK4l3As5FxZEbEMnw/ZRgxJkfIhRpEhXo0Od0xwM5k4AoHdid/Hb9YG8z+d/8eFjCkbC4ZvSnUzn3Mi5nFMEY5pI5c+j7B8crGuFE5NvsNRvwP+1cfsdNJuKPryXc5qR6rle/lK4nbIYhpTaMJbgA0uyBDiJqYn9RBBV7a1V9w/eNK6T5tvBMCNVYCK6bxdkwg1S9Fwp868E/L7fsSh8uH5LvUpfd9/eObmCs5MCdO1MgAT1PUV2gLTfG3VWyHtgGLpgnIOSdUER5bt+mablCQAVuUSFUT7s9X4aFLk3jPkCSIRq4Qzrwj2kSqaKkUG1gJlVWLi1YQsfr/dt30z7i6HuGFFxNVRnfFOrxc+o+0obai2pTRu3y3K6LKfLcv5gWc7dRs8lWP/wrG1tutcc+tq/7MSnVetEH0ZLfl2FEmt9trZGl1zV7YOWItS1s0JLUtsdDMuCebx4TGobhkaXpx9Ozj58N7IwPDo5/XB2ejLqv9SxrSyNWr9TVqV33yn77/1O2d2nXvDWn4jObY1pylnowesn76DvjUnP/TczjoLvTm8C/83R4CgYzPYHxbegBsUnUQe/rL4v+inoBdf3LF1KcPqQUmgo8tPewmZwtL+39+n//gMAAP// +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class CapturesGetRequest: + """ + Shows details for a captured payment, by ID. + """ + def __init__(self, capture_id): + self.verb = "GET" + self.path = "/v2/payments/captures/{capture_id}?".replace("{capture_id}", quote(str(capture_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/captures_refund_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/captures_refund_request.py new file mode 100644 index 00000000..fbc6af88 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/captures_refund_request.py @@ -0,0 +1,35 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# captures_refund_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class CapturesRefundRequest: + """ + Refunds a captured payment, by ID. For a full refund, include an empty payload in the JSON request body. For a partial refund, include an amount object in the JSON request body. + """ + def __init__(self, capture_id): + self.verb = "POST" + self.path = "/v2/payments/captures/{capture_id}/refund?".replace("{capture_id}", quote(str(capture_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + def pay_pal_request_id(self, pay_pal_request_id): + self.headers["PayPal-Request-Id"] = str(pay_pal_request_id) + + def prefer(self, prefer): + self.headers["Prefer"] = str(prefer) + + + + def request_body(self, refund_request): + self.body = refund_request + return self diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/refunds_get_request.py b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/refunds_get_request.py new file mode 100644 index 00000000..7dacf0cb --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/refunds_get_request.py @@ -0,0 +1,25 @@ +# This class was generated on Tue, 10 Jul 2018 10:40:35 PDT by version 0.1.0-dev+0ee05a-dirty of Braintree SDK Generator +# refunds_get_request.py +# @version 0.1.0-dev+0ee05a-dirty +# @type request +# @data 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 +# DO NOT EDIT +import paypalhttp + +try: + from urllib import quote # Python 2.X +except ImportError: + from urllib.parse import quote # Python 3+ + +class RefundsGetRequest: + """ + Shows details for a refund, by ID. + """ + def __init__(self, refund_id): + self.verb = "GET" + self.path = "/v2/payments/refunds/{refund_id}?".replace("{refund_id}", quote(str(refund_id))) + self.headers = {} + self.headers["Content-Type"] = "application/json" + self.body = None + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/LICENSE b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/LICENSE new file mode 100644 index 00000000..1c8dec73 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2009-2021 PayPal, Inc. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/METADATA new file mode 100644 index 00000000..cff7e049 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/METADATA @@ -0,0 +1,31 @@ +Metadata-Version: 2.1 +Name: paypalhttp +Version: 1.0.1 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: PayPal +License: MIT +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +License-File: LICENSE +Requires-Dist: requests (>=2.0.0) +Requires-Dist: six (>=1.0.0) +Requires-Dist: pyopenssl (>=0.15) + + + PayPalHttp is a generic http client designed to be used with code-generated projects. + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/RECORD new file mode 100644 index 00000000..35155011 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/RECORD @@ -0,0 +1,36 @@ +paypalhttp-1.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +paypalhttp-1.0.1.dist-info/LICENSE,sha256=KjEXrKbzR6R8sMQiq8wP7mpyYEx9FOy3bUCMo5jKjw4,1061 +paypalhttp-1.0.1.dist-info/METADATA,sha256=yP4FofKT5usj_TRv8MV2fkCVThD18YfewqOPZ2HYSDQ,1058 +paypalhttp-1.0.1.dist-info/RECORD,, +paypalhttp-1.0.1.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +paypalhttp-1.0.1.dist-info/top_level.txt,sha256=eQq3_W8-SYoKpUqtGXzt-012HAfVLOhAcuX331TouMs,55 +paypalhttp/__init__.py,sha256=FcnJGWPGodSj1p9SAtqTmyKFY9C1LQBXcHSaAke2EpI,257 +paypalhttp/__pycache__/__init__.cpython-312.pyc,, +paypalhttp/__pycache__/encoder.cpython-312.pyc,, +paypalhttp/__pycache__/environment.cpython-312.pyc,, +paypalhttp/__pycache__/file.cpython-312.pyc,, +paypalhttp/__pycache__/http_client.cpython-312.pyc,, +paypalhttp/__pycache__/http_error.cpython-312.pyc,, +paypalhttp/__pycache__/http_response.cpython-312.pyc,, +paypalhttp/encoder.py,sha256=1G2S4diP3N2PMpBxoxa4aPaGMXr-01tFruoBL2Jhifo,1877 +paypalhttp/environment.py,sha256=b6s-xJtnkeqHKjCoGQ1A_ZfPYIOjJNtsR4EFcRlAotk,95 +paypalhttp/file.py,sha256=KPSm5FVc4iUkmboPhcIbhsUhMkjryrS8eK9gTI2ydrY,830 +paypalhttp/http_client.py,sha256=FTfpEkqHgI_uPTpM2Yx7_TtESJzql2WNRxWKDstz6Ho,2764 +paypalhttp/http_error.py,sha256=yNiy9zqdx61fXcQ-mG_ipW4HzY3yWPQ650yVqpHupW8,266 +paypalhttp/http_response.py,sha256=z2puSQ6c0lsyNf6_i02KDVF9Yz2V9kkJp1mRAB5Hz8s,1626 +paypalhttp/serializers/__init__.py,sha256=6sU5eS1wccc0velxmh5nTLu1j3xa7Kbu4NDkO4dJg8w,303 +paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc,, +paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc,, +paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc,, +paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc,, +paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc,, +paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc,, +paypalhttp/serializers/form_encoded_serializer.py,sha256=yenWlU7LMjJS9u16jZcQkwNUsWg4e-Iw_I0BnCQZYbc,471 +paypalhttp/serializers/form_part.py,sha256=Br8mH_AWHqxKikcUE1KCVL6C4Dd0dVjNeAPKB9ggqCw,267 +paypalhttp/serializers/json_serializer.py,sha256=I0ZWikiLuDCJfdCgDzxTH4-SxS8fkpfJdbhtR830ZV4,222 +paypalhttp/serializers/multipart_serializer.py,sha256=LBppTIgiO8XmPFLT_d2nOE6GLy65r-W2xJJ5ykqcIbY,2986 +paypalhttp/serializers/text_serializer.py,sha256=Mqu7iGJwDzbgxW5N1VVmwkK06CA305CX-ySsdEKcUG4,185 +paypalhttp/testutils/__init__.py,sha256=vSeXkcJlzJ7ZYwMEvTXSEp3brDRBReY8hSi0WpiZn7g,57 +paypalhttp/testutils/__pycache__/__init__.cpython-312.pyc,, +paypalhttp/testutils/__pycache__/testharness.cpython-312.pyc,, +paypalhttp/testutils/testharness.py,sha256=35ihiUTLdM5NXz9YzgqQdYJELo9pqz3_9v4mI2_Z9zM,744 diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/WHEEL new file mode 100644 index 00000000..5bad85fd --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/top_level.txt new file mode 100644 index 00000000..db24f036 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp-1.0.1.dist-info/top_level.txt @@ -0,0 +1,3 @@ +paypalhttp +paypalhttp/serializers +paypalhttp/testutils diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__init__.py new file mode 100644 index 00000000..ce154a94 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__init__.py @@ -0,0 +1,6 @@ +from paypalhttp.environment import Environment +from paypalhttp.file import File +from paypalhttp.http_client import HttpClient +from paypalhttp.http_response import HttpResponse +from paypalhttp.http_error import HttpError +from paypalhttp.serializers import * diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..cc886ec7 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc new file mode 100644 index 00000000..8991964f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc new file mode 100644 index 00000000..1485f853 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc new file mode 100644 index 00000000..7dede435 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc new file mode 100644 index 00000000..0fe5a094 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc new file mode 100644 index 00000000..0f774bb9 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc new file mode 100644 index 00000000..b7db7561 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/encoder.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/encoder.py new file mode 100644 index 00000000..29b27f9d --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/encoder.py @@ -0,0 +1,54 @@ +import re +import os + +class Encoder(object): + + def __init__(self, encoders): + self.encoders = encoders + + def serialize_request(self, httprequest): + if hasattr(httprequest, "headers"): + if "content-type" in httprequest.headers: + contenttype = httprequest.headers["content-type"] + enc = self._encoder(contenttype) + if enc: + return enc.encode(httprequest) + else: + message = "Unable to serialize request with Content-Type {0}. Supported encodings are {1}".format( + contenttype, self.supported_encodings()) + print(message) + raise IOError(message) + else: + message = "Http request does not have content-type header set" + print(message) + raise IOError(message) + + def deserialize_response(self, response_body, headers): + if headers and "content-type" in headers: + contenttype = headers["content-type"].lower() + enc = self._encoder(contenttype) + if enc: + return enc.decode(response_body) + else: + message = "Unable to deserialize response with content-type {0}. Supported decodings are {1}".format( + contenttype, self.supported_encodings()) + print(message) + raise IOError(message) + else: + message = "Http response does not have content-type header set" + print(message) + raise IOError(message) + + + def supported_encodings(self): + return [enc.content_type() for enc in self.encoders] + + def _encoder(self, content_type): + for enc in self.encoders: + if re.match(enc.content_type(), content_type) is not None: + return enc + return None + + + + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/environment.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/environment.py new file mode 100644 index 00000000..6e6feade --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/environment.py @@ -0,0 +1,4 @@ +class Environment(object): + def __init__(self, base_url): + self.base_url = base_url + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/file.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/file.py new file mode 100644 index 00000000..291432a2 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/file.py @@ -0,0 +1,35 @@ +class File(object): + + @classmethod + def fromhandle(cls, handle): + return File(handle.name, handle.mode) + + def __init__(self, name, mode='rb'): + self._handle = None + self._data = None + + self.mode = mode + self.closed = False + self.name = name + + def read(self): + self.open() + + if self._data: + return self._data + else: + self._data = self._handle.read() + return self._data + + def close(self): + if self._handle: + self._handle.close() + self._handle = None + self.closed = True + + def open(self): + if not self._handle: + if not self.closed: + self._handle = open(self.name, self.mode) + else: + raise IOError('Open of closed file') diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_client.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_client.py new file mode 100644 index 00000000..f1422db1 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_client.py @@ -0,0 +1,81 @@ +import requests +import copy + +from paypalhttp.encoder import Encoder +from paypalhttp.http_response import HttpResponse +from paypalhttp.http_error import HttpError +from paypalhttp.serializers import Json, Text, Multipart, FormEncoded + + +class HttpClient(object): + + def __init__(self, environment): + self._injectors = [] + self.environment = environment + self.encoder = Encoder([Json(), Text(), Multipart(), FormEncoded()]) + + def get_user_agent(self): + return "Python HTTP/1.1" + + def get_timeout(self): + return 30 + + def add_injector(self, injector): + if injector and '__call__' in dir(injector): + self._injectors.append(injector) + else: + message = "injector must be a function or implement the __call__ method" + print(message) + raise TypeError(message) + + def execute(self, request): + reqCpy = copy.deepcopy(request) + + try: + getattr(reqCpy, 'headers') + except AttributeError: + reqCpy.headers = {} + + for injector in self._injectors: + injector(reqCpy) + + data = None + + formatted_headers = self.format_headers(reqCpy.headers) + + if "user-agent" not in formatted_headers: + reqCpy.headers["user-agent"] = self.get_user_agent() + + if hasattr(reqCpy, 'body') and reqCpy.body is not None: + raw_headers = reqCpy.headers + reqCpy.headers = formatted_headers + data = self.encoder.serialize_request(reqCpy) + reqCpy.headers = self.map_headers(raw_headers, formatted_headers) + + resp = requests.request(method=reqCpy.verb, + url=self.environment.base_url + reqCpy.path, + headers=reqCpy.headers, + data=data) + + return self.parse_response(resp) + + def format_headers(self, headers): + return dict((k.lower(), v) for k, v in headers.items()) + + def map_headers(self, raw_headers, formatted_headers): + for header_name in raw_headers: + if header_name.lower() in formatted_headers: + raw_headers[header_name] = formatted_headers[header_name.lower()] + return raw_headers + + def parse_response(self, response): + status_code = response.status_code + + if 200 <= status_code <= 299: + body = "" + if response.text and (len(response.text) > 0 and response.text != 'None'): + body = self.encoder.deserialize_response(response.text, self.format_headers(response.headers)) + + return HttpResponse(body, response.status_code, response.headers) + else: + raise HttpError(response.text, response.status_code, response.headers) diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_error.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_error.py new file mode 100644 index 00000000..793886e9 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_error.py @@ -0,0 +1,10 @@ +class HttpError(IOError): + + def __init__(self, message, status_code, headers): + IOError.__init__(self) + self.message = message + self.status_code = status_code + self.headers = headers + + def __str__(self): + return self.message diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_response.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_response.py new file mode 100644 index 00000000..ac44eed8 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/http_response.py @@ -0,0 +1,66 @@ +def setattr_mixed(dest, key, value): + if isinstance(dest, list): + dest.append(value) + else: + setattr(dest, key, value) + + +def construct_object(name, data, cls=object): + if isinstance(data, dict): + iterator = iter(data) + dest = Result(data) + elif isinstance(data, list): + iterator = range(len(data)) + dest = [] + else: + return data + + for k in iterator: + v = data[k] + + k = str(k).replace("-", "_").lower() + if isinstance(v, dict): + setattr_mixed(dest, k, construct_object(k, v)) + elif isinstance(v, list): + l = [] + for i in range(len(v)): + setattr_mixed(l, i, construct_object(k, v[i])) + + setattr_mixed(dest, k, l) + else: + setattr_mixed(dest, k, v) + + return dest + + +class Result(object): + + def __init__(self, data): + self._dict = data; + + def dict(self): + return self._dict + + def __contains__(self, key): + return key in self._dict + + def __getitem__(self, key): + return self._dict[key] + + +class HttpResponse(object): + + def __init__(self, data, status_code, headers=None): + if headers is None: + headers = {} + + self.status_code = status_code + self.headers = headers + if data and len(data) > 0: + if isinstance(data, str): + self.result = data + elif isinstance(data, dict) or isinstance(data, list): + self.result = construct_object('Result', data) # todo: pass through response type + else: + self.result = None + diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__init__.py new file mode 100644 index 00000000..eb9cd973 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__init__.py @@ -0,0 +1,5 @@ +from paypalhttp.serializers.form_encoded_serializer import FormEncoded +from paypalhttp.serializers.json_serializer import Json +from paypalhttp.serializers.text_serializer import Text +from paypalhttp.serializers.multipart_serializer import Multipart +from paypalhttp.serializers.form_part import FormPart diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4c8cef5e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc new file mode 100644 index 00000000..4266450c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc new file mode 100644 index 00000000..6adea224 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc new file mode 100644 index 00000000..cd5aabfa Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc new file mode 100644 index 00000000..45a5703b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc new file mode 100644 index 00000000..077e9b93 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/form_encoded_serializer.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/form_encoded_serializer.py new file mode 100644 index 00000000..556d387d --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/form_encoded_serializer.py @@ -0,0 +1,18 @@ +try: + from urllib import quote +except ImportError: + from urllib.parse import quote + +class FormEncoded: + def encode(self, request): + params = [] + for k, v in request.body.items(): + params.append("{0}={1}".format(k, quote(v))) + + return '&'.join(params) + + def decode(self, data): + raise IOError("FormEncoded does not support deserialization") + + def content_type(self): + return "application/x-www-form-urlencoded" diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/form_part.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/form_part.py new file mode 100644 index 00000000..ea4ba193 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/form_part.py @@ -0,0 +1,8 @@ +class FormPart(object): + + def __init__(self, value, headers): + self.value = value + self.headers = {} + + for key in headers: + self.headers['-'.join(map(lambda word: word[0].upper() + word[1:], key.lower().split('-')))] = headers[key] diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/json_serializer.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/json_serializer.py new file mode 100644 index 00000000..f75a6126 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/json_serializer.py @@ -0,0 +1,13 @@ +import json + + +class Json: + + def encode(self, request): + return json.dumps(request.body) + + def decode(self, data): + return json.loads(data) + + def content_type(self): + return "application/json" diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/multipart_serializer.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/multipart_serializer.py new file mode 100644 index 00000000..00586af8 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/multipart_serializer.py @@ -0,0 +1,85 @@ +import time +import os + +from paypalhttp import File +from paypalhttp.encoder import Encoder +from paypalhttp.serializers.form_part import FormPart + +from paypalhttp.serializers import Json, Text, FormEncoded + +CRLF = "\r\n" + +class FormPartRequest: + pass + +class Multipart: + + def encode(self, request): + boundary = str(time.time()).replace(".", "") + request.headers["content-type"] = "multipart/form-data; boundary=" + boundary + params = [] + form_params = [] + file_params = [] + for k, v in request.body.items(): + if isinstance(v, File): + file_params.append(self.add_file_part(k, v)) + elif isinstance(v, FormPart): + form_params.append(self.add_form_part(k, v)) + else: # It's a regular form param + form_params.append(self.add_form_field(k, v)) + + params = form_params + file_params + data = "--" + boundary + CRLF + ("--" + boundary + CRLF).join(params) + CRLF + "--" + boundary + "--" + + return data + + def decode(self, data): + raise IOError('Multipart does not support deserialization.') + + def content_type(self): + return "multipart/.*" + + def add_form_field(self, key, value): + return "Content-Disposition: form-data; name=\"{}\"{}{}{}{}".format(key, CRLF, CRLF, value, CRLF) + + def add_form_part(self, key, formPart): + retValue = "Content-Disposition: form-data; name=\"{}\"".format(key) + formatted_headers = self.format_headers(formPart.headers) + if formatted_headers["content-type"] == "application/json": + retValue += "; filename=\"{}.json\"".format(key) + retValue += CRLF + + for key in formPart.headers: + retValue += "{}: {}{}".format(key, formPart.headers[key], CRLF) + + retValue += CRLF + + req = FormPartRequest() + req.headers = formatted_headers + req.body = formPart.value + retValue += Encoder([Json(), Text(), FormEncoded()]).serialize_request(req) + + retValue += CRLF + return retValue + + def add_file_part(self, key, f): + mime_type = self.mime_type_for_filename(os.path.basename(f.name)) + s = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"{}".format(key, os.path.basename(f.name), CRLF) + return s + "Content-Type: {}{}{}{}{}".format(mime_type, CRLF, CRLF, f.read(), CRLF) + + def format_headers(self, headers): + if headers: + return dict((k.lower(), v) for k, v in headers.items()) + + def mime_type_for_filename(self, filename): + _, extension = os.path.splitext(filename) + if extension == ".jpeg" or extension == ".jpg": + return "image/jpeg" + elif extension == ".png": + return "image/png" + elif extension == ".gif": + return "image/gif" + elif extension == ".pdf": + return "application/pdf" + else: + return "application/octet-stream" diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/text_serializer.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/text_serializer.py new file mode 100644 index 00000000..588b43e6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/text_serializer.py @@ -0,0 +1,10 @@ +class Text: + + def encode(self, request): + return str(request.body) + + def decode(self, data): + return str(data) + + def content_type(self): + return "text/.*" diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__init__.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__init__.py new file mode 100644 index 00000000..fde2a3ab --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__init__.py @@ -0,0 +1 @@ +from paypalhttp.testutils.testharness import TestHarness diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..96efd2f4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__pycache__/testharness.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__pycache__/testharness.cpython-312.pyc new file mode 100644 index 00000000..9579b571 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/__pycache__/testharness.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/testharness.py b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/testharness.py new file mode 100644 index 00000000..fe0fc09f --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/paypalhttp/testutils/testharness.py @@ -0,0 +1,26 @@ +import responses +import unittest +import json +import paypalhttp + +class TestHarness(unittest.TestCase): + + def environment(self): + return paypalhttp.Environment("http://localhost") + + def stub_request_with_empty_reponse(self, request): + self.stub_request_with_response(request) + + + def stub_request_with_response(self, request, response_body="", status=200, content_type="application/json"): + body = None + if response_body: + if isinstance(response_body, str): + body = response_body + else: + body = json.dumps(response_body) + + + responses.add(request.verb, self.environment().base_url + request.path, body=body, content_type=content_type, status=status) + + diff --git a/Backend/venv/lib/python3.12/site-packages/png.py b/Backend/venv/lib/python3.12/site-packages/png.py new file mode 100644 index 00000000..868bce4c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/png.py @@ -0,0 +1,2372 @@ +#!/usr/bin/env python + +# png.py - PNG encoder/decoder in pure Python +# +# Copyright (C) 2006 Johann C. Rocholl +# Portions Copyright (C) 2009 David Jones +# And probably portions Copyright (C) 2006 Nicko van Someren +# +# Original concept by Johann C. Rocholl. +# +# LICENCE (MIT) +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +The ``png`` module can read and write PNG files. + +Installation and Overview +------------------------- + +``pip install pypng`` + +For help, type ``import png; help(png)`` in your python interpreter. + +A good place to start is the :class:`Reader` and :class:`Writer` classes. + +Coverage of PNG formats is fairly complete; +all allowable bit depths (1/2/4/8/16/24/32/48/64 bits per pixel) and +colour combinations are supported: + +- greyscale (1/2/4/8/16 bit); +- RGB, RGBA, LA (greyscale with alpha) with 8/16 bits per channel; +- colour mapped images (1/2/4/8 bit). + +Interlaced images, +which support a progressive display when downloading, +are supported for both reading and writing. + +A number of optional chunks can be specified (when writing) +and understood (when reading): ``tRNS``, ``bKGD``, ``gAMA``. + +The ``sBIT`` chunk can be used to specify precision for +non-native bit depths. + +Requires Python 3.5 or higher. +Installation is trivial, +but see the ``README.txt`` file (with the source distribution) for details. + +Full use of all features will need some reading of the PNG specification +http://www.w3.org/TR/2003/REC-PNG-20031110/. + +The package also comes with command line utilities. + +- ``pripamtopng`` converts + `Netpbm `_ PAM/PNM files to PNG; +- ``pripngtopam`` converts PNG to file PAM/PNM. + +There are a few more for simple PNG manipulations. + +Spelling and Terminology +------------------------ + +Generally British English spelling is used in the documentation. +So that's "greyscale" and "colour". +This not only matches the author's native language, +it's also used by the PNG specification. + +Colour Models +------------- + +The major colour models supported by PNG (and hence by PyPNG) are: + +- greyscale; +- greyscale--alpha; +- RGB; +- RGB--alpha. + +Also referred to using the abbreviations: L, LA, RGB, RGBA. +Each letter codes a single channel: +*L* is for Luminance or Luma or Lightness (greyscale images); +*A* stands for Alpha, the opacity channel +(used for transparency effects, but higher values are more opaque, +so it makes sense to call it opacity); +*R*, *G*, *B* stand for Red, Green, Blue (colour image). + +Lists, arrays, sequences, and so on +----------------------------------- + +When getting pixel data out of this module (reading) and +presenting data to this module (writing) there are +a number of ways the data could be represented as a Python value. + +The preferred format is a sequence of *rows*, +which each row being a sequence of *values*. +In this format, the values are in pixel order, +with all the values from all the pixels in a row +being concatenated into a single sequence for that row. + +Consider an image that is 3 pixels wide by 2 pixels high, and each pixel +has RGB components: + +Sequence of rows:: + + list([R,G,B, R,G,B, R,G,B], + [R,G,B, R,G,B, R,G,B]) + +Each row appears as its own list, +but the pixels are flattened so that three values for one pixel +simply follow the three values for the previous pixel. + +This is the preferred because +it provides a good compromise between space and convenience. +PyPNG regards itself as at liberty to replace any sequence type with +any sufficiently compatible other sequence type; +in practice each row is an array (``bytearray`` or ``array.array``). + +To allow streaming the outer list is sometimes +an iterator rather than an explicit list. + +An alternative format is a single array holding all the values. + +Array of values:: + + [R,G,B, R,G,B, R,G,B, + R,G,B, R,G,B, R,G,B] + +The entire image is one single giant sequence of colour values. +Generally an array will be used (to save space), not a list. + +The top row comes first, +and within each row the pixels are ordered from left-to-right. +Within a pixel the values appear in the order R-G-B-A +(or L-A for greyscale--alpha). + +There is another format, which should only be used with caution. +It is mentioned because it is used internally, +is close to what lies inside a PNG file itself, +and has some support from the public API. +This format is called *packed*. +When packed, each row is a sequence of bytes (integers from 0 to 255), +just as it is before PNG scanline filtering is applied. +When the bit depth is 8 this is the same as a sequence of rows; +when the bit depth is less than 8 (1, 2 and 4), +several pixels are packed into each byte; +when the bit depth is 16 each pixel value is decomposed into 2 bytes +(and `packed` is a misnomer). +This format is used by the :meth:`Writer.write_packed` method. +It isn't usually a convenient format, +but may be just right if the source data for +the PNG image comes from something that uses a similar format +(for example, 1-bit BMPs, or another PNG file). +""" + +__version__ = "0.20220715.0" + +import collections +import io # For io.BytesIO +import itertools +import math +# http://www.python.org/doc/2.4.4/lib/module-operator.html +import operator +import re +import struct +import sys +# http://www.python.org/doc/2.4.4/lib/module-warnings.html +import warnings +import zlib + +from array import array + + +__all__ = ['Image', 'Reader', 'Writer', 'write_chunks', 'from_array'] + + +# The PNG signature. +# http://www.w3.org/TR/PNG/#5PNG-file-signature +signature = struct.pack('8B', 137, 80, 78, 71, 13, 10, 26, 10) + +# The xstart, ystart, xstep, ystep for the Adam7 interlace passes. +adam7 = ((0, 0, 8, 8), + (4, 0, 8, 8), + (0, 4, 4, 8), + (2, 0, 4, 4), + (0, 2, 2, 4), + (1, 0, 2, 2), + (0, 1, 1, 2)) + + +def adam7_generate(width, height): + """ + Generate the coordinates for the reduced scanlines + of an Adam7 interlaced image + of size `width` by `height` pixels. + + Yields a generator for each pass, + and each pass generator yields a series of (x, y, xstep) triples, + each one identifying a reduced scanline consisting of + pixels starting at (x, y) and taking every xstep pixel to the right. + """ + + for xstart, ystart, xstep, ystep in adam7: + if xstart >= width: + continue + yield ((xstart, y, xstep) for y in range(ystart, height, ystep)) + + +# Models the 'pHYs' chunk (used by the Reader) +Resolution = collections.namedtuple('_Resolution', 'x y unit_is_meter') + + +def group(s, n): + return list(zip(* [iter(s)] * n)) + + +def isarray(x): + return isinstance(x, array) + + +def check_palette(palette): + """ + Check a palette argument (to the :class:`Writer` class) for validity. + Returns the palette as a list if okay; + raises an exception otherwise. + """ + + # None is the default and is allowed. + if palette is None: + return None + + p = list(palette) + if not (0 < len(p) <= 256): + raise ProtocolError( + "a palette must have between 1 and 256 entries," + " see https://www.w3.org/TR/PNG/#11PLTE") + seen_triple = False + for i, t in enumerate(p): + if len(t) not in (3, 4): + raise ProtocolError( + "palette entry %d: entries must be 3- or 4-tuples." % i) + if len(t) == 3: + seen_triple = True + if seen_triple and len(t) == 4: + raise ProtocolError( + "palette entry %d: all 4-tuples must precede all 3-tuples" % i) + for x in t: + if int(x) != x or not(0 <= x <= 255): + raise ProtocolError( + "palette entry %d: " + "values must be integer: 0 <= x <= 255" % i) + return p + + +def check_sizes(size, width, height): + """ + Check that these arguments, if supplied, are consistent. + Return a (width, height) pair. + """ + + if not size: + return width, height + + if len(size) != 2: + raise ProtocolError( + "size argument should be a pair (width, height) instead is %r" % (size,)) + if width is not None and width != size[0]: + raise ProtocolError( + "size[0] (%r) and width (%r) should match when both are used." + % (size[0], width)) + if height is not None and height != size[1]: + raise ProtocolError( + "size[1] (%r) and height (%r) should match when both are used." + % (size[1], height)) + return size + + +def check_color(c, greyscale, which): + """ + Checks that a colour argument for transparent or background options + is the right form. + Returns the colour + (which, if it's a bare integer, is "corrected" to a 1-tuple). + """ + + if c is None: + return c + if greyscale: + try: + len(c) + except TypeError: + c = (c,) + if len(c) != 1: + raise ProtocolError("%s for greyscale must be 1-tuple" % which) + if not is_natural(c[0]): + raise ProtocolError( + "%s colour for greyscale must be integer" % which) + else: + if not (len(c) == 3 and + is_natural(c[0]) and + is_natural(c[1]) and + is_natural(c[2])): + raise ProtocolError( + "%s colour must be a triple of integers" % which) + return c + + +class Error(Exception): + def __str__(self): + return self.__class__.__name__ + ': ' + ' '.join(self.args) + + +class FormatError(Error): + """ + Problem with input file format. + In other words, PNG file does not conform to + the specification in some way and is invalid. + """ + + +class ProtocolError(Error): + """ + Problem with the way the programming interface has been used, + or the data presented to it. + """ + + +class ChunkError(FormatError): + pass + + +class Default: + """The default for the greyscale parameter.""" + + +class Writer: + """ + PNG encoder in pure Python. + """ + + def __init__(self, width=None, height=None, + size=None, + greyscale=Default, + alpha=False, + bitdepth=8, + palette=None, + transparent=None, + background=None, + gamma=None, + compression=None, + interlace=False, + planes=None, + colormap=None, + maxval=None, + chunk_limit=2**20, + x_pixels_per_unit=None, + y_pixels_per_unit=None, + unit_is_meter=False): + """ + Create a PNG encoder object. + + Arguments: + + width, height + Image size in pixels, as two separate arguments. + size + Image size (w,h) in pixels, as single argument. + greyscale + Pixels are greyscale, not RGB. + alpha + Input data has alpha channel (RGBA or LA). + bitdepth + Bit depth: from 1 to 16 (for each channel). + palette + Create a palette for a colour mapped image (colour type 3). + transparent + Specify a transparent colour (create a ``tRNS`` chunk). + background + Specify a default background colour (create a ``bKGD`` chunk). + gamma + Specify a gamma value (create a ``gAMA`` chunk). + compression + zlib compression level: 0 (none) to 9 (more compressed); + default: -1 or None. + interlace + Create an interlaced image. + chunk_limit + Write multiple ``IDAT`` chunks to save memory. + x_pixels_per_unit + Number of pixels a unit along the x axis (write a + `pHYs` chunk). + y_pixels_per_unit + Number of pixels a unit along the y axis (write a + `pHYs` chunk). Along with `x_pixel_unit`, this gives + the pixel size ratio. + unit_is_meter + `True` to indicate that the unit (for the `pHYs` + chunk) is metre. + + The image size (in pixels) can be specified either by using the + `width` and `height` arguments, or with the single `size` + argument. + If `size` is used it should be a pair (*width*, *height*). + + The `greyscale` argument indicates whether input pixels + are greyscale (when true), or colour (when false). + The default is true unless `palette=` is used. + + The `alpha` argument (a boolean) specifies + whether input pixels have an alpha channel (or not). + + `bitdepth` specifies the bit depth of the source pixel values. + Each channel may have a different bit depth. + Each source pixel must have values that are + an integer between 0 and ``2**bitdepth-1``, where + `bitdepth` is the bit depth for the corresponding channel. + For example, 8-bit images have values between 0 and 255. + PNG only stores images with bit depths of + 1,2,4,8, or 16 (the same for all channels). + When `bitdepth` is not one of these values or where + channels have different bit depths, + the next highest valid bit depth is selected, + and an ``sBIT`` (significant bits) chunk is generated + that specifies the original precision of the source image. + In this case the supplied pixel values will be rescaled to + fit the range of the selected bit depth. + + The PNG file format supports many bit depth / colour model + combinations, but not all. + The details are somewhat arcane + (refer to the PNG specification for full details). + Briefly: + Bit depths < 8 (1,2,4) are only allowed with greyscale and + colour mapped images; + colour mapped images cannot have bit depth 16. + + For colour mapped images + (in other words, when the `palette` argument is specified) + the `bitdepth` argument must match one of + the valid PNG bit depths: 1, 2, 4, or 8. + (It is valid to have a PNG image with a palette and + an ``sBIT`` chunk, but the meaning is slightly different; + it would be awkward to use the `bitdepth` argument for this.) + + The `palette` option, when specified, + causes a colour mapped image to be created: + the PNG colour type is set to 3; + `greyscale` must not be true; `alpha` must not be true; + `transparent` must not be set. + The bit depth must be 1,2,4, or 8. + When a colour mapped image is created, + the pixel values are palette indexes and + the `bitdepth` argument specifies the size of these indexes + (not the size of the colour values in the palette). + + The palette argument value should be a sequence of 3- or + 4-tuples. + 3-tuples specify RGB palette entries; + 4-tuples specify RGBA palette entries. + All the 4-tuples (if present) must come before all the 3-tuples. + A ``PLTE`` chunk is created; + if there are 4-tuples then a ``tRNS`` chunk is created as well. + The ``PLTE`` chunk will contain all the RGB triples in the same + sequence; + the ``tRNS`` chunk will contain the alpha channel for + all the 4-tuples, in the same sequence. + Palette entries are always 8-bit. + + If specified, the `transparent` and `background` parameters must be + a tuple with one element for each channel in the image. + Either a 3-tuple of integer (RGB) values for a colour image, or + a 1-tuple of a single integer for a greyscale image. + + If specified, the `gamma` parameter must be a positive number + (generally, a `float`). + A ``gAMA`` chunk will be created. + Note that this will not change the values of the pixels as + they appear in the PNG file, + they are assumed to have already + been converted appropriately for the gamma specified. + + The `compression` argument specifies the compression level to + be used by the ``zlib`` module. + Values from 1 to 9 (highest) specify compression. + 0 means no compression. + -1 and ``None`` both mean that the ``zlib`` module uses + the default level of compression (which is generally acceptable). + + If `interlace` is true then an interlaced image is created + (using PNG's so far only interlace method, *Adam7*). + This does not affect how the pixels should be passed in, + rather it changes how they are arranged into the PNG file. + On slow connexions interlaced images can be + partially decoded by the browser to give + a rough view of the image that is + successively refined as more image data appears. + + .. note :: + + Enabling the `interlace` option requires the entire image + to be processed in working memory. + + `chunk_limit` is used to limit the amount of memory used whilst + compressing the image. + In order to avoid using large amounts of memory, + multiple ``IDAT`` chunks may be created. + """ + + # At the moment the `planes` argument is ignored; + # its purpose is to act as a dummy so that + # ``Writer(x, y, **info)`` works, where `info` is a dictionary + # returned by Reader.read and friends. + # Ditto for `colormap`. + + width, height = check_sizes(size, width, height) + del size + + if not is_natural(width) or not is_natural(height): + raise ProtocolError("width and height must be integers") + if width <= 0 or height <= 0: + raise ProtocolError("width and height must be greater than zero") + # http://www.w3.org/TR/PNG/#7Integers-and-byte-order + if width > 2 ** 31 - 1 or height > 2 ** 31 - 1: + raise ProtocolError("width and height cannot exceed 2**31-1") + + if alpha and transparent is not None: + raise ProtocolError( + "transparent colour not allowed with alpha channel") + + # bitdepth is either single integer, or tuple of integers. + # Convert to tuple. + try: + len(bitdepth) + except TypeError: + bitdepth = (bitdepth, ) + for b in bitdepth: + valid = is_natural(b) and 1 <= b <= 16 + if not valid: + raise ProtocolError( + "each bitdepth %r must be a positive integer <= 16" % + (bitdepth,)) + + # Calculate channels, and + # expand bitdepth to be one element per channel. + palette = check_palette(palette) + alpha = bool(alpha) + colormap = bool(palette) + if greyscale is Default and palette: + greyscale = False + greyscale = bool(greyscale) + if colormap: + color_planes = 1 + planes = 1 + else: + color_planes = (3, 1)[greyscale] + planes = color_planes + alpha + if len(bitdepth) == 1: + bitdepth *= planes + + bitdepth, self.rescale = check_bitdepth_rescale( + palette, + bitdepth, + transparent, alpha, greyscale) + + # These are assertions, because above logic should have + # corrected or raised all problematic cases. + if bitdepth < 8: + assert greyscale or palette + assert not alpha + if bitdepth > 8: + assert not palette + + transparent = check_color(transparent, greyscale, 'transparent') + background = check_color(background, greyscale, 'background') + + # It's important that the true boolean values + # (greyscale, alpha, colormap, interlace) are converted + # to bool because Iverson's convention is relied upon later on. + self.width = width + self.height = height + self.transparent = transparent + self.background = background + self.gamma = gamma + self.greyscale = greyscale + self.alpha = alpha + self.colormap = colormap + self.bitdepth = int(bitdepth) + self.compression = compression + self.chunk_limit = chunk_limit + self.interlace = bool(interlace) + self.palette = palette + self.x_pixels_per_unit = x_pixels_per_unit + self.y_pixels_per_unit = y_pixels_per_unit + self.unit_is_meter = bool(unit_is_meter) + + self.color_type = (4 * self.alpha + + 2 * (not greyscale) + + 1 * self.colormap) + assert self.color_type in (0, 2, 3, 4, 6) + + self.color_planes = color_planes + self.planes = planes + # :todo: fix for bitdepth < 8 + self.psize = (self.bitdepth / 8) * self.planes + + def write(self, outfile, rows): + """ + Write a PNG image to the output file. + `rows` should be an iterable that yields each row + (each row is a sequence of values). + The rows should be the rows of the original image, + so there should be ``self.height`` rows of + ``self.width * self.planes`` values. + If `interlace` is specified (when creating the instance), + then an interlaced PNG file will be written. + Supply the rows in the normal image order; + the interlacing is carried out internally. + + .. note :: + + Interlacing requires the entire image to be in working memory. + """ + + # Values per row + vpr = self.width * self.planes + + def check_rows(rows): + """ + Yield each row in rows, + but check each row first (for correct width). + """ + for i, row in enumerate(rows): + try: + wrong_length = len(row) != vpr + except TypeError: + # When using an itertools.ichain object or + # other generator not supporting __len__, + # we set this to False to skip the check. + wrong_length = False + if wrong_length: + # Note: row numbers start at 0. + raise ProtocolError( + "Expected %d values but got %d values, in row %d" % + (vpr, len(row), i)) + yield row + + if self.interlace: + fmt = 'BH'[self.bitdepth > 8] + a = array(fmt, itertools.chain(*check_rows(rows))) + return self.write_array(outfile, a) + + nrows = self.write_passes(outfile, check_rows(rows)) + if nrows != self.height: + raise ProtocolError( + "rows supplied (%d) does not match height (%d)" % + (nrows, self.height)) + return nrows + + def write_passes(self, outfile, rows): + """ + Write a PNG image to the output file. + + Most users are expected to find the :meth:`write` or + :meth:`write_array` method more convenient. + + The rows should be given to this method in the order that + they appear in the output file. + For straightlaced images, this is the usual top to bottom ordering. + For interlaced images the rows should have been interlaced before + passing them to this function. + + `rows` should be an iterable that yields each row + (each row being a sequence of values). + """ + + # Ensure rows are scaled (to 4-/8-/16-bit), + # and packed into bytes. + + if self.rescale: + rows = rescale_rows(rows, self.rescale) + + if self.bitdepth < 8: + rows = pack_rows(rows, self.bitdepth) + elif self.bitdepth == 16: + rows = unpack_rows(rows) + + return self.write_packed(outfile, rows) + + def write_packed(self, outfile, rows): + """ + Write PNG file to `outfile`. + `rows` should be an iterator that yields each packed row; + a packed row being a sequence of packed bytes. + + The rows have a filter byte prefixed and + are then compressed into one or more IDAT chunks. + They are not processed any further, + so if bitdepth is other than 1, 2, 4, 8, 16, + the pixel values should have been scaled + before passing them to this method. + + This method does work for interlaced images but it is best avoided. + For interlaced images, the rows should be + presented in the order that they appear in the file. + """ + + self.write_preamble(outfile) + + # http://www.w3.org/TR/PNG/#11IDAT + if self.compression is not None: + compressor = zlib.compressobj(self.compression) + else: + compressor = zlib.compressobj() + + # data accumulates bytes to be compressed for the IDAT chunk; + # it's compressed when sufficiently large. + data = bytearray() + + # raise i scope out of the for loop. set to -1, because the for loop + # sets i to 0 on the first pass + i = -1 + for i, row in enumerate(rows): + # Add "None" filter type. + # Currently, it's essential that this filter type be used + # for every scanline as + # we do not mark the first row of a reduced pass image; + # that means we could accidentally compute + # the wrong filtered scanline if we used + # "up", "average", or "paeth" on such a line. + data.append(0) + data.extend(row) + if len(data) > self.chunk_limit: + compressed = compressor.compress(data) + if len(compressed): + write_chunk(outfile, b'IDAT', compressed) + data = bytearray() + + compressed = compressor.compress(bytes(data)) + flushed = compressor.flush() + if len(compressed) or len(flushed): + write_chunk(outfile, b'IDAT', compressed + flushed) + # http://www.w3.org/TR/PNG/#11IEND + write_chunk(outfile, b'IEND') + return i + 1 + + def write_preamble(self, outfile): + # http://www.w3.org/TR/PNG/#5PNG-file-signature + + # This is the first write that is made when + # writing a PNG file. + # This one, and only this one, is checked for TypeError, + # which generally indicates that we are writing bytes + # into a text stream. + try: + outfile.write(signature) + except TypeError as e: + raise ProtocolError("PNG must be written to a binary stream") from e + + # http://www.w3.org/TR/PNG/#11IHDR + write_chunk(outfile, b'IHDR', + struct.pack("!2I5B", self.width, self.height, + self.bitdepth, self.color_type, + 0, 0, self.interlace)) + + # See :chunk:order + # http://www.w3.org/TR/PNG/#11gAMA + if self.gamma is not None: + write_chunk(outfile, b'gAMA', + struct.pack("!L", int(round(self.gamma * 1e5)))) + + # See :chunk:order + # http://www.w3.org/TR/PNG/#11sBIT + if self.rescale: + write_chunk( + outfile, b'sBIT', + struct.pack('%dB' % self.planes, + * [s[0] for s in self.rescale])) + + # :chunk:order: Without a palette (PLTE chunk), + # ordering is relatively relaxed. + # With one, gAMA chunk must precede PLTE chunk + # which must precede tRNS and bKGD. + # See http://www.w3.org/TR/PNG/#5ChunkOrdering + if self.palette: + p, t = make_palette_chunks(self.palette) + write_chunk(outfile, b'PLTE', p) + if t: + # tRNS chunk is optional; + # Only needed if palette entries have alpha. + write_chunk(outfile, b'tRNS', t) + + # http://www.w3.org/TR/PNG/#11tRNS + if self.transparent is not None: + if self.greyscale: + fmt = "!1H" + else: + fmt = "!3H" + write_chunk(outfile, b'tRNS', + struct.pack(fmt, *self.transparent)) + + # http://www.w3.org/TR/PNG/#11bKGD + if self.background is not None: + if self.greyscale: + fmt = "!1H" + else: + fmt = "!3H" + write_chunk(outfile, b'bKGD', + struct.pack(fmt, *self.background)) + + # http://www.w3.org/TR/PNG/#11pHYs + if (self.x_pixels_per_unit is not None and + self.y_pixels_per_unit is not None): + tup = (self.x_pixels_per_unit, + self.y_pixels_per_unit, + int(self.unit_is_meter)) + write_chunk(outfile, b'pHYs', struct.pack("!LLB", *tup)) + + def write_array(self, outfile, pixels): + """ + Write an array that holds all the image values + as a PNG file on the output file. + See also :meth:`write` method. + """ + + if self.interlace: + if type(pixels) != array: + # Coerce to array type + fmt = 'BH'[self.bitdepth > 8] + pixels = array(fmt, pixels) + return self.write_passes( + outfile, + self.array_scanlines_interlace(pixels) + ) + else: + return self.write_passes( + outfile, + self.array_scanlines(pixels) + ) + + def array_scanlines(self, pixels): + """ + Generates rows (each a sequence of values) from + a single array of values. + """ + + # Values per row + vpr = self.width * self.planes + stop = 0 + for y in range(self.height): + start = stop + stop = start + vpr + yield pixels[start:stop] + + def array_scanlines_interlace(self, pixels): + """ + Generator for interlaced scanlines from an array. + `pixels` is the full source image as a single array of values. + The generator yields each scanline of the reduced passes in turn, + each scanline being a sequence of values. + """ + + # http://www.w3.org/TR/PNG/#8InterlaceMethods + # Array type. + fmt = 'BH'[self.bitdepth > 8] + # Value per row + vpr = self.width * self.planes + + # Each iteration generates a scanline starting at (x, y) + # and consisting of every xstep pixels. + for lines in adam7_generate(self.width, self.height): + for x, y, xstep in lines: + # Pixels per row (of reduced image) + ppr = int(math.ceil((self.width - x) / float(xstep))) + # Values per row (of reduced image) + reduced_row_len = ppr * self.planes + if xstep == 1: + # Easy case: line is a simple slice. + offset = y * vpr + yield pixels[offset: offset + vpr] + continue + # We have to step by xstep, + # which we can do one plane at a time + # using the step in Python slices. + row = array(fmt) + # There's no easier way to set the length of an array + row.extend(pixels[0:reduced_row_len]) + offset = y * vpr + x * self.planes + end_offset = (y + 1) * vpr + skip = self.planes * xstep + for i in range(self.planes): + row[i::self.planes] = \ + pixels[offset + i: end_offset: skip] + yield row + + +def write_chunk(outfile, tag, data=b''): + """ + Write a PNG chunk to the output file, including length and + checksum. + """ + + data = bytes(data) + # http://www.w3.org/TR/PNG/#5Chunk-layout + outfile.write(struct.pack("!I", len(data))) + outfile.write(tag) + outfile.write(data) + checksum = zlib.crc32(tag) + checksum = zlib.crc32(data, checksum) + checksum &= 2 ** 32 - 1 + outfile.write(struct.pack("!I", checksum)) + + +def write_chunks(out, chunks): + """Create a PNG file by writing out the chunks.""" + + out.write(signature) + for chunk in chunks: + write_chunk(out, *chunk) + + +def rescale_rows(rows, rescale): + """ + Take each row in rows (an iterator) and yield + a fresh row with the pixels scaled according to + the rescale parameters in the list `rescale`. + Each element of `rescale` is a tuple of + (source_bitdepth, target_bitdepth), + with one element per channel. + """ + + # One factor for each channel + fs = [float(2 ** s[1] - 1)/float(2 ** s[0] - 1) + for s in rescale] + + # Assume all target_bitdepths are the same + target_bitdepths = set(s[1] for s in rescale) + assert len(target_bitdepths) == 1 + (target_bitdepth, ) = target_bitdepths + typecode = 'BH'[target_bitdepth > 8] + + # Number of channels + n_chans = len(rescale) + + for row in rows: + rescaled_row = array(typecode, iter(row)) + for i in range(n_chans): + channel = array( + typecode, + (int(round(fs[i] * x)) for x in row[i::n_chans])) + rescaled_row[i::n_chans] = channel + yield rescaled_row + + +def pack_rows(rows, bitdepth): + """Yield packed rows that are a byte array. + Each byte is packed with the values from several pixels. + """ + + assert bitdepth < 8 + assert 8 % bitdepth == 0 + + # samples per byte + spb = int(8 / bitdepth) + + def make_byte(block): + """Take a block of (2, 4, or 8) values, + and pack them into a single byte. + """ + + res = 0 + for v in block: + res = (res << bitdepth) + v + return res + + for row in rows: + a = bytearray(row) + # Adding padding bytes so we can group into a whole + # number of spb-tuples. + n = float(len(a)) + extra = math.ceil(n / spb) * spb - n + a.extend([0] * int(extra)) + # Pack into bytes. + # Each block is the samples for one byte. + blocks = group(a, spb) + yield bytearray(make_byte(block) for block in blocks) + + +def unpack_rows(rows): + """Unpack each row from being 16-bits per value, + to being a sequence of bytes. + """ + for row in rows: + fmt = '!%dH' % len(row) + yield bytearray(struct.pack(fmt, *row)) + + +def make_palette_chunks(palette): + """ + Create the byte sequences for a ``PLTE`` and + if necessary a ``tRNS`` chunk. + Returned as a pair (*p*, *t*). + *t* will be ``None`` if no ``tRNS`` chunk is necessary. + """ + + p = bytearray() + t = bytearray() + + for x in palette: + p.extend(x[0:3]) + if len(x) > 3: + t.append(x[3]) + if t: + return p, t + return p, None + + +def check_bitdepth_rescale( + palette, bitdepth, transparent, alpha, greyscale): + """ + Returns (bitdepth, rescale) pair. + """ + + if palette: + if len(bitdepth) != 1: + raise ProtocolError( + "with palette, only a single bitdepth may be used") + (bitdepth, ) = bitdepth + if bitdepth not in (1, 2, 4, 8): + raise ProtocolError( + "with palette, bitdepth must be 1, 2, 4, or 8") + if transparent is not None: + raise ProtocolError("transparent and palette not compatible") + if alpha: + raise ProtocolError("alpha and palette not compatible") + if greyscale: + raise ProtocolError("greyscale and palette not compatible") + return bitdepth, None + + # No palette, check for sBIT chunk generation. + + if greyscale and not alpha: + # Single channel, L. + (bitdepth,) = bitdepth + if bitdepth in (1, 2, 4, 8, 16): + return bitdepth, None + if bitdepth > 8: + targetbitdepth = 16 + elif bitdepth == 3: + targetbitdepth = 4 + else: + assert bitdepth in (5, 6, 7) + targetbitdepth = 8 + return targetbitdepth, [(bitdepth, targetbitdepth)] + + assert alpha or not greyscale + + depth_set = tuple(set(bitdepth)) + if depth_set in [(8,), (16,)]: + # No sBIT required. + (bitdepth, ) = depth_set + return bitdepth, None + + targetbitdepth = (8, 16)[max(bitdepth) > 8] + return targetbitdepth, [(b, targetbitdepth) for b in bitdepth] + + +# Regex for decoding mode string +RegexModeDecode = re.compile("(LA?|RGBA?);?([0-9]*)", flags=re.IGNORECASE) + + +def from_array(a, mode=None, info={}): + """ + Create a PNG :class:`Image` object from a 2-dimensional array. + One application of this function is easy PIL-style saving: + ``png.from_array(pixels, 'L').save('foo.png')``. + + Unless they are specified using the *info* parameter, + the PNG's height and width are taken from the array size. + The first axis is the height; the second axis is the + ravelled width and channel index. + The array is treated is a sequence of rows, + each row being a sequence of values (``width*channels`` in number). + So an RGB image that is 16 pixels high and 8 wide will + occupy a 2-dimensional array that is 16x24 + (each row will be 8*3 = 24 sample values). + + *mode* is a string that specifies the image colour format in a + PIL-style mode. It can be: + + ``'L'`` + greyscale (1 channel) + ``'LA'`` + greyscale with alpha (2 channel) + ``'RGB'`` + colour image (3 channel) + ``'RGBA'`` + colour image with alpha (4 channel) + + The mode string can also specify the bit depth + (overriding how this function normally derives the bit depth, + see below). + Appending ``';16'`` to the mode will cause the PNG to be + 16 bits per channel; + any decimal from 1 to 16 can be used to specify the bit depth. + + When a 2-dimensional array is used *mode* determines how many + channels the image has, and so allows the width to be derived from + the second array dimension. + + The array is expected to be a ``numpy`` array, + but it can be any suitable Python sequence. + For example, a list of lists can be used: + ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``. + The exact rules are: ``len(a)`` gives the first dimension, height; + ``len(a[0])`` gives the second dimension. + It's slightly more complicated than that because + an iterator of rows can be used, and it all still works. + Using an iterator allows data to be streamed efficiently. + + The bit depth of the PNG is normally taken from + the array element's datatype + (but if *mode* specifies a bitdepth then that is used instead). + The array element's datatype is determined in a way which + is supposed to work both for ``numpy`` arrays and for Python + ``array.array`` objects. + A 1 byte datatype will give a bit depth of 8, + a 2 byte datatype will give a bit depth of 16. + If the datatype does not have an implicit size, + like the above example where it is a plain Python list of lists, + then a default of 8 is used. + + The *info* parameter is a dictionary that can + be used to specify metadata (in the same style as + the arguments to the :class:`png.Writer` class). + For this function the keys that are useful are: + + height + overrides the height derived from the array dimensions and + allows *a* to be an iterable. + width + overrides the width derived from the array dimensions. + bitdepth + overrides the bit depth derived from the element datatype + (but must match *mode* if that also specifies a bit depth). + + Generally anything specified in the *info* dictionary will + override any implicit choices that this function would otherwise make, + but must match any explicit ones. + For example, if the *info* dictionary has a ``greyscale`` key then + this must be true when mode is ``'L'`` or ``'LA'`` and + false when mode is ``'RGB'`` or ``'RGBA'``. + """ + + # We abuse the *info* parameter by modifying it. Take a copy here. + # (Also typechecks *info* to some extent). + info = dict(info) + + # Syntax check mode string. + match = RegexModeDecode.match(mode) + if not match: + raise Error("mode string should be 'RGB' or 'L;16' or similar.") + + mode, bitdepth = match.groups() + if bitdepth: + bitdepth = int(bitdepth) + + # Colour format. + if 'greyscale' in info: + if bool(info['greyscale']) != ('L' in mode): + raise ProtocolError("info['greyscale'] should match mode.") + info['greyscale'] = 'L' in mode + + alpha = 'A' in mode + if 'alpha' in info: + if bool(info['alpha']) != alpha: + raise ProtocolError("info['alpha'] should match mode.") + info['alpha'] = alpha + + # Get bitdepth from *mode* if possible. + if bitdepth: + if info.get("bitdepth") and bitdepth != info['bitdepth']: + raise ProtocolError( + "bitdepth (%d) should match bitdepth of info (%d)." % + (bitdepth, info['bitdepth'])) + info['bitdepth'] = bitdepth + + # Fill in and/or check entries in *info*. + # Dimensions. + width, height = check_sizes( + info.get("size"), + info.get("width"), + info.get("height")) + if width: + info["width"] = width + if height: + info["height"] = height + + if "height" not in info: + try: + info['height'] = len(a) + except TypeError: + raise ProtocolError( + "len(a) does not work, supply info['height'] instead.") + + planes = len(mode) + if 'planes' in info: + if info['planes'] != planes: + raise Error("info['planes'] should match mode.") + + # In order to work out whether we the array is 2D or 3D we need its + # first row, which requires that we take a copy of its iterator. + # We may also need the first row to derive width and bitdepth. + a, t = itertools.tee(a) + row = next(t) + del t + + testelement = row + if 'width' not in info: + width = len(row) // planes + info['width'] = width + + if 'bitdepth' not in info: + try: + dtype = testelement.dtype + # goto the "else:" clause. Sorry. + except AttributeError: + try: + # Try a Python array.array. + bitdepth = 8 * testelement.itemsize + except AttributeError: + # We can't determine it from the array element's datatype, + # use a default of 8. + bitdepth = 8 + else: + # If we got here without exception, + # we now assume that the array is a numpy array. + if dtype.kind == 'b': + bitdepth = 1 + else: + bitdepth = 8 * dtype.itemsize + info['bitdepth'] = bitdepth + + for thing in ["width", "height", "bitdepth", "greyscale", "alpha"]: + assert thing in info + + return Image(a, info) + + +# So that refugee's from PIL feel more at home. Not documented. +fromarray = from_array + + +class Image: + """A PNG image. You can create an :class:`Image` object from + an array of pixels by calling :meth:`png.from_array`. It can be + saved to disk with the :meth:`save` method. + """ + + def __init__(self, rows, info): + """ + .. note :: + + The constructor is not public. Please do not call it. + """ + + self.rows = rows + self.info = info + + def save(self, file): + """Save the image to the named *file*. + + See `.write()` if you already have an open file object. + + In general, you can only call this method once; + after it has been called the first time the PNG image is written, + the source data will have been streamed, and + cannot be streamed again. + """ + + w = Writer(**self.info) + + with open(file, 'wb') as fd: + w.write(fd, self.rows) + + def stream(self): + """Stream the rows into a list, so that the rows object + can be accessed multiple times, or randomly. + """ + + self.rows = list(self.rows) + + def write(self, file): + """Write the image to the open file object. + + See `.save()` if you have a filename. + + In general, you can only call this method once; + after it has been called the first time the PNG image is written, + the source data will have been streamed, and + cannot be streamed again. + """ + + w = Writer(**self.info) + w.write(file, self.rows) + + +class Reader: + """ + Pure Python PNG decoder in pure Python. + """ + + def __init__(self, _guess=None, filename=None, file=None, bytes=None): + """ + The constructor expects exactly one keyword argument. + If you supply a positional argument instead, + it will guess the input type. + Choose from the following keyword arguments: + + filename + Name of input file (a PNG file). + file + A file-like object (object with a read() method). + bytes + ``bytes`` or ``bytearray`` with PNG data. + + """ + keywords_supplied = ( + (_guess is not None) + + (filename is not None) + + (file is not None) + + (bytes is not None)) + if keywords_supplied != 1: + raise TypeError("Reader() takes exactly 1 argument") + + # Will be the first 8 bytes, later on. See validate_signature. + self.signature = None + self.transparent = None + # A pair of (len,type) if a chunk has been read but its data and + # checksum have not (in other words the file position is just + # past the 4 bytes that specify the chunk type). + # See preamble method for how this is used. + self.atchunk = None + + if _guess is not None: + if isarray(_guess): + bytes = _guess + elif isinstance(_guess, str): + filename = _guess + elif hasattr(_guess, 'read'): + file = _guess + + if bytes is not None: + self.file = io.BytesIO(bytes) + elif filename is not None: + self.file = open(filename, "rb") + elif file is not None: + self.file = file + else: + raise ProtocolError("expecting filename, file or bytes array") + + def chunk(self, lenient=False): + """ + Read the next PNG chunk from the input file; + returns a (*type*, *data*) tuple. + *type* is the chunk's type as a byte string + (all PNG chunk types are 4 bytes long). + *data* is the chunk's data content, as a byte string. + + If the optional `lenient` argument evaluates to `True`, + checksum failures will raise warnings rather than exceptions. + """ + + self.validate_signature() + + # http://www.w3.org/TR/PNG/#5Chunk-layout + if not self.atchunk: + self.atchunk = self._chunk_len_type() + if not self.atchunk: + raise ChunkError("No more chunks.") + length, type = self.atchunk + self.atchunk = None + + data = self.file.read(length) + if len(data) != length: + raise ChunkError( + 'Chunk %s too short for required %i octets.' + % (type, length)) + checksum = self.file.read(4) + if len(checksum) != 4: + raise ChunkError('Chunk %s too short for checksum.' % type) + verify = zlib.crc32(type) + verify = zlib.crc32(data, verify) + verify = struct.pack('!I', verify) + if checksum != verify: + (a, ) = struct.unpack('!I', checksum) + (b, ) = struct.unpack('!I', verify) + message = ("Checksum error in %s chunk: 0x%08X != 0x%08X." + % (type.decode('ascii'), a, b)) + if lenient: + warnings.warn(message, RuntimeWarning) + else: + raise ChunkError(message) + return type, data + + def chunks(self): + """Return an iterator that will yield each chunk as a + (*chunktype*, *content*) pair. + """ + + while True: + t, v = self.chunk() + yield t, v + if t == b'IEND': + break + + def undo_filter(self, filter_type, scanline, previous): + """ + Undo the filter for a scanline. + `scanline` is a sequence of bytes that + does not include the initial filter type byte. + `previous` is decoded previous scanline + (for straightlaced images this is the previous pixel row, + but for interlaced images, it is + the previous scanline in the reduced image, + which in general is not the previous pixel row in the final image). + When there is no previous scanline + (the first row of a straightlaced image, + or the first row in one of the passes in an interlaced image), + then this argument should be ``None``. + + The scanline will have the effects of filtering removed; + the result will be returned as a fresh sequence of bytes. + """ + + # :todo: Would it be better to update scanline in place? + result = scanline + + if filter_type == 0: + return result + + if filter_type not in (1, 2, 3, 4): + raise FormatError( + 'Invalid PNG Filter Type. ' + 'See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .') + + # Filter unit. The stride from one pixel to the corresponding + # byte from the previous pixel. Normally this is the pixel + # size in bytes, but when this is smaller than 1, the previous + # byte is used instead. + fu = max(1, self.psize) + + # For the first line of a pass, synthesize a dummy previous + # line. An alternative approach would be to observe that on the + # first line 'up' is the same as 'null', 'paeth' is the same + # as 'sub', with only 'average' requiring any special case. + if not previous: + previous = bytearray([0] * len(scanline)) + + # Call appropriate filter algorithm. Note that 0 has already + # been dealt with. + fn = (None, + undo_filter_sub, + undo_filter_up, + undo_filter_average, + undo_filter_paeth)[filter_type] + fn(fu, scanline, previous, result) + return result + + def _deinterlace(self, raw): + """ + Read raw pixel data, undo filters, deinterlace, and flatten. + Return a single array of values. + """ + + # Values per row (of the target image) + vpr = self.width * self.planes + + # Values per image + vpi = vpr * self.height + # Interleaving writes to the output array randomly + # (well, not quite), so the entire output array must be in memory. + # Make a result array, and make it big enough. + if self.bitdepth > 8: + a = array('H', [0] * vpi) + else: + a = bytearray([0] * vpi) + source_offset = 0 + + for lines in adam7_generate(self.width, self.height): + # The previous (reconstructed) scanline. + # `None` at the beginning of a pass + # to indicate that there is no previous line. + recon = None + for x, y, xstep in lines: + # Pixels per row (reduced pass image) + ppr = int(math.ceil((self.width - x) / float(xstep))) + # Row size in bytes for this pass. + row_size = int(math.ceil(self.psize * ppr)) + + filter_type = raw[source_offset] + source_offset += 1 + scanline = raw[source_offset: source_offset + row_size] + source_offset += row_size + recon = self.undo_filter(filter_type, scanline, recon) + # Convert so that there is one element per pixel value + flat = self._bytes_to_values(recon, width=ppr) + if xstep == 1: + assert x == 0 + offset = y * vpr + a[offset: offset + vpr] = flat + else: + offset = y * vpr + x * self.planes + end_offset = (y + 1) * vpr + skip = self.planes * xstep + for i in range(self.planes): + a[offset + i: end_offset: skip] = \ + flat[i:: self.planes] + + return a + + def _iter_bytes_to_values(self, byte_rows): + """ + Iterator that yields each scanline; + each scanline being a sequence of values. + `byte_rows` should be an iterator that yields + the bytes of each row in turn. + """ + + for row in byte_rows: + yield self._bytes_to_values(row) + + def _bytes_to_values(self, bs, width=None): + """Convert a packed row of bytes into a row of values. + Result will be a freshly allocated object, + not shared with the argument. + """ + + if self.bitdepth == 8: + return bytearray(bs) + if self.bitdepth == 16: + return array('H', + struct.unpack('!%dH' % (len(bs) // 2), bs)) + + assert self.bitdepth < 8 + if width is None: + width = self.width + # Samples per byte + spb = 8 // self.bitdepth + out = bytearray() + mask = 2**self.bitdepth - 1 + shifts = [self.bitdepth * i + for i in reversed(list(range(spb)))] + for o in bs: + out.extend([mask & (o >> i) for i in shifts]) + return out[:width] + + def _iter_straight_packed(self, byte_blocks): + """Iterator that undoes the effect of filtering; + yields each row as a sequence of packed bytes. + Assumes input is straightlaced. + `byte_blocks` should be an iterable that yields the raw bytes + in blocks of arbitrary size. + """ + + # length of row, in bytes + rb = self.row_bytes + a = bytearray() + # The previous (reconstructed) scanline. + # None indicates first line of image. + recon = None + for some_bytes in byte_blocks: + a.extend(some_bytes) + while len(a) >= rb + 1: + filter_type = a[0] + scanline = a[1: rb + 1] + del a[: rb + 1] + recon = self.undo_filter(filter_type, scanline, recon) + yield recon + if len(a) != 0: + # :file:format We get here with a file format error: + # when the available bytes (after decompressing) do not + # pack into exact rows. + raise FormatError('Wrong size for decompressed IDAT chunk.') + assert len(a) == 0 + + def validate_signature(self): + """ + If signature (header) has not been read then read and + validate it; otherwise do nothing. + No signature (empty read()) will raise EOFError; + An invalid signature will raise FormatError. + EOFError is raised to make possible the case where + a program can read multiple PNG files from the same stream. + The end of the stream can be distinguished from non-PNG files + or corrupted PNG files. + """ + + if self.signature: + return + self.signature = self.file.read(8) + if len(self.signature) == 0: + raise EOFError("End of PNG stream.") + if self.signature != signature: + raise FormatError("PNG file has invalid signature.") + + def preamble(self, lenient=False): + """ + Extract the image metadata by reading + the initial part of the PNG file up to + the start of the ``IDAT`` chunk. + All the chunks that precede the ``IDAT`` chunk are + read and either processed for metadata or discarded. + + If the optional `lenient` argument evaluates to `True`, + checksum failures will raise warnings rather than exceptions. + """ + + self.validate_signature() + + while True: + if not self.atchunk: + self.atchunk = self._chunk_len_type() + if self.atchunk is None: + raise FormatError('This PNG file has no IDAT chunks.') + if self.atchunk[1] == b'IDAT': + return + self.process_chunk(lenient=lenient) + + def _chunk_len_type(self): + """ + Reads just enough of the input to + determine the next chunk's length and type; + return a (*length*, *type*) pair where *type* is a byte sequence. + If there are no more chunks, ``None`` is returned. + """ + + x = self.file.read(8) + if not x: + return None + if len(x) != 8: + raise FormatError( + 'End of file whilst reading chunk length and type.') + length, type = struct.unpack('!I4s', x) + if length > 2 ** 31 - 1: + raise FormatError('Chunk %s is too large: %d.' % (type, length)) + # Check that all bytes are in valid ASCII range. + # https://www.w3.org/TR/2003/REC-PNG-20031110/#5Chunk-layout + type_bytes = set(bytearray(type)) + if not(type_bytes <= set(range(65, 91)) | set(range(97, 123))): + raise FormatError( + 'Chunk %r has invalid Chunk Type.' + % list(type)) + return length, type + + def process_chunk(self, lenient=False): + """ + Process the next chunk and its data. + This only processes the following chunk types: + ``IHDR``, ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``, ``pHYs``. + All other chunk types are ignored. + + If the optional `lenient` argument evaluates to `True`, + checksum failures will raise warnings rather than exceptions. + """ + + type, data = self.chunk(lenient=lenient) + method = '_process_' + type.decode('ascii') + m = getattr(self, method, None) + if m: + m(data) + + def _process_IHDR(self, data): + # http://www.w3.org/TR/PNG/#11IHDR + if len(data) != 13: + raise FormatError('IHDR chunk has incorrect length.') + (self.width, self.height, self.bitdepth, self.color_type, + self.compression, self.filter, + self.interlace) = struct.unpack("!2I5B", data) + + check_bitdepth_colortype(self.bitdepth, self.color_type) + + if self.compression != 0: + raise FormatError( + "Unknown compression method %d" % self.compression) + if self.filter != 0: + raise FormatError( + "Unknown filter method %d," + " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." + % self.filter) + if self.interlace not in (0, 1): + raise FormatError( + "Unknown interlace method %d, see " + "http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods" + " ." + % self.interlace) + + # Derived values + # http://www.w3.org/TR/PNG/#6Colour-values + colormap = bool(self.color_type & 1) + greyscale = not(self.color_type & 2) + alpha = bool(self.color_type & 4) + color_planes = (3, 1)[greyscale or colormap] + planes = color_planes + alpha + + self.colormap = colormap + self.greyscale = greyscale + self.alpha = alpha + self.color_planes = color_planes + self.planes = planes + self.psize = float(self.bitdepth) / float(8) * planes + if int(self.psize) == self.psize: + self.psize = int(self.psize) + self.row_bytes = int(math.ceil(self.width * self.psize)) + # Stores PLTE chunk if present, and is used to check + # chunk ordering constraints. + self.plte = None + # Stores tRNS chunk if present, and is used to check chunk + # ordering constraints. + self.trns = None + # Stores sBIT chunk if present. + self.sbit = None + + def _process_PLTE(self, data): + # http://www.w3.org/TR/PNG/#11PLTE + if self.plte: + warnings.warn("Multiple PLTE chunks present.") + self.plte = data + if len(data) % 3 != 0: + raise FormatError( + "PLTE chunk's length should be a multiple of 3.") + if len(data) > (2 ** self.bitdepth) * 3: + raise FormatError("PLTE chunk is too long.") + if len(data) == 0: + raise FormatError("Empty PLTE is not allowed.") + + def _process_bKGD(self, data): + try: + if self.colormap: + if not self.plte: + warnings.warn( + "PLTE chunk is required before bKGD chunk.") + self.background = struct.unpack('B', data) + else: + self.background = struct.unpack("!%dH" % self.color_planes, + data) + except struct.error: + raise FormatError("bKGD chunk has incorrect length.") + + def _process_tRNS(self, data): + # http://www.w3.org/TR/PNG/#11tRNS + self.trns = data + if self.colormap: + if not self.plte: + warnings.warn("PLTE chunk is required before tRNS chunk.") + else: + if len(data) > len(self.plte) / 3: + # Was warning, but promoted to Error as it + # would otherwise cause pain later on. + raise FormatError("tRNS chunk is too long.") + else: + if self.alpha: + raise FormatError( + "tRNS chunk is not valid with colour type %d." % + self.color_type) + try: + self.transparent = \ + struct.unpack("!%dH" % self.color_planes, data) + except struct.error: + raise FormatError("tRNS chunk has incorrect length.") + + def _process_gAMA(self, data): + try: + self.gamma = struct.unpack("!L", data)[0] / 100000.0 + except struct.error: + raise FormatError("gAMA chunk has incorrect length.") + + def _process_sBIT(self, data): + self.sbit = data + if (self.colormap and len(data) != 3 or + not self.colormap and len(data) != self.planes): + raise FormatError("sBIT chunk has incorrect length.") + + def _process_pHYs(self, data): + # http://www.w3.org/TR/PNG/#11pHYs + self.phys = data + fmt = "!LLB" + if len(data) != struct.calcsize(fmt): + raise FormatError("pHYs chunk has incorrect length.") + self.x_pixels_per_unit, self.y_pixels_per_unit, unit = \ + struct.unpack(fmt, data) + self.unit_is_meter = bool(unit) + + def read(self, lenient=False): + """ + Read the PNG file and decode it. + Returns (`width`, `height`, `rows`, `info`). + + May use excessive memory. + + `rows` is a sequence of rows; + each row is a sequence of values. + + If the optional `lenient` argument evaluates to True, + checksum failures will raise warnings rather than exceptions. + """ + + def iteridat(): + """Iterator that yields all the ``IDAT`` chunks as strings.""" + while True: + type, data = self.chunk(lenient=lenient) + if type == b'IEND': + # http://www.w3.org/TR/PNG/#11IEND + break + if type != b'IDAT': + continue + # type == b'IDAT' + # http://www.w3.org/TR/PNG/#11IDAT + if self.colormap and not self.plte: + warnings.warn("PLTE chunk is required before IDAT chunk") + yield data + + self.preamble(lenient=lenient) + raw = decompress(iteridat()) + + if self.interlace: + def rows_from_interlace(): + """Yield each row from an interlaced PNG.""" + # It's important that this iterator doesn't read + # IDAT chunks until it yields the first row. + bs = bytearray(itertools.chain(*raw)) + arraycode = 'BH'[self.bitdepth > 8] + # Like :meth:`group` but + # producing an array.array object for each row. + values = self._deinterlace(bs) + vpr = self.width * self.planes + for i in range(0, len(values), vpr): + row = array(arraycode, values[i:i+vpr]) + yield row + rows = rows_from_interlace() + else: + rows = self._iter_bytes_to_values(self._iter_straight_packed(raw)) + info = dict() + for attr in 'greyscale alpha planes bitdepth interlace'.split(): + info[attr] = getattr(self, attr) + info['size'] = (self.width, self.height) + for attr in 'gamma transparent background'.split(): + a = getattr(self, attr, None) + if a is not None: + info[attr] = a + if getattr(self, 'x_pixels_per_unit', None): + info['physical'] = Resolution(self.x_pixels_per_unit, + self.y_pixels_per_unit, + self.unit_is_meter) + if self.plte: + info['palette'] = self.palette() + return self.width, self.height, rows, info + + def read_flat(self): + """ + Read a PNG file and decode it into a single array of values. + Returns (*width*, *height*, *values*, *info*). + + May use excessive memory. + + `values` is a single array. + + The :meth:`read` method is more stream-friendly than this, + because it returns a sequence of rows. + """ + + x, y, pixel, info = self.read() + arraycode = 'BH'[info['bitdepth'] > 8] + pixel = array(arraycode, itertools.chain(*pixel)) + return x, y, pixel, info + + def palette(self, alpha='natural'): + """ + Returns a palette that is a sequence of 3-tuples or 4-tuples, + synthesizing it from the ``PLTE`` and ``tRNS`` chunks. + These chunks should have already been processed (for example, + by calling the :meth:`preamble` method). + All the tuples are the same size: + 3-tuples if there is no ``tRNS`` chunk, + 4-tuples when there is a ``tRNS`` chunk. + + Assumes that the image is colour type + 3 and therefore a ``PLTE`` chunk is required. + + If the `alpha` argument is ``'force'`` then an alpha channel is + always added, forcing the result to be a sequence of 4-tuples. + """ + + if not self.plte: + raise FormatError( + "Required PLTE chunk is missing in colour type 3 image.") + plte = group(array('B', self.plte), 3) + if self.trns or alpha == 'force': + trns = array('B', self.trns or []) + trns.extend([255] * (len(plte) - len(trns))) + plte = list(map(operator.add, plte, group(trns, 1))) + return plte + + def asDirect(self): + """ + Returns the image data as a direct representation of + an ``x * y * planes`` array. + This removes the need for callers to deal with + palettes and transparency themselves. + Images with a palette (colour type 3) are converted to RGB or RGBA; + images with transparency (a ``tRNS`` chunk) are converted to + LA or RGBA as appropriate. + When returned in this format the pixel values represent + the colour value directly without needing to refer + to palettes or transparency information. + + Like the :meth:`read` method this method returns a 4-tuple: + + (*width*, *height*, *rows*, *info*) + + This method normally returns pixel values with + the bit depth they have in the source image, but + when the source PNG has an ``sBIT`` chunk it is inspected and + can reduce the bit depth of the result pixels; + pixel values will be reduced according to the bit depth + specified in the ``sBIT`` chunk. + PNG nerds should note a single result bit depth is + used for all channels: + the maximum of the ones specified in the ``sBIT`` chunk. + An RGB565 image will be rescaled to 6-bit RGB666. + + The *info* dictionary that is returned reflects + the `direct` format and not the original source image. + For example, an RGB source image with a ``tRNS`` chunk + to represent a transparent colour, + will start with ``planes=3`` and ``alpha=False`` for the + source image, + but the *info* dictionary returned by this method + will have ``planes=4`` and ``alpha=True`` because + an alpha channel is synthesized and added. + + *rows* is a sequence of rows; + each row being a sequence of values + (like the :meth:`read` method). + + All the other aspects of the image data are not changed. + """ + + self.preamble() + + # Simple case, no conversion necessary. + if not self.colormap and not self.trns and not self.sbit: + return self.read() + + x, y, pixels, info = self.read() + + if self.colormap: + info['colormap'] = False + info['alpha'] = bool(self.trns) + info['bitdepth'] = 8 + info['planes'] = 3 + bool(self.trns) + plte = self.palette() + + def iterpal(pixels): + for row in pixels: + row = [plte[x] for x in row] + yield array('B', itertools.chain(*row)) + pixels = iterpal(pixels) + elif self.trns: + # It would be nice if there was some reasonable way + # of doing this without generating a whole load of + # intermediate tuples. But tuples does seem like the + # easiest way, with no other way clearly much simpler or + # much faster. (Actually, the L to LA conversion could + # perhaps go faster (all those 1-tuples!), but I still + # wonder whether the code proliferation is worth it) + it = self.transparent + maxval = 2 ** info['bitdepth'] - 1 + planes = info['planes'] + info['alpha'] = True + info['planes'] += 1 + typecode = 'BH'[info['bitdepth'] > 8] + + def itertrns(pixels): + for row in pixels: + # For each row we group it into pixels, then form a + # characterisation vector that says whether each + # pixel is opaque or not. Then we convert + # True/False to 0/maxval (by multiplication), + # and add it as the extra channel. + row = group(row, planes) + opa = map(it.__ne__, row) + opa = map(maxval.__mul__, opa) + opa = list(zip(opa)) # convert to 1-tuples + yield array( + typecode, + itertools.chain(*map(operator.add, row, opa))) + pixels = itertrns(pixels) + targetbitdepth = None + if self.sbit: + sbit = struct.unpack('%dB' % len(self.sbit), self.sbit) + targetbitdepth = max(sbit) + if targetbitdepth > info['bitdepth']: + raise Error('sBIT chunk %r exceeds bitdepth %d' % + (sbit, self.bitdepth)) + if min(sbit) <= 0: + raise Error('sBIT chunk %r has a 0-entry' % sbit) + if targetbitdepth: + shift = info['bitdepth'] - targetbitdepth + info['bitdepth'] = targetbitdepth + + def itershift(pixels): + for row in pixels: + yield [p >> shift for p in row] + pixels = itershift(pixels) + return x, y, pixels, info + + def _as_rescale(self, get, targetbitdepth): + """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`.""" + + width, height, pixels, info = get() + maxval = 2**info['bitdepth'] - 1 + targetmaxval = 2**targetbitdepth - 1 + factor = float(targetmaxval) / float(maxval) + info['bitdepth'] = targetbitdepth + + def iterscale(): + for row in pixels: + yield [int(round(x * factor)) for x in row] + if maxval == targetmaxval: + return width, height, pixels, info + else: + return width, height, iterscale(), info + + def asRGB8(self): + """ + Return the image data as an RGB pixels with 8-bits per sample. + This is like the :meth:`asRGB` method except that + this method additionally rescales the values so that + they are all between 0 and 255 (8-bit). + In the case where the source image has a bit depth < 8 + the transformation preserves all the information; + where the source image has bit depth > 8, then + rescaling to 8-bit values loses precision. + No dithering is performed. + Like :meth:`asRGB`, + an alpha channel in the source image will raise an exception. + + This function returns a 4-tuple: + (*width*, *height*, *rows*, *info*). + *width*, *height*, *info* are as per the :meth:`read` method. + + *rows* is the pixel data as a sequence of rows. + """ + + return self._as_rescale(self.asRGB, 8) + + def asRGBA8(self): + """ + Return the image data as RGBA pixels with 8-bits per sample. + This method is similar to :meth:`asRGB8` and :meth:`asRGBA`: + The result pixels have an alpha channel, *and* + values are rescaled to the range 0 to 255. + The alpha channel is synthesized if necessary + (with a small speed penalty). + """ + + return self._as_rescale(self.asRGBA, 8) + + def asRGB(self): + """ + Return image as RGB pixels. + RGB colour images are passed through unchanged; + greyscales are expanded into RGB triplets + (there is a small speed overhead for doing this). + + An alpha channel in the source image will raise an exception. + + The return values are as for the :meth:`read` method except that + the *info* reflect the returned pixels, not the source image. + In particular, + for this method ``info['greyscale']`` will be ``False``. + """ + + width, height, pixels, info = self.asDirect() + if info['alpha']: + raise Error("will not convert image with alpha channel to RGB") + if not info['greyscale']: + return width, height, pixels, info + info['greyscale'] = False + info['planes'] = 3 + + if info['bitdepth'] > 8: + def newarray(): + return array('H', [0]) + else: + def newarray(): + return bytearray([0]) + + def iterrgb(): + for row in pixels: + a = newarray() * 3 * width + for i in range(3): + a[i::3] = row + yield a + return width, height, iterrgb(), info + + def asRGBA(self): + """ + Return image as RGBA pixels. + Greyscales are expanded into RGB triplets; + an alpha channel is synthesized if necessary. + The return values are as for the :meth:`read` method except that + the *info* reflect the returned pixels, not the source image. + In particular, for this method + ``info['greyscale']`` will be ``False``, and + ``info['alpha']`` will be ``True``. + """ + + width, height, pixels, info = self.asDirect() + if info['alpha'] and not info['greyscale']: + return width, height, pixels, info + typecode = 'BH'[info['bitdepth'] > 8] + maxval = 2**info['bitdepth'] - 1 + maxbuffer = struct.pack('=' + typecode, maxval) * 4 * width + + if info['bitdepth'] > 8: + def newarray(): + return array('H', maxbuffer) + else: + def newarray(): + return bytearray(maxbuffer) + + if info['alpha'] and info['greyscale']: + # LA to RGBA + def convert(): + for row in pixels: + # Create a fresh target row, then copy L channel + # into first three target channels, and A channel + # into fourth channel. + a = newarray() + convert_la_to_rgba(row, a) + yield a + elif info['greyscale']: + # L to RGBA + def convert(): + for row in pixels: + a = newarray() + convert_l_to_rgba(row, a) + yield a + else: + assert not info['alpha'] and not info['greyscale'] + # RGB to RGBA + + def convert(): + for row in pixels: + a = newarray() + convert_rgb_to_rgba(row, a) + yield a + info['alpha'] = True + info['greyscale'] = False + info['planes'] = 4 + return width, height, convert(), info + + +def decompress(data_blocks): + """ + `data_blocks` should be an iterable that + yields the compressed data (from the ``IDAT`` chunks). + This yields decompressed byte strings. + """ + + # Currently, with no max_length parameter to decompress, + # this routine will do one yield per IDAT chunk: Not very + # incremental. + d = zlib.decompressobj() + # Each IDAT chunk is passed to the decompressor, then any + # remaining state is decompressed out. + for data in data_blocks: + # :todo: add a max_length argument here to limit output size. + yield bytearray(d.decompress(data)) + yield bytearray(d.flush()) + + +def check_bitdepth_colortype(bitdepth, colortype): + """ + Check that `bitdepth` and `colortype` are both valid, + and specified in a valid combination. + Returns (None) if valid, raise an Exception if not valid. + """ + + if bitdepth not in (1, 2, 4, 8, 16): + raise FormatError("invalid bit depth %d" % bitdepth) + if colortype not in (0, 2, 3, 4, 6): + raise FormatError("invalid colour type %d" % colortype) + # Check indexed (palettized) images have 8 or fewer bits + # per pixel; check only indexed or greyscale images have + # fewer than 8 bits per pixel. + if colortype & 1 and bitdepth > 8: + raise FormatError( + "Indexed images (colour type %d) cannot" + " have bitdepth > 8 (bit depth %d)." + " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." + % (bitdepth, colortype)) + if bitdepth < 8 and colortype not in (0, 3): + raise FormatError( + "Illegal combination of bit depth (%d)" + " and colour type (%d)." + " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." + % (bitdepth, colortype)) + + +def is_natural(x): + """A non-negative integer.""" + try: + is_integer = int(x) == x + except (TypeError, ValueError): + return False + return is_integer and x >= 0 + + +def undo_filter_sub(filter_unit, scanline, previous, result): + """Undo sub filter.""" + + ai = 0 + # Loops starts at index fu. Observe that the initial part + # of the result is already filled in correctly with + # scanline. + for i in range(filter_unit, len(result)): + x = scanline[i] + a = result[ai] + result[i] = (x + a) & 0xff + ai += 1 + + +def undo_filter_up(filter_unit, scanline, previous, result): + """Undo up filter.""" + + for i in range(len(result)): + x = scanline[i] + b = previous[i] + result[i] = (x + b) & 0xff + + +def undo_filter_average(filter_unit, scanline, previous, result): + """Undo up filter.""" + + ai = -filter_unit + for i in range(len(result)): + x = scanline[i] + if ai < 0: + a = 0 + else: + a = result[ai] + b = previous[i] + result[i] = (x + ((a + b) >> 1)) & 0xff + ai += 1 + + +def undo_filter_paeth(filter_unit, scanline, previous, result): + """Undo Paeth filter.""" + + # Also used for ci. + ai = -filter_unit + for i in range(len(result)): + x = scanline[i] + if ai < 0: + a = c = 0 + else: + a = result[ai] + c = previous[ai] + b = previous[i] + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + pr = a + elif pb <= pc: + pr = b + else: + pr = c + result[i] = (x + pr) & 0xff + ai += 1 + + +def convert_la_to_rgba(row, result): + for i in range(3): + result[i::4] = row[0::2] + result[3::4] = row[1::2] + + +def convert_l_to_rgba(row, result): + """ + Convert a grayscale image to RGBA. + This method assumes the alpha channel in result is + already correctly initialized. + """ + for i in range(3): + result[i::4] = row + + +def convert_rgb_to_rgba(row, result): + """ + Convert an RGB image to RGBA. + This method assumes the alpha channel in result is + already correctly initialized. + """ + for i in range(3): + result[i::4] = row[i::3] + + +# Only reason to include this in this module is that +# several utilities need it, and it is small. +def binary_stdin(): + """ + A sys.stdin that returns bytes. + """ + + return sys.stdin.buffer + + +def binary_stdout(): + """ + A sys.stdout that accepts bytes. + """ + + stdout = sys.stdout.buffer + + # On Windows the C runtime file orientation needs changing. + if sys.platform == "win32": + import msvcrt + import os + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + + return stdout + + +def cli_open(path): + if path == "-": + return binary_stdin() + return open(path, "rb") + + +def main(argv): + """ + Run command line PNG. + Which reports version. + """ + print(__version__, __file__) + + +if __name__ == '__main__': + try: + main(sys.argv) + except Error as e: + print(e, file=sys.stderr) diff --git a/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/METADATA new file mode 100644 index 00000000..5c46dbe1 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/METADATA @@ -0,0 +1,525 @@ +Metadata-Version: 2.4 +Name: pyOpenSSL +Version: 25.3.0 +Summary: Python wrapper module around the OpenSSL library +Home-page: https://pyopenssl.org/ +Author: The pyOpenSSL developers +Author-email: cryptography-dev@python.org +License: Apache License, Version 2.0 +Project-URL: Source, https://github.com/pyca/pyopenssl +Classifier: Development Status :: 6 - Mature +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Security :: Cryptography +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Networking +Requires-Python: >=3.7 +License-File: LICENSE +Requires-Dist: cryptography<47,>=45.0.7 +Requires-Dist: typing-extensions>=4.9; python_version < "3.13" and python_version >= "3.8" +Provides-Extra: test +Requires-Dist: pytest-rerunfailures; extra == "test" +Requires-Dist: pretend; extra == "test" +Requires-Dist: pytest>=3.0.1; extra == "test" +Provides-Extra: docs +Requires-Dist: sphinx!=5.2.0,!=5.2.0.post0,!=7.2.5; extra == "docs" +Requires-Dist: sphinx_rtd_theme; extra == "docs" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: project-url +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary + +======================================================== +pyOpenSSL -- A Python wrapper around the OpenSSL library +======================================================== + +.. image:: https://readthedocs.org/projects/pyopenssl/badge/?version=stable + :target: https://pyopenssl.org/en/stable/ + :alt: Stable Docs + +.. image:: https://github.com/pyca/pyopenssl/workflows/CI/badge.svg?branch=main + :target: https://github.com/pyca/pyopenssl/actions?query=workflow%3ACI+branch%3Amain + +**Note:** The Python Cryptographic Authority **strongly suggests** the use of `pyca/cryptography`_ +where possible. If you are using pyOpenSSL for anything other than making a TLS connection +**you should move to cryptography and drop your pyOpenSSL dependency**. + +High-level wrapper around a subset of the OpenSSL library. Includes + +* ``SSL.Connection`` objects, wrapping the methods of Python's portable sockets +* Callbacks written in Python +* Extensive error-handling mechanism, mirroring OpenSSL's error codes + +... and much more. + +You can find more information in the documentation_. +Development takes place on GitHub_. + + +Discussion +========== + +If you run into bugs, you can file them in our `issue tracker`_. + +We maintain a cryptography-dev_ mailing list for both user and development discussions. + +You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get involved. + + +.. _documentation: https://pyopenssl.org/ +.. _`issue tracker`: https://github.com/pyca/pyopenssl/issues +.. _cryptography-dev: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _GitHub: https://github.com/pyca/pyopenssl +.. _`pyca/cryptography`: https://github.com/pyca/cryptography + + +Release Information +=================== + +25.4.0 (UNRELEASED) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +25.3.0 (2025-09-16) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Maximum supported ``cryptography`` version is now 46.x. + + +25.2.0 (2025-09-14) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- The minimum ``cryptography`` version is now 45.0.7. + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- pyOpenSSL now sets ``SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER`` on connections by default, matching CPython's behavior. +- Added ``OpenSSL.SSL.Context.clear_mode``. +- Added ``OpenSSL.SSL.Context.set_tls13_ciphersuites`` to set the allowed TLS 1.3 ciphers. +- Added ``OpenSSL.SSL.Connection.set_info_callback`` + +25.1.0 (2025-05-17) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +- Attempting using any methods that mutate an ``OpenSSL.SSL.Context`` after it + has been used to create an ``OpenSSL.SSL.Connection`` will emit a warning. In + a future release, this will raise an exception. + +Changes: +^^^^^^^^ + +* ``cryptography`` maximum version has been increased to 45.0.x. + + +25.0.0 (2025-01-12) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Corrected type annotations on ``Context.set_alpn_select_callback``, ``Context.set_session_cache_mode``, ``Context.set_options``, ``Context.set_mode``, ``X509.subject_name_hash``, and ``X509Store.load_locations``. +- Deprecated APIs are now marked using ``warnings.deprecated``. ``mypy`` will emit deprecation notices for them when used with ``--enable-error-code deprecated``. + +24.3.0 (2024-11-27) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Removed the deprecated ``OpenSSL.crypto.CRL``, ``OpenSSL.crypto.Revoked``, ``OpenSSL.crypto.dump_crl``, and ``OpenSSL.crypto.load_crl``. ``cryptography.x509``'s CRL functionality should be used instead. +- Removed the deprecated ``OpenSSL.crypto.sign`` and ``OpenSSL.crypto.verify``. ``cryptography.hazmat.primitives.asymmetric``'s signature APIs should be used instead. + +Deprecations: +^^^^^^^^^^^^^ + +- Deprecated ``OpenSSL.rand`` - callers should use ``os.urandom()`` instead. +- Deprecated ``add_extensions`` and ``get_extensions`` on ``OpenSSL.crypto.X509Req`` and ``OpenSSL.crypto.X509``. These should have been deprecated at the same time ``X509Extension`` was. Users should use pyca/cryptography's X.509 APIs instead. +- Deprecated ``OpenSSL.crypto.get_elliptic_curves`` and ``OpenSSL.crypto.get_elliptic_curve``, as well as passing the reult of them to ``OpenSSL.SSL.Context.set_tmp_ecdh``, users should instead pass curves from ``cryptography``. +- Deprecated passing ``X509`` objects to ``OpenSSL.SSL.Context.use_certificate``, ``OpenSSL.SSL.Connection.use_certificate``, ``OpenSSL.SSL.Context.add_extra_chain_cert``, and ``OpenSSL.SSL.Context.add_client_ca``, users should instead pass ``cryptography.x509.Certificate`` instances. This is in preparation for deprecating pyOpenSSL's ``X509`` entirely. +- Deprecated passing ``PKey`` objects to ``OpenSSL.SSL.Context.use_privatekey`` and ``OpenSSL.SSL.Connection.use_privatekey``, users should instead pass ``cryptography`` priate key instances. This is in preparation for deprecating pyOpenSSL's ``PKey`` entirely. + +Changes: +^^^^^^^^ + +* ``cryptography`` maximum version has been increased to 44.0.x. +* ``OpenSSL.SSL.Connection.get_certificate``, ``OpenSSL.SSL.Connection.get_peer_certificate``, ``OpenSSL.SSL.Connection.get_peer_cert_chain``, and ``OpenSSL.SSL.Connection.get_verified_chain`` now take an ``as_cryptography`` keyword-argument. When ``True`` is passed then ``cryptography.x509.Certificate`` are returned, instead of ``OpenSSL.crypto.X509``. In the future, passing ``False`` (the default) will be deprecated. + + +24.2.1 (2024-07-20) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Fixed changelog to remove sphinx specific restructured text strings. + + +24.2.0 (2024-07-20) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +- Deprecated ``OpenSSL.crypto.X509Req``, ``OpenSSL.crypto.load_certificate_request``, ``OpenSSL.crypto.dump_certificate_request``. Instead, ``cryptography.x509.CertificateSigningRequest``, ``cryptography.x509.CertificateSigningRequestBuilder``, ``cryptography.x509.load_der_x509_csr``, or ``cryptography.x509.load_pem_x509_csr`` should be used. + +Changes: +^^^^^^^^ + +- Added type hints for the ``SSL`` module. + `#1308 `_. +- Changed ``OpenSSL.crypto.PKey.from_cryptography_key`` to accept public and private EC, ED25519, ED448 keys. + `#1310 `_. + +24.1.0 (2024-03-09) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Removed the deprecated ``OpenSSL.crypto.PKCS12`` and + ``OpenSSL.crypto.NetscapeSPKI``. ``OpenSSL.crypto.PKCS12`` may be replaced + by the PKCS#12 APIs in the ``cryptography`` package. + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +24.0.0 (2024-01-22) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Added ``OpenSSL.SSL.Connection.get_selected_srtp_profile`` to determine which SRTP profile was negotiated. + `#1279 `_. + +23.3.0 (2023-10-25) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Dropped support for Python 3.6. +- The minimum ``cryptography`` version is now 41.0.5. +- Removed ``OpenSSL.crypto.load_pkcs7`` and ``OpenSSL.crypto.load_pkcs12`` which had been deprecated for 3 years. +- Added ``OpenSSL.SSL.OP_LEGACY_SERVER_CONNECT`` to allow legacy insecure renegotiation between OpenSSL and unpatched servers. + `#1234 `_. + +Deprecations: +^^^^^^^^^^^^^ + +- Deprecated ``OpenSSL.crypto.PKCS12`` (which was intended to have been deprecated at the same time as ``OpenSSL.crypto.load_pkcs12``). +- Deprecated ``OpenSSL.crypto.NetscapeSPKI``. +- Deprecated ``OpenSSL.crypto.CRL`` +- Deprecated ``OpenSSL.crypto.Revoked`` +- Deprecated ``OpenSSL.crypto.load_crl`` and ``OpenSSL.crypto.dump_crl`` +- Deprecated ``OpenSSL.crypto.sign`` and ``OpenSSL.crypto.verify`` +- Deprecated ``OpenSSL.crypto.X509Extension`` + +Changes: +^^^^^^^^ + +- Changed ``OpenSSL.crypto.X509Store.add_crl`` to also accept + ``cryptography``'s ``x509.CertificateRevocationList`` arguments in addition + to the now deprecated ``OpenSSL.crypto.CRL`` arguments. +- Fixed ``test_set_default_verify_paths`` test so that it is skipped if no + network connection is available. + +23.2.0 (2023-05-30) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Removed ``X509StoreFlags.NOTIFY_POLICY``. + `#1213 `_. + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- ``cryptography`` maximum version has been increased to 41.0.x. +- Invalid versions are now rejected in ``OpenSSL.crypto.X509Req.set_version``. +- Added ``X509VerificationCodes`` to ``OpenSSL.SSL``. + `#1202 `_. + +23.1.1 (2023-03-28) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Worked around an issue in OpenSSL 3.1.0 which caused `X509Extension.get_short_name` to raise an exception when no short name was known to OpenSSL. + `#1204 `_. + +23.1.0 (2023-03-24) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- ``cryptography`` maximum version has been increased to 40.0.x. +- Add ``OpenSSL.SSL.Connection.DTLSv1_get_timeout`` and ``OpenSSL.SSL.Connection.DTLSv1_handle_timeout`` + to support DTLS timeouts `#1180 `_. + +23.0.0 (2023-01-01) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Add ``OpenSSL.SSL.X509StoreFlags.PARTIAL_CHAIN`` constant to allow for users + to perform certificate verification on partial certificate chains. + `#1166 `_ +- ``cryptography`` maximum version has been increased to 39.0.x. + +22.1.0 (2022-09-25) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Remove support for SSLv2 and SSLv3. +- The minimum ``cryptography`` version is now 38.0.x (and we now pin releases + against ``cryptography`` major versions to prevent future breakage) +- The ``OpenSSL.crypto.X509StoreContextError`` exception has been refactored, + changing its internal attributes. + `#1133 `_ + +Deprecations: +^^^^^^^^^^^^^ + +- ``OpenSSL.SSL.SSLeay_version`` is deprecated in favor of + ``OpenSSL.SSL.OpenSSL_version``. The constants ``OpenSSL.SSL.SSLEAY_*`` are + deprecated in favor of ``OpenSSL.SSL.OPENSSL_*``. + +Changes: +^^^^^^^^ + +- Add ``OpenSSL.SSL.Connection.set_verify`` and ``OpenSSL.SSL.Connection.get_verify_mode`` + to override the context object's verification flags. + `#1073 `_ +- Add ``OpenSSL.SSL.Connection.use_certificate`` and ``OpenSSL.SSL.Connection.use_privatekey`` + to set a certificate per connection (and not just per context) `#1121 `_. + +22.0.0 (2022-01-29) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Drop support for Python 2.7. + `#1047 `_ +- The minimum ``cryptography`` version is now 35.0. + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Expose wrappers for some `DTLS + `_ + primitives. `#1026 `_ + +21.0.0 (2021-09-28) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- The minimum ``cryptography`` version is now 3.3. +- Drop support for Python 3.5 + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Raise an error when an invalid ALPN value is set. + `#993 `_ +- Added ``OpenSSL.SSL.Context.set_min_proto_version`` and ``OpenSSL.SSL.Context.set_max_proto_version`` + to set the minimum and maximum supported TLS version `#985 `_. +- Updated ``to_cryptography`` and ``from_cryptography`` methods to support an upcoming release of ``cryptography`` without raising deprecation warnings. + `#1030 `_ + +20.0.1 (2020-12-15) +------------------- + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Deprecations: +^^^^^^^^^^^^^ + +Changes: +^^^^^^^^ + +- Fixed compatibility with OpenSSL 1.1.0. + +20.0.0 (2020-11-27) +------------------- + + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- The minimum ``cryptography`` version is now 3.2. +- Remove deprecated ``OpenSSL.tsafe`` module. +- Removed deprecated ``OpenSSL.SSL.Context.set_npn_advertise_callback``, ``OpenSSL.SSL.Context.set_npn_select_callback``, and ``OpenSSL.SSL.Connection.get_next_proto_negotiated``. +- Drop support for Python 3.4 +- Drop support for OpenSSL 1.0.1 and 1.0.2 + +Deprecations: +^^^^^^^^^^^^^ + +- Deprecated ``OpenSSL.crypto.load_pkcs7`` and ``OpenSSL.crypto.load_pkcs12``. + +Changes: +^^^^^^^^ + +- Added a new optional ``chain`` parameter to ``OpenSSL.crypto.X509StoreContext()`` + where additional untrusted certificates can be specified to help chain building. + `#948 `_ +- Added ``OpenSSL.crypto.X509Store.load_locations`` to set trusted + certificate file bundles and/or directories for verification. + `#943 `_ +- Added ``Context.set_keylog_callback`` to log key material. + `#910 `_ +- Added ``OpenSSL.SSL.Connection.get_verified_chain`` to retrieve the + verified certificate chain of the peer. + `#894 `_. +- Make verification callback optional in ``Context.set_verify``. + If omitted, OpenSSL's default verification is used. + `#933 `_ +- Fixed a bug that could truncate or cause a zero-length key error due to a + null byte in private key passphrase in ``OpenSSL.crypto.load_privatekey`` + and ``OpenSSL.crypto.dump_privatekey``. + `#947 `_ + +19.1.0 (2019-11-18) +------------------- + + +Backward-incompatible changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Removed deprecated ``ContextType``, ``ConnectionType``, ``PKeyType``, ``X509NameType``, ``X509ReqType``, ``X509Type``, ``X509StoreType``, ``CRLType``, ``PKCS7Type``, ``PKCS12Type``, and ``NetscapeSPKIType`` aliases. + Use the classes without the ``Type`` suffix instead. + `#814 `_ +- The minimum ``cryptography`` version is now 2.8 due to issues on macOS with a transitive dependency. + `#875 `_ + +Deprecations: +^^^^^^^^^^^^^ + +- Deprecated ``OpenSSL.SSL.Context.set_npn_advertise_callback``, ``OpenSSL.SSL.Context.set_npn_select_callback``, and ``OpenSSL.SSL.Connection.get_next_proto_negotiated``. + ALPN should be used instead. + `#820 `_ + + +Changes: +^^^^^^^^ + +- Support ``bytearray`` in ``SSL.Connection.send()`` by using cffi's from_buffer. + `#852 `_ +- The ``OpenSSL.SSL.Context.set_alpn_select_callback`` can return a new ``NO_OVERLAPPING_PROTOCOLS`` sentinel value + to allow a TLS handshake to complete without an application protocol. + +`Full changelog `_. + diff --git a/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/RECORD new file mode 100644 index 00000000..b1a5c82e --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/RECORD @@ -0,0 +1,21 @@ +OpenSSL/SSL.py,sha256=KzyeY0OEOTCiASBbgYC5AEEQ0qFNDv8JqEqfDz2CmW4,115717 +OpenSSL/__init__.py,sha256=46ENrQmALeGbnIoOeFaa3xEiaGcmoYPa16Dfdp6hMio,497 +OpenSSL/__pycache__/SSL.cpython-312.pyc,, +OpenSSL/__pycache__/__init__.cpython-312.pyc,, +OpenSSL/__pycache__/_util.cpython-312.pyc,, +OpenSSL/__pycache__/crypto.cpython-312.pyc,, +OpenSSL/__pycache__/debug.cpython-312.pyc,, +OpenSSL/__pycache__/rand.cpython-312.pyc,, +OpenSSL/__pycache__/version.cpython-312.pyc,, +OpenSSL/_util.py,sha256=FUIjL9tiCFmyQBBvICPcGd6vqjCmvx-OFKhHEVM-pZE,3692 +OpenSSL/crypto.py,sha256=KxbXAitSJDMQpZ0dsD25osnvgwqrWQ3m1YMK5uLzEhU,79657 +OpenSSL/debug.py,sha256=vCl77f2MslUoTRSu9fqP5DL_9DK_RSC7Jpu07Ip9gQM,1008 +OpenSSL/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +OpenSSL/rand.py,sha256=tSxdA95ITOS24rLCI-tE_hm4V8-i68d6nDGFt4vROQM,1252 +OpenSSL/version.py,sha256=sBsBulyckMCs-65_YlxSzhnC56644kYqbdyQvFIUHps,641 +pyopenssl-25.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyopenssl-25.3.0.dist-info/METADATA,sha256=GvHLx6jxAeyBfkStQxKGIc5ZVEyOXEFYUn2n2hDzmBY,17950 +pyopenssl-25.3.0.dist-info/RECORD,, +pyopenssl-25.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 +pyopenssl-25.3.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +pyopenssl-25.3.0.dist-info/top_level.txt,sha256=NNxWqS8hKNJh2cUXa1RZOMX62VJfyd8URo1TsYnR_MU,8 diff --git a/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/WHEEL new file mode 100644 index 00000000..e7fa31b6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/licenses/LICENSE b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/licenses/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/licenses/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/top_level.txt new file mode 100644 index 00000000..effce34b --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyopenssl-25.3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +OpenSSL diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/LICENSE b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/LICENSE new file mode 100644 index 00000000..13e68178 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2011-2021 Mark Percival , +Nathan Reynolds , Andrey Kislyuk , +and PyOTP contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/METADATA new file mode 100644 index 00000000..268fa934 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/METADATA @@ -0,0 +1,215 @@ +Metadata-Version: 2.1 +Name: pyotp +Version: 2.9.0 +Summary: Python One Time Password Library +Home-page: https://github.com/pyotp/pyotp +Author: PyOTP contributors +Author-email: kislyuk@gmail.com +License: MIT License +Project-URL: Documentation, https://pyauth.github.io/pyotp +Project-URL: Source Code, https://github.com/pyauth/pyotp +Project-URL: Issue Tracker, https://github.com/pyauth/pyotp/issues +Project-URL: Change Log, https://github.com/pyauth/pyotp/blob/master/Changes.rst +Platform: MacOS X +Platform: Posix +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.7 +License-File: LICENSE +Provides-Extra: test +Requires-Dist: coverage ; extra == 'test' +Requires-Dist: wheel ; extra == 'test' +Requires-Dist: ruff ; extra == 'test' +Requires-Dist: mypy ; extra == 'test' + +PyOTP - The Python One-Time Password Library +============================================ + +PyOTP is a Python library for generating and verifying one-time passwords. It can be used to implement two-factor (2FA) +or multi-factor (MFA) authentication methods in web applications and in other systems that require users to log in. + +Open MFA standards are defined in `RFC 4226 `_ (HOTP: An HMAC-Based One-Time +Password Algorithm) and in `RFC 6238 `_ (TOTP: Time-Based One-Time Password +Algorithm). PyOTP implements server-side support for both of these standards. Client-side support can be enabled by +sending authentication codes to users over SMS or email (HOTP) or, for TOTP, by instructing users to use `Google +Authenticator `_, `Authy `_, or another +compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth:// +`_ QR codes provided by PyOTP. + +Implementers should read and follow the `HOTP security requirements `_ +and `TOTP security considerations `_ sections of the relevant RFCs. At +minimum, application implementers should follow this checklist: + +- Ensure transport confidentiality by using HTTPS +- Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled access database +- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most + recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is + seen) +- Throttle (rate limit) brute-force attacks against your application's login functionality (see RFC 4226, section 7.3) +- When implementing a "greenfield" application, consider supporting + `FIDO U2F `_/`WebAuthn `_ in + addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a shared secret design, which strengthens your + MFA solution against server-side attacks. Hardware U2F also sequesters the client secret in a dedicated single-purpose + device, which strengthens your clients against client-side attacks. And by automating scoping of credentials to + relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation + of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP `_. + +We also recommend that implementers read the +`OWASP Authentication Cheat Sheet +`_ and +`NIST SP 800-63-3: Digital Authentication Guideline `_ for a high level overview of +authentication best practices. + +Quick overview of using One Time Passwords on your phone +-------------------------------------------------------- + +* OTPs involve a shared secret, stored both on the phone and the server +* OTPs can be generated on a phone without internet connectivity +* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured + with a password) +* Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR + Code + +Installation +------------ +:: + + pip install pyotp + +Usage +----- + +Time-based OTPs +~~~~~~~~~~~~~~~ +:: + + import pyotp + import time + + totp = pyotp.TOTP('base32secret3232') + totp.now() # => '492039' + + # OTP verified for current time + totp.verify('492039') # => True + time.sleep(30) + totp.verify('492039') # => False + +Counter-based OTPs +~~~~~~~~~~~~~~~~~~ +:: + + import pyotp + + hotp = pyotp.HOTP('base32secret3232') + hotp.at(0) # => '260182' + hotp.at(1) # => '055283' + hotp.at(1401) # => '316439' + + # OTP verified with a counter + hotp.verify('316439', 1401) # => True + hotp.verify('316439', 1402) # => False + +Generating a Secret Key +~~~~~~~~~~~~~~~~~~~~~~~ +A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other +OTP apps:: + + pyotp.random_base32() + +Some applications want the secret key to be formatted as a hex-encoded string:: + + pyotp.random_hex() # returns a 40-character hex-encoded secret + +Google Authenticator Compatible +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes +the ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps:: + + pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='alice@google.com', issuer_name='Secure App') + + >>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App' + + pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name="alice@google.com", issuer_name="Secure App", initial_count=0) + + >>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0' + +This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be +scanned and added to the users list of OTP credentials. + +Parsing these URLs is also supported:: + + pyotp.parse_uri('otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App') + + >>> + + pyotp.parse_uri('otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0' + + >>> + +Working example +~~~~~~~~~~~~~~~ + +Scan the following barcode with your phone's OTP app (e.g. Google Authenticator): + +.. image:: https://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP + +Now run the following and compare the output:: + + import pyotp + totp = pyotp.TOTP("JBSWY3DPEHPK3PXP") + print("Current OTP:", totp.now()) + +Third-party contributions +~~~~~~~~~~~~~~~~~~~~~~~~~ +The following third-party contributions are not described by a standard, not officially supported, and provided for +reference only: + +* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same API as `pyotp.TOTP()`. + +Links +~~~~~ + +* `Project home page (GitHub) `_ +* `Documentation `_ +* `Package distribution (PyPI) `_ +* `Change log `_ +* `RFC 4226: HOTP: An HMAC-Based One-Time Password `_ +* `RFC 6238: TOTP: Time-Based One-Time Password Algorithm `_ +* `ROTP `_ - Original Ruby OTP library by `Mark Percival `_ +* `OTPHP `_ - PHP port of ROTP by `Le Lag `_ +* `OWASP Authentication Cheat Sheet `_ +* `NIST SP 800-63-3: Digital Authentication Guideline `_ + +For new applications: + +* `WebAuthn `_ +* `PyWARP `_ + +Versioning +~~~~~~~~~~ +This package follows the `Semantic Versioning 2.0.0 `_ standard. To control changes, it is +recommended that application developers pin the package version and manage it using `pip-tools +`_ or similar. For library developers, pinning the major version is +recommended. + +.. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg + :target: https://github.com/pyauth/pyotp/actions +.. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg + :target: https://codecov.io/github/pyauth/pyotp?branch=master +.. image:: https://img.shields.io/pypi/v/pyotp.svg + :target: https://pypi.python.org/pypi/pyotp +.. image:: https://img.shields.io/pypi/l/pyotp.svg + :target: https://pypi.python.org/pypi/pyotp +.. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest + :target: https://pyotp.readthedocs.io/ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/RECORD new file mode 100644 index 00000000..d405836e --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/RECORD @@ -0,0 +1,24 @@ +pyotp-2.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyotp-2.9.0.dist-info/LICENSE,sha256=eViFEjNBRYFyqtbr-rMbBxzHtAUkkp5pAWYtXbv1fqM,1174 +pyotp-2.9.0.dist-info/METADATA,sha256=tzMB3HMnzN03wYxnEUKlcunov3bdUamPfRhStls9KgQ,9841 +pyotp-2.9.0.dist-info/RECORD,, +pyotp-2.9.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyotp-2.9.0.dist-info/WHEEL,sha256=AtBG6SXL3KF_v0NxLf0ehyVOh0cold-JbJYXNGorC6Q,92 +pyotp-2.9.0.dist-info/top_level.txt,sha256=rFDgz6Sm4nbkYk6tlSRiMcJPyYdnY6LcZiAaYTxVc5k,6 +pyotp/__init__.py,sha256=9Apoa0L4aEPVRk2EzT-5ZY26vma_33YEttZczJVHVtY,3792 +pyotp/__pycache__/__init__.cpython-312.pyc,, +pyotp/__pycache__/compat.cpython-312.pyc,, +pyotp/__pycache__/hotp.cpython-312.pyc,, +pyotp/__pycache__/otp.cpython-312.pyc,, +pyotp/__pycache__/totp.cpython-312.pyc,, +pyotp/__pycache__/utils.cpython-312.pyc,, +pyotp/compat.py,sha256=i-pxcRWpShxkMTMucqPJdPJibYUaVtEhL9pyweKQ84k,209 +pyotp/contrib/__init__.py,sha256=2hKOYyF4NnZ09Rw3FmeoYmk1l7l631cgIpj2kjzbKfw,38 +pyotp/contrib/__pycache__/__init__.cpython-312.pyc,, +pyotp/contrib/__pycache__/steam.cpython-312.pyc,, +pyotp/contrib/steam.py,sha256=ouLep7jWIkW1nw-GUVXU8RZ6NO3qEaQoCMnlkJzZWKk,1410 +pyotp/hotp.py,sha256=mQZuyB4Cq5Pv8-k_yCUlUz0OVMGXG5nH6T_foTN2eo8,2672 +pyotp/otp.py,sha256=MdEXmgLPyb-weylhF_xvlICRJN7g2clOLnKK-h5y3Lc,2222 +pyotp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyotp/totp.py,sha256=SqE6vJaoyRABAA7L1WP-FRam2O8LP4Z4m24eFo_X_WE,4096 +pyotp/utils.py,sha256=svycyd8gatRF9uXrONq8A6X7FKdBXaMfqtotEfU3Dqk,3120 diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/WHEEL new file mode 100644 index 00000000..d272f6ed --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/top_level.txt new file mode 100644 index 00000000..6c1907dc --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp-2.9.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pyotp diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__init__.py b/Backend/venv/lib/python3.12/site-packages/pyotp/__init__.py new file mode 100644 index 00000000..a6957901 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/__init__.py @@ -0,0 +1,110 @@ +import hashlib +from re import split +from typing import Any, Dict, Sequence +from urllib.parse import parse_qsl, unquote, urlparse + +from . import contrib # noqa:F401 +from .compat import random +from .hotp import HOTP as HOTP +from .otp import OTP as OTP +from .totp import TOTP as TOTP + + +def random_base32(length: int = 32, chars: Sequence[str] = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")) -> str: + # Note: the otpauth scheme DOES NOT use base32 padding for secret lengths not divisible by 8. + # Some third-party tools have bugs when dealing with such secrets. + # We might consider warning the user when generating a secret of length not divisible by 8. + if length < 32: + raise ValueError("Secrets should be at least 160 bits") + + return "".join(random.choice(chars) for _ in range(length)) + + +def random_hex(length: int = 40, chars: Sequence[str] = list("ABCDEF0123456789")) -> str: + if length < 40: + raise ValueError("Secrets should be at least 160 bits") + return random_base32(length=length, chars=chars) + + +def parse_uri(uri: str) -> OTP: + """ + Parses the provisioning URI for the OTP; works for either TOTP or HOTP. + + See also: + https://github.com/google/google-authenticator/wiki/Key-Uri-Format + + :param uri: the hotp/totp URI to parse + :returns: OTP object + """ + + # Secret (to be filled in later) + secret = None + + # Encoder (to be filled in later) + encoder = None + + # Digits (to be filled in later) + digits = None + + # Data we'll parse to the correct constructor + otp_data: Dict[str, Any] = {} + + # Parse with URLlib + parsed_uri = urlparse(unquote(uri)) + + if parsed_uri.scheme != "otpauth": + raise ValueError("Not an otpauth URI") + + # Parse issuer/accountname info + accountinfo_parts = split(":|%3A", parsed_uri.path[1:], maxsplit=1) + if len(accountinfo_parts) == 1: + otp_data["name"] = accountinfo_parts[0] + else: + otp_data["issuer"] = accountinfo_parts[0] + otp_data["name"] = accountinfo_parts[1] + + # Parse values + for key, value in parse_qsl(parsed_uri.query): + if key == "secret": + secret = value + elif key == "issuer": + if "issuer" in otp_data and otp_data["issuer"] is not None and otp_data["issuer"] != value: + raise ValueError("If issuer is specified in both label and parameters, it should be equal.") + otp_data["issuer"] = value + elif key == "algorithm": + if value == "SHA1": + otp_data["digest"] = hashlib.sha1 + elif value == "SHA256": + otp_data["digest"] = hashlib.sha256 + elif value == "SHA512": + otp_data["digest"] = hashlib.sha512 + else: + raise ValueError("Invalid value for algorithm, must be SHA1, SHA256 or SHA512") + elif key == "encoder": + encoder = value + elif key == "digits": + digits = int(value) + otp_data["digits"] = digits + elif key == "period": + otp_data["interval"] = int(value) + elif key == "counter": + otp_data["initial_count"] = int(value) + elif key != "image": + raise ValueError("{} is not a valid parameter".format(key)) + + if encoder != "steam": + if digits is not None and digits not in [6, 7, 8]: + raise ValueError("Digits may only be 6, 7, or 8") + + if not secret: + raise ValueError("No secret found in URI") + + # Create objects + if encoder == "steam": + return contrib.Steam(secret, **otp_data) + if parsed_uri.netloc == "totp": + return TOTP(secret, **otp_data) + elif parsed_uri.netloc == "hotp": + return HOTP(secret, **otp_data) + + raise ValueError("Not a supported OTP type") diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e5b24c86 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc new file mode 100644 index 00000000..2e3e0d74 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc new file mode 100644 index 00000000..19adacb8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc new file mode 100644 index 00000000..984d32d7 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc new file mode 100644 index 00000000..ebc4e4d7 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc new file mode 100644 index 00000000..cd05b9d9 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/compat.py b/Backend/venv/lib/python3.12/site-packages/pyotp/compat.py new file mode 100644 index 00000000..79758c09 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/compat.py @@ -0,0 +1,7 @@ +# Use secrets module if available (Python version >= 3.6) per PEP 506 +try: + from secrets import SystemRandom # type: ignore +except ImportError: + from random import SystemRandom + +random = SystemRandom() diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__init__.py b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__init__.py new file mode 100644 index 00000000..cdd567d5 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__init__.py @@ -0,0 +1 @@ +from .steam import Steam # noqa:F401 diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..13cd7472 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc new file mode 100644 index 00000000..c1ecddfb Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/steam.py b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/steam.py new file mode 100644 index 00000000..404ef817 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/steam.py @@ -0,0 +1,49 @@ +import hashlib +from typing import Optional + +from ..totp import TOTP + +STEAM_CHARS = "23456789BCDFGHJKMNPQRTVWXY" # steam's custom alphabet +STEAM_DEFAULT_DIGITS = 5 # Steam TOTP code length + + +class Steam(TOTP): + """ + Steam's custom TOTP. Subclass of `pyotp.totp.TOTP`. + """ + + def __init__( + self, + s: str, + name: Optional[str] = None, + issuer: Optional[str] = None, + interval: int = 30, + digits: int = 5 + ) -> None: + """ + :param s: secret in base32 format + :param interval: the time interval in seconds for OTP. This defaults to 30. + :param name: account name + :param issuer: issuer + """ + self.interval = interval + super().__init__(s=s, digits=10, digest=hashlib.sha1, name=name, issuer=issuer) + + def generate_otp(self, input: int) -> str: + """ + :param input: the HMAC counter value to use as the OTP input. + Usually either the counter, or the computed integer based on the Unix timestamp + """ + str_code = super().generate_otp(input) + int_code = int(str_code) + + steam_code = "" + total_chars = len(STEAM_CHARS) + + for _ in range(STEAM_DEFAULT_DIGITS): + pos = int_code % total_chars + char = STEAM_CHARS[int(pos)] + steam_code += char + int_code //= total_chars + + return steam_code diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/hotp.py b/Backend/venv/lib/python3.12/site-packages/pyotp/hotp.py new file mode 100644 index 00000000..5c20531c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/hotp.py @@ -0,0 +1,83 @@ +import hashlib +from typing import Any, Optional + +from . import utils +from .otp import OTP + + +class HOTP(OTP): + """ + Handler for HMAC-based OTP counters. + """ + + def __init__( + self, + s: str, + digits: int = 6, + digest: Any = None, + name: Optional[str] = None, + issuer: Optional[str] = None, + initial_count: int = 0, + ) -> None: + """ + :param s: secret in base32 format + :param initial_count: starting HMAC counter value, defaults to 0 + :param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more. + :param digest: digest function to use in the HMAC (expected to be SHA1) + :param name: account name + :param issuer: issuer + """ + if digest is None: + digest = hashlib.sha1 + + self.initial_count = initial_count + super().__init__(s=s, digits=digits, digest=digest, name=name, issuer=issuer) + + def at(self, count: int) -> str: + """ + Generates the OTP for the given count. + + :param count: the OTP HMAC counter + :returns: OTP + """ + return self.generate_otp(self.initial_count + count) + + def verify(self, otp: str, counter: int) -> bool: + """ + Verifies the OTP passed in against the current counter OTP. + + :param otp: the OTP to check against + :param counter: the OTP HMAC counter + """ + return utils.strings_equal(str(otp), str(self.at(counter))) + + def provisioning_uri( + self, + name: Optional[str] = None, + initial_count: Optional[int] = None, + issuer_name: Optional[str] = None, + image: Optional[str] = None, + ) -> str: + """ + Returns the provisioning URI for the OTP. This can then be + encoded in a QR Code and used to provision an OTP app like + Google Authenticator. + + See also: + https://github.com/google/google-authenticator/wiki/Key-Uri-Format + + :param name: name of the user account + :param initial_count: starting HMAC counter value, defaults to 0 + :param issuer_name: the name of the OTP issuer; this will be the + organization title of the OTP entry in Authenticator + :returns: provisioning URI + """ + return utils.build_uri( + self.secret, + name=name if name else self.name, + initial_count=initial_count if initial_count else self.initial_count, + issuer=issuer_name if issuer_name else self.issuer, + algorithm=self.digest().name, + digits=self.digits, + image=image, + ) diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/otp.py b/Backend/venv/lib/python3.12/site-packages/pyotp/otp.py new file mode 100644 index 00000000..9f0d1afa --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/otp.py @@ -0,0 +1,68 @@ +import base64 +import hashlib +import hmac +from typing import Any, Optional + + +class OTP(object): + """ + Base class for OTP handlers. + """ + + def __init__( + self, + s: str, + digits: int = 6, + digest: Any = hashlib.sha1, + name: Optional[str] = None, + issuer: Optional[str] = None, + ) -> None: + self.digits = digits + if digits > 10: + raise ValueError("digits must be no greater than 10") + self.digest = digest + self.secret = s + self.name = name or "Secret" + self.issuer = issuer + + def generate_otp(self, input: int) -> str: + """ + :param input: the HMAC counter value to use as the OTP input. + Usually either the counter, or the computed integer based on the Unix timestamp + """ + if input < 0: + raise ValueError("input must be positive integer") + hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest) + hmac_hash = bytearray(hasher.digest()) + offset = hmac_hash[-1] & 0xF + code = ( + (hmac_hash[offset] & 0x7F) << 24 + | (hmac_hash[offset + 1] & 0xFF) << 16 + | (hmac_hash[offset + 2] & 0xFF) << 8 + | (hmac_hash[offset + 3] & 0xFF) + ) + str_code = str(10_000_000_000 + (code % 10**self.digits)) + return str_code[-self.digits :] + + def byte_secret(self) -> bytes: + secret = self.secret + missing_padding = len(secret) % 8 + if missing_padding != 0: + secret += "=" * (8 - missing_padding) + return base64.b32decode(secret, casefold=True) + + @staticmethod + def int_to_bytestring(i: int, padding: int = 8) -> bytes: + """ + Turns an integer to the OATH specified + bytestring, which is fed to the HMAC + along with the secret + """ + result = bytearray() + while i != 0: + result.append(i & 0xFF) + i >>= 8 + # It's necessary to convert the final result from bytearray to bytes + # because the hmac functions in python 2.6 and 3.3 don't work with + # bytearray + return bytes(bytearray(reversed(result)).rjust(padding, b"\0")) diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/py.typed b/Backend/venv/lib/python3.12/site-packages/pyotp/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/totp.py b/Backend/venv/lib/python3.12/site-packages/pyotp/totp.py new file mode 100644 index 00000000..b04e665a --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/totp.py @@ -0,0 +1,119 @@ +import calendar +import datetime +import hashlib +import time +from typing import Any, Optional, Union + +from . import utils +from .otp import OTP + + +class TOTP(OTP): + """ + Handler for time-based OTP counters. + """ + + def __init__( + self, + s: str, + digits: int = 6, + digest: Any = None, + name: Optional[str] = None, + issuer: Optional[str] = None, + interval: int = 30, + ) -> None: + """ + :param s: secret in base32 format + :param interval: the time interval in seconds for OTP. This defaults to 30. + :param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more. + :param digest: digest function to use in the HMAC (expected to be SHA1) + :param name: account name + :param issuer: issuer + """ + if digest is None: + digest = hashlib.sha1 + + self.interval = interval + super().__init__(s=s, digits=digits, digest=digest, name=name, issuer=issuer) + + def at(self, for_time: Union[int, datetime.datetime], counter_offset: int = 0) -> str: + """ + Accepts either a Unix timestamp integer or a datetime object. + + To get the time until the next timecode change (seconds until the current OTP expires), use this instead: + + .. code:: python + + totp = pyotp.TOTP(...) + time_remaining = totp.interval - datetime.datetime.now().timestamp() % totp.interval + + :param for_time: the time to generate an OTP for + :param counter_offset: the amount of ticks to add to the time counter + :returns: OTP value + """ + if not isinstance(for_time, datetime.datetime): + for_time = datetime.datetime.fromtimestamp(int(for_time)) + return self.generate_otp(self.timecode(for_time) + counter_offset) + + def now(self) -> str: + """ + Generate the current time OTP + + :returns: OTP value + """ + return self.generate_otp(self.timecode(datetime.datetime.now())) + + def verify(self, otp: str, for_time: Optional[datetime.datetime] = None, valid_window: int = 0) -> bool: + """ + Verifies the OTP passed in against the current time OTP. + + :param otp: the OTP to check against + :param for_time: Time to check OTP at (defaults to now) + :param valid_window: extends the validity to this many counter ticks before and after the current one + :returns: True if verification succeeded, False otherwise + """ + if for_time is None: + for_time = datetime.datetime.now() + + if valid_window: + for i in range(-valid_window, valid_window + 1): + if utils.strings_equal(str(otp), str(self.at(for_time, i))): + return True + return False + + return utils.strings_equal(str(otp), str(self.at(for_time))) + + def provisioning_uri( + self, name: Optional[str] = None, issuer_name: Optional[str] = None, image: Optional[str] = None + ) -> str: + + """ + Returns the provisioning URI for the OTP. This can then be + encoded in a QR Code and used to provision an OTP app like + Google Authenticator. + + See also: + https://github.com/google/google-authenticator/wiki/Key-Uri-Format + + """ + return utils.build_uri( + self.secret, + name if name else self.name, + issuer=issuer_name if issuer_name else self.issuer, + algorithm=self.digest().name, + digits=self.digits, + period=self.interval, + image=image, + ) + + def timecode(self, for_time: datetime.datetime) -> int: + """ + Accepts either a timezone naive (`for_time.tzinfo is None`) or + a timezone aware datetime as argument and returns the + corresponding counter value (timecode). + + """ + if for_time.tzinfo: + return int(calendar.timegm(for_time.utctimetuple()) / self.interval) + else: + return int(time.mktime(for_time.timetuple()) / self.interval) diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/utils.py b/Backend/venv/lib/python3.12/site-packages/pyotp/utils.py new file mode 100644 index 00000000..f8608ccb --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pyotp/utils.py @@ -0,0 +1,88 @@ +import unicodedata +from hmac import compare_digest +from typing import Dict, Optional, Union +from urllib.parse import quote, urlencode, urlparse + + +def build_uri( + secret: str, + name: str, + initial_count: Optional[int] = None, + issuer: Optional[str] = None, + algorithm: Optional[str] = None, + digits: Optional[int] = None, + period: Optional[int] = None, + image: Optional[str] = None, +) -> str: + """ + Returns the provisioning URI for the OTP; works for either TOTP or HOTP. + + This can then be encoded in a QR Code and used to provision the Google + Authenticator app. + + For module-internal use. + + See also: + https://github.com/google/google-authenticator/wiki/Key-Uri-Format + + :param secret: the hotp/totp secret used to generate the URI + :param name: name of the account + :param initial_count: starting counter value, defaults to None. + If none, the OTP type will be assumed as TOTP. + :param issuer: the name of the OTP issuer; this will be the + organization title of the OTP entry in Authenticator + :param algorithm: the algorithm used in the OTP generation. + :param digits: the length of the OTP generated code. + :param period: the number of seconds the OTP generator is set to + expire every code. + :param image: optional logo image url + :returns: provisioning uri + """ + # initial_count may be 0 as a valid param + is_initial_count_present = initial_count is not None + + # Handling values different from defaults + is_algorithm_set = algorithm is not None and algorithm != "sha1" + is_digits_set = digits is not None and digits != 6 + is_period_set = period is not None and period != 30 + + otp_type = "hotp" if is_initial_count_present else "totp" + base_uri = "otpauth://{0}/{1}?{2}" + + url_args: Dict[str, Union[None, int, str]] = {"secret": secret} + + label = quote(name) + if issuer is not None: + label = quote(issuer) + ":" + label + url_args["issuer"] = issuer + + if is_initial_count_present: + url_args["counter"] = initial_count + if is_algorithm_set: + url_args["algorithm"] = algorithm.upper() # type: ignore + if is_digits_set: + url_args["digits"] = digits + if is_period_set: + url_args["period"] = period + if image: + image_uri = urlparse(image) + if image_uri.scheme != "https" or not image_uri.netloc or not image_uri.path: + raise ValueError("{} is not a valid url".format(image_uri)) + url_args["image"] = image + + uri = base_uri.format(otp_type, label, urlencode(url_args).replace("+", "%20")) + return uri + + +def strings_equal(s1: str, s2: str) -> bool: + """ + Timing-attack resistant string comparison. + + Normal comparison using == will short-circuit on the first mismatching + character. This avoids that by scanning the whole string, though we + still reveal to a timing attack whether the strings are the same + length. + """ + s1 = unicodedata.normalize("NFKC", s1) + s2 = unicodedata.normalize("NFKC", s2) + return compare_digest(s1.encode("utf-8"), s2.encode("utf-8")) diff --git a/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/LICENCE b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/LICENCE new file mode 100644 index 00000000..097f9dc3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/LICENCE @@ -0,0 +1,21 @@ +LICENCE (MIT) + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/METADATA new file mode 100644 index 00000000..4c9d3b94 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/METADATA @@ -0,0 +1,479 @@ +Metadata-Version: 2.1 +Name: pypng +Version: 0.20220715.0 +Summary: Pure Python library for saving and loading PNG images +Home-page: https://gitlab.com/drj11/pypng +Author: David Jones +Author-email: drj@pobox.com +License: MIT +Classifier: Topic :: Multimedia :: Graphics +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Description-Content-Type: text/markdown; charset=UTF-8 +License-File: LICENCE + +# README for PyPNG + +drj@pobox.com + + +# INTRODUCTION + +PNG module for Python. PyPNG is written entirely in Python. + +- PyPNG home page: https://gitlab.com/drj11/pypng/ +- PyPNG Documentation: https://pypng.readthedocs.io/en/latest/ +- PyPNG mailing list: https://groups.google.com/forum/#!forum/pypng-users + + +## QUICK START + + import png + png.from_array([[255, 0, 0, 255], + [0, 255, 255, 0]], 'L').save("small_smiley.png") + +After that, try `import png` then `help(png)`. +Also, lickable HTML documentation appears in the `html/` directory. +If HTML is no good then you could try the ReST sources +in the `man/` directory. + + +## INSTALLATION + +PyPNG is pure Python and has no dependencies. +It requires Python 3.5 or any compatible higher version. + +To install PyPNG package via pip use: + + python -m pip install git+https://gitlab.com/drj11/pypng@pypng-0.20220715.0 + +After install use + + `import png` + +to access the `png` module in your Python program. + +You can also install from source using `setuptools`. +PyPNG uses `setup.cfg` and `pyproject.toml` to record its +configuration. + +To install from (version controlled) sources using a suitable +version of `pip`: + +`cd` into the directory and execute the command: + + python -m pip install . + +PyPNG is so simple, that you don't need installation tools. +You can copy `code/png.py` wherever you like. +It's intended that you can copy `png.py` into +your application and distribute it. +The following `curl` command should copy the latest version into +your current directory: + + curl -LO https://gitlab.com/drj11/pypng/-/raw/main/code/png.py + + +## RELEASE NOTES + +(For issues see https://gitlab.com/drj11/pypng/-/issues/ ) + + +### Release (the next) + +### Release 0.20220715.0 + +Development moved to gitlab: https://gitlab.com/drj11/pypng + +If you pass an empty file to PyPNG +it now raises the builtin Python exception `EOFError`. +This should make it easier to diagnose _empty file_ problems separately +from genuine format errors +(which use `png.FormatError`). +This is a slightly breaking change to the API. + +New `prirowpng` tool to join PNG images in a row left-to-right +(old internal `pipcat` tool). + +New `pricolpng` tool to join PNG images in a column top-to-bottom. + +Support for plain PGM files (magic number P2) added to `pripamtopng`. + +New `priplan9topng` tool to convert from Plan 9 image format to PNG. +In reality this has been lurking in the codebase for years, but +has recently been converted to Python 3. +The author has only a limited collection of Plan 9 images, +which limits the testing that can be done. +The author welcomes bug reports for Plan 9 images. + +The `priplan9topng` tool has an even more experimental option +`--font` which converts Plan 9 subfont files to a sequence of +PNG files. + + +### Release 0.0.21 + +Support for Python 2 is dropped. +Python 3.5 and onwards are supported. +Some of the ancillary tools are modified to work on Python 3. + +Installs via wheel files. + +`prichunkpng` command line tool now has some new options to add +chunks: +- `--iccprofile` to add a `iCCP` chunk (ICC Profile); +- `--physical` to add a `pHYs` chunk, + specifying the intended pixel size; +- `--sigbit` to add a `sBIT` chunk, + specifying the encoded significant bits; +- `--transparent` to add a `tRNS` chunk, + specifying the transparent colour. + +`priditherpng` command line tool standardised and +converted to Python 3. + +`pripngtopam` tool now has a `--plain` option to output plain PGM +and PPM formats. The `topam` part of the name is a bit of a +misnomer: when possible (L and RGB PNG files) the tool will +output either a PGM (grey) or a PPM (RGB) file. Essentially all +tools that can process a PAM file can also process a PGM or a +PPM file. PAM files cannot be _plain_ so using the option +will raise an error in the case where a true PAM file is +written. + +Better error messages when you write the wrong number of rows. + +(Slightly experimentally) running the `png` module as a command +line tool, with `python -m png`, will report the version and +file location of the `png` module. + + +### Release 0.0.20 + +Support for earlier versions of Python is dropped. +Python 3.4 and onwards are supported. +Python 2.7 also works, but this is the last +release to support any version of Python 2. + +Cython code is removed, which simplifies the implementation. + +Removed the (optional) dependency `setuptools`. + +Changed the default for `png.Writer` to be greyscale. + +Removed 3D support from `.from_array()`. + + +### Release 0.0.19 + +Support for earlier versions of Python is dropped in order to +simplify the code. +From the Python 3.x series all versions from 3.2 onward are supported. +From the 2.x series only Python 2.6 and 2.7 are supported. + +Code cleaned. +Tests renamed and more organised. +Generally Flake 8 compliant. +Fewer special cases for ancient versions of Python. + +The row length is checked when writing PNG files. +Previously it was possible to write badly formed PNG files +by passing in rows of the wrong length. + +`pHYS` chunk is now processed. + +The `Writer()` interface now supports source pixels +that have a different bitdepth for each channel. +To exploit this, pass in a tuple for the bitdepth argument. + +Ancillary tools regularised and simplified. + +`pripamtopng` command line tool converts NetPBM PNM/PAM files to PNG. +Previously `png.py` did this. +Note that only one input file is converted, +if you have intensity and opacity in separate files, +you have two options: +either use `pamstack` to convert them into a single PAM file and +convert that, or +convert each file to PNG, then use `priweavepng` to weave them together. +Both will work. + +`pripngtopam` command line tool converts PNG to NetPBM PNM/PAM file. +Previously `png.py` did this. + +`python -m pngsuite` is now a command line tool to +write various images from the PNG suite of test images. +Previously this was possible using `gen`. + +`priweavepng` command line tool performs channel extraction across +multiple images. +This is a more general version of `pipstack` (which has been removed), +and is inspired by `pamstack` from NetPBM. + +The `--interlace` option available on many previous tools is +now only available on `priweavepng`, +making it the preferred tool for generating interlaced PNGs. + +`prichunkpng` command line tool adds and deletes chunks and +"intelligently" knows about transparent, gamma, background chunks. +It is the preferred tool for adding those chunks, +which was previously possible using various options of other tools. + +`gen` has been renamed to `priforgepng`. + +`priforgepng` uses centre pixel sampling, which means that a 256 pixel +wide 8-bit gradient takes on all possible 256 values. +It also improves output for very small images. + +`priforgepng` uses `Fraction` for internal maths meaning that +the stripe patterns are accurate and do not have loose pixels. + +`priforgepng` only outputs greyscale PNG files +(but see next item for a feature to generate colour PNGs). + +`priforgepng` can output multiple PNGs onto the same stream. +This aligns well with a feature of `priweavepng` which +accepts multiple PNGs from stdin. +LA, RGB, and RGBA test images can be generated by +piping `priforgepng` into `priweavepng`: +`priforgepng RTL RTR RBR | priweavepng - - -` +will generate an RGB PNG. + + +### Release 0.0.18 + +Thanks to `github.com/sean-duffy`, +`.from_array()` can now take a 3D array. + +Converting to PNMs was broken in Python 3; this is now fixed. +Issue 26: https://gitlab.com/drj11/pypng/-/issues/26 + + +### Release 0.0.17 + +Various fixes when running on Python 3 and Windows. +Merging pull requests from `github.com/ironfroggy` and +`github.com/techtonik`, +and merging by hand a commit from `github.com/Scondo`. + + +### Release 0.0.16 + +Compatible with nose: `nosetests png.py` now works. + +Allow any "file-like" object as an input. + +Handle newlines in `texttopng`. + + +### Release 0.0.15 + +Fixed various URLs to point at github.com instead of googlecode. + + +### Release 0.0.14 + +When using `png.py` as a command line tool, +it can now produce non-square test images. + +PyPNG now installs "out of the box" on Python 3 on a plain install +(previously `distribute` or `pip` was required). + +PyPNG welcomes the following community contributions: + + Joaquín Cuenca Abela speeds up PNG reading when Cython is available. + + Josh Bleecher Snyder adds a lenient mode + which has relaxed checksum checking. + + nathan@dunfield.info fixed a problem writing files + when using the command line tool on Windows (Issue 62). + +The following issues have been fixed: + + On github: + + Issue 6: Palette processing is annoying + + On googlecode: + + Issue 62: Problem writing PNG files on Windows + +Development has moved from googlecode to github. +All issues below here, and the one immediately above, +are from the googlecode issue tracker. +All newer issue should be on github. + + +### Release 0.0.13 + +PyPNG now installs "out of the box" on Python 3. +Thanks to simon.sapin@kozea.fr and nathan@dunfield.info for the patch. + +The following issues have been fixed: + + Issue 63: setup.py does not use 2to3 + Issue 64: Typo in documentation + + +### Release 0.0.12 + +PyPNG now works on Python 3 if you use the `2to3` tool. +Fix for converting grey images to RGBA. + +The following issues have been fixed: + + Issue 60: Greyscale images not properly being converted to RGBA + Issue 61: Doesn't work on Python 3 + + +### Release 0.0.11 + +Added the "How Fast is PyPNG" section to the documentation. +Changed it so that more PNG formats return their rows as +Python `array.array` instances. + + +### Release 0.0.10 + +Fix for read_flat method (broken for ages). + +The following issues have been fixed: + + Issue 56: read_flat broken + + +### Release 0.0.9 + +Tentative fix for a deprecation warning on 64-bit Python 2.5 systems. +Conversion tool for Plan 9 images. + +Issue 54 (below) is tentative. +The PyPNG developers have been unable to reproduce the error +(as it seems to be on 64-bit Python 2.5 systems); +any user reports would be most welcome. + +The following issues have been fixed: + + Issue 54: Deprecation warnings when using pypng. + Issue 55: Cannot convert Plan 9 images. + + +### Release 0.0.8 + +Mostly more robust to dodgy input PNGs, +as a result of testing with `brokensuite`. +One fixed bug was a critical: +an infinite loop for a least one input (Issue 52 below). + +The following issues have been fixed: + + Issue 47: Leading blanks when using write_packed. + Issue 48: pipdither fails when input has no gamma chunk. + Issue 49: pipdither fail with 1-bit input. + Issue 50: pipchunk adds second gamma chunk. + Issue 51: piprgb and pipasgrey fail for color mapped images. + Issue 52: Some inputs cause infinite loop. + + +### Release 0.0.7 + +Better documentation (in `html/ex.html` mostly) for NumPy integration. + +The following issues have been fixed: + + Issue 46: Unclear how to get PNG pixel data into and out of NumPy. + + +### Release 0.0.6 + +NumPy integer types now work. + +The following issues have been fixed: + + Issue 44: Cannot use numpy.uint16 for pixel values. + + +### Release 0.0.5 + +`sBIT` chunks are now handled, +meaning that PyPNG can handle any (single) bit depth from 1 to 16 +from end to end. + +The following issues have been fixed: + + Issue 28: Does not add sBIT chunk. + Issue 36: Ignores sBIT chunk when present. + + +### Release 0.0.4 + +PyPNG now works on Python 2.2 +(significant for Symbian users as PyS60 is based on Python 2.2). +Not all features are supported on Python 2.2. + +The following issues have been fixed: + + Issue 16: Source and doc uses 'K' where it should use 'L'. + Issue 32: Does not accept packed data. + Issue 33: Cannot create greyscale PNG with transparency. + Issue 35: Does not work on Python 2.2. + + +### Release 0.0.3 + +Handling PAM files allows end to end handling of alpha channels in +workflows that involve both Netpbm formats and PNG. +PyPNG now works in Python 2.3. + +The following issues have been fixed: + + Issue 14: Does not read PAM files. + Issue 15: Does not write PAM files. + Issue 25: Incorrect handling of tRNS chunk. + Issue 26: asRGBA8 method crashes out for color type 2 images. + Issue 27: Fails on Python 2.3. + + +### Release 0.0.2 + +Lickable HTML documentation is now provided (see the html/ directory), +generated by Sphinx. + +The following issues have been fixed: + + Issue 8: Documentation is not lickable. + Issue 9: Advantage over PIL is not clear. + Issue 19: Bogus message for PNM inputs with unsupported maxval + Issue 20: Cannot write large PNG files + + +### Release 0.0.1 + +Stuff happened. + + +## MANIFEST + +- .../ - top-level crud (like this README, and setup.py). +- .../asset - assets (needed for testing) +- .../code/ - the Python code. +- .../man/ - manuals (in source/plain-text). +- .../proc/ - documented procedures (release procedure). + + +## REFERENCES + +- Python: www.python.org +- PNG: http://www.w3.org/TR/PNG/ + +## END diff --git a/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/RECORD new file mode 100644 index 00000000..3b558e7f --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/RECORD @@ -0,0 +1,20 @@ +../../../bin/prichunkpng,sha256=K5a8m9I7Ts6f_cHEBNDvhHDHdvBngRDlBfuPGggdzYQ,7638 +../../../bin/pricolpng,sha256=AcdHEHMinyiDeyR7w3y5uYUVdpV2F0ewaS60QNRCU0E,2040 +../../../bin/priditherpng,sha256=IDPUOeSY1iC6JBaP1p5pI2ognR91ePCQkC9JzdiODBw,7806 +../../../bin/priforgepng,sha256=fArK2ncPK7nS7kjBj2cByllI7HwutONm0dnmbbUor7A,5969 +../../../bin/prigreypng,sha256=iGsdPvqeayl9x4rkjAahlJOscH-T8ynQrXPxyb8zzKE,1913 +../../../bin/pripalpng,sha256=T2zFfCM6eRXyGmS9XOyrVHjnRKiakUyDCvlPBnkd3Sg,2604 +../../../bin/pripamtopng,sha256=hDffs43n6M98hDAh4S56ivjD_mASPS2COkWKkRD7nNU,10553 +../../../bin/priplan9topng,sha256=TcWctFOQi2aeBu4h5qKG_566ZsJGhFf8_XmxRyvb9Ug,16304 +../../../bin/pripnglsch,sha256=og-O4TCNVukVUO0_vbw0OznuB9PfnqcDnUBRae0snQA,728 +../../../bin/pripngtopam,sha256=AuBgC8VDfJAD9-bEMo2UXnvRYoHvzqQnO0ahrtRINhA,2744 +../../../bin/prirowpng,sha256=IRh5ENeOgkAGUAqqPL-hmeDdFD5ch_nFKPsVUQxhn4c,1963 +../../../bin/priweavepng,sha256=eQswyjlV5KfECY5gFVfHk6B2bnL2Cx9NkFMuZ4hp-iM,6661 +__pycache__/png.cpython-312.pyc,, +png.py,sha256=scYhEHiviSu-l1-UjUDhny6ewGSZT2NbbkMbXFXlaDA,82781 +pypng-0.20220715.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pypng-0.20220715.0.dist-info/LICENCE,sha256=jdEn2Hu5Aaucj1hOmLfHqoU52yf-Yv8Bse5L6TtqUTo,1038 +pypng-0.20220715.0.dist-info/METADATA,sha256=oxg6Joyc6tTit7iGa6qvywFefBP-E9FpFxrzwwAnkqw,13639 +pypng-0.20220715.0.dist-info/RECORD,, +pypng-0.20220715.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +pypng-0.20220715.0.dist-info/top_level.txt,sha256=M9g0SnE1xCqjh2cGuQj5W3Atg_9T4F5Kr_F8B79nqY4,4 diff --git a/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/WHEEL new file mode 100644 index 00000000..becc9a66 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/top_level.txt new file mode 100644 index 00000000..fd8a20b6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pypng-0.20220715.0.dist-info/top_level.txt @@ -0,0 +1 @@ +png diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/LICENSE b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/LICENSE new file mode 100644 index 00000000..bb4b0c70 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/LICENSE @@ -0,0 +1,48 @@ +Copyright (c) 2011, Lincoln Loop +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the package name nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +------------------------------------------------------------------------------- + + +Original text and license from the pyqrnative package where this was forked +from (http://code.google.com/p/pyqrnative): + +#Ported from the Javascript library by Sam Curren +# +#QRCode for Javascript +#http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/qrcode.js +# +#Copyright (c) 2009 Kazuhiko Arase +# +#URL: http://www.d-project.com/ +# +#Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# The word "QR Code" is registered trademark of +# DENSO WAVE INCORPORATED +# http://www.denso-wave.com/qrcode/faqpatent-e.html diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/METADATA new file mode 100644 index 00000000..0ae99660 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/METADATA @@ -0,0 +1,640 @@ +Metadata-Version: 2.1 +Name: qrcode +Version: 7.4.2 +Summary: QR Code image generator +Home-page: https://github.com/lincolnloop/python-qrcode +Author: Lincoln Loop +Author-email: info@lincolnloop.com +License: BSD +Keywords: qr denso-wave IEC18004 +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Multimedia :: Graphics +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.7 +License-File: LICENSE +Requires-Dist: typing-extensions +Requires-Dist: pypng +Requires-Dist: colorama ; platform_system == "Windows" +Provides-Extra: all +Requires-Dist: zest.releaser[recommended] ; extra == 'all' +Requires-Dist: tox ; extra == 'all' +Requires-Dist: pytest ; extra == 'all' +Requires-Dist: pytest-cov ; extra == 'all' +Requires-Dist: pillow (>=9.1.0) ; extra == 'all' +Provides-Extra: dev +Requires-Dist: tox ; extra == 'dev' +Requires-Dist: pytest ; extra == 'dev' +Requires-Dist: pytest-cov ; extra == 'dev' +Provides-Extra: maintainer +Requires-Dist: zest.releaser[recommended] ; extra == 'maintainer' +Provides-Extra: pil +Requires-Dist: pillow (>=9.1.0) ; extra == 'pil' +Provides-Extra: test +Requires-Dist: coverage ; extra == 'test' +Requires-Dist: pytest ; extra == 'test' + +============================= +Pure python QR Code generator +============================= + +Generate QR codes. + +A standard install uses pypng_ to generate PNG files and can also render QR +codes directly to the console. A standard install is just:: + + pip install qrcode + +For more image functionality, install qrcode with the ``pil`` dependency so +that pillow_ is installed and can be used for generating images:: + + pip install "qrcode[pil]" + +.. _pypng: https://pypi.python.org/pypi/pypng +.. _pillow: https://pypi.python.org/pypi/Pillow + + +What is a QR Code? +================== + +A Quick Response code is a two-dimensional pictographic code used for its fast +readability and comparatively large storage capacity. The code consists of +black modules arranged in a square pattern on a white background. The +information encoded can be made up of any kind of data (e.g., binary, +alphanumeric, or Kanji symbols) + +Usage +===== + +From the command line, use the installed ``qr`` script:: + + qr "Some text" > test.png + +Or in Python, use the ``make`` shortcut function: + +.. code:: python + + import qrcode + img = qrcode.make('Some data here') + type(img) # qrcode.image.pil.PilImage + img.save("some_file.png") + +Advanced Usage +-------------- + +For more control, use the ``QRCode`` class. For example: + +.. code:: python + + import qrcode + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data('Some data') + qr.make(fit=True) + + img = qr.make_image(fill_color="black", back_color="white") + +The ``version`` parameter is an integer from 1 to 40 that controls the size of +the QR Code (the smallest, version 1, is a 21x21 matrix). +Set to ``None`` and use the ``fit`` parameter when making the code to determine +this automatically. + +``fill_color`` and ``back_color`` can change the background and the painting +color of the QR, when using the default image factory. Both parameters accept +RGB color tuples. + +.. code:: python + + + img = qr.make_image(back_color=(255, 195, 235), fill_color=(55, 95, 35)) + +The ``error_correction`` parameter controls the error correction used for the +QR Code. The following four constants are made available on the ``qrcode`` +package: + +``ERROR_CORRECT_L`` + About 7% or less errors can be corrected. +``ERROR_CORRECT_M`` (default) + About 15% or less errors can be corrected. +``ERROR_CORRECT_Q`` + About 25% or less errors can be corrected. +``ERROR_CORRECT_H``. + About 30% or less errors can be corrected. + +The ``box_size`` parameter controls how many pixels each "box" of the QR code +is. + +The ``border`` parameter controls how many boxes thick the border should be +(the default is 4, which is the minimum according to the specs). + +Other image factories +===================== + +You can encode as SVG, or use a new pure Python image processor to encode to +PNG images. + +The Python examples below use the ``make`` shortcut. The same ``image_factory`` +keyword argument is a valid option for the ``QRCode`` class for more advanced +usage. + +SVG +--- + +You can create the entire SVG or an SVG fragment. When building an entire SVG +image, you can use the factory that combines as a path (recommended, and +default for the script) or a factory that creates a simple set of rectangles. + +From your command line:: + + qr --factory=svg-path "Some text" > test.svg + qr --factory=svg "Some text" > test.svg + qr --factory=svg-fragment "Some text" > test.svg + +Or in Python: + +.. code:: python + + import qrcode + import qrcode.image.svg + + if method == 'basic': + # Simple factory, just a set of rects. + factory = qrcode.image.svg.SvgImage + elif method == 'fragment': + # Fragment factory (also just a set of rects) + factory = qrcode.image.svg.SvgFragmentImage + else: + # Combined path factory, fixes white space that may occur when zooming + factory = qrcode.image.svg.SvgPathImage + + img = qrcode.make('Some data here', image_factory=factory) + +Two other related factories are available that work the same, but also fill the +background of the SVG with white:: + + qrcode.image.svg.SvgFillImage + qrcode.image.svg.SvgPathFillImage + +The ``QRCode.make_image()`` method forwards additional keyword arguments to the +underlying ElementTree XML library. This helps to fine tune the root element of +the resulting SVG: + +.. code:: python + + import qrcode + qr = qrcode.QRCode(image_factory=qrcode.image.svg.SvgPathImage) + qr.add_data('Some data') + qr.make(fit=True) + + img = qr.make_image(attrib={'class': 'some-css-class'}) + +You can convert the SVG image into strings using the ``to_string()`` method. +Additional keyword arguments are forwarded to ElementTrees ``tostring()``: + +.. code:: python + + img.to_string(encoding='unicode') + + +Pure Python PNG +--------------- + +If Pillow is not installed, the default image factory will be a pure Python PNG +encoder that uses `pypng`. + +You can use the factory explicitly from your command line:: + + qr --factory=png "Some text" > test.png + +Or in Python: + +.. code:: python + + import qrcode + from qrcode.image.pure import PyPNGImage + img = qrcode.make('Some data here', image_factory=PyPNGImage) + + +Styled Image +------------ + +Works only with versions_ >=7.2 (SVG styled images require 7.4). + +.. _versions: https://github.com/lincolnloop/python-qrcode/blob/master/CHANGES.rst#72-19-july-2021 + +To apply styles to the QRCode, use the ``StyledPilImage`` or one of the +standard SVG_ image factories. These accept an optional ``module_drawer`` +parameter to control the shape of the QR Code. + +These QR Codes are not guaranteed to work with all readers, so do some +experimentation and set the error correction to high (especially if embedding +an image). + +Other PIL module drawers: + + .. image:: doc/module_drawers.png + +For SVGs, use ``SvgSquareDrawer``, ``SvgCircleDrawer``, +``SvgPathSquareDrawer``, or ``SvgPathCircleDrawer``. + +These all accept a ``size_ratio`` argument which allows for "gapped" squares or +circles by reducing this less than the default of ``Decimal(1)``. + + +The ``StyledPilImage`` additionally accepts an optional ``color_mask`` +parameter to change the colors of the QR Code, and an optional +``embeded_image_path`` to embed an image in the center of the code. + +Other color masks: + + .. image:: doc/color_masks.png + +Here is a code example to draw a QR code with rounded corners, radial gradient +and an embedded image: + +.. code:: python + + import qrcode + from qrcode.image.styledpil import StyledPilImage + from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer + from qrcode.image.styles.colormasks import RadialGradiantColorMask + + qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L) + qr.add_data('Some data') + + img_1 = qr.make_image(image_factory=StyledPilImage, module_drawer=RoundedModuleDrawer()) + img_2 = qr.make_image(image_factory=StyledPilImage, color_mask=RadialGradiantColorMask()) + img_3 = qr.make_image(image_factory=StyledPilImage, embeded_image_path="/path/to/image.png") + +Examples +======== + +Get the text content from `print_ascii`: + +.. code:: python + + import io + import qrcode + qr = qrcode.QRCode() + qr.add_data("Some text") + f = io.StringIO() + qr.print_ascii(out=f) + f.seek(0) + print(f.read()) + +The `add_data` method will append data to the current QR object. To add new data by replacing previous content in the same object, first use clear method: + +.. code:: python + + import qrcode + qr = qrcode.QRCode() + qr.add_data('Some data') + img = qr.make_image() + qr.clear() + qr.add_data('New data') + other_img = qr.make_image() + +Pipe ascii output to text file in command line:: + + qr --ascii "Some data" > "test.txt" + cat test.txt + +Alternative to piping output to file to avoid PowerShell issues:: + + # qr "Some data" > test.png + qr --output=test.png "Some data" + +========== +Change log +========== + +7.4.2 (6 February 2023) +======================= + +- Allow ``pypng`` factory to allow for saving to a string (like + ``qr.save("some_file.png")``) in addition to file-like objects. + + +7.4.1 (3 February 2023) +======================= + +- Fix bad over-optimization in v7.4 that broke large QR codes. Thanks to + mattiasj-axis! + + +7.4 (1 February 2023) +===================== + +- Restructure the factory drawers, allowing different shapes in SVG image + factories as well. + +- Add a ``--factory-drawer`` option to the ``qr`` console script. + +- Optimize the output for the ``SVGPathImage`` factory (more than 30% reduction + in file sizes). + +- Add a ``pypng`` image factory as a pure Python PNG solution. If ``pillow`` is + *not* installed, then this becomes the default factory. + +- The ``pymaging`` image factory has been removed, but its factory shortcut and + the actual PymagingImage factory class now just link to the PyPNGImage + factory. + + +7.3.1 (1 October 2021) +====================== + +- Improvements for embedded image. + + +7.3 (19 August 2021) +==================== + +- Skip color mask if QR is black and white + + +7.2 (19 July 2021) +================== + +- Add Styled PIL image factory, allowing different color masks and shapes in QR codes + +- Small performance inprovement + +- Add check for border size parameter + + +7.1 (1 July 2021) +================= + +- Add --ascii parameter to command line interface allowing to output ascii when stdout is piped + +- Add --output parameter to command line interface to specify output file + +- Accept RGB tuples in fill_color and back_color + +- Add to_string method to SVG images + +- Replace inline styles with SVG attributes to avoid CSP issues + +- Add Python3.10 to supported versions + + +7.0 (29 June 2021) +================== + +- Drop Python < 3.6 support. + + +6.1 (14 January 2019) +===================== + +- Fix short chunks of data not being optimized to the correct mode. + +- Tests fixed for Python 3 + + +6.0 (23 March 2018) +=================== + +- Fix optimize length being ignored in ``QRCode.add_data``. + +- Better calculation of the best mask pattern and related optimizations. Big + thanks to cryptogun! + + +5.3 (18 May 2016) +================= + +* Fix incomplete block table for QR version 15. Thanks Rodrigo Queiro for the + report and Jacob Welsh for the investigation and fix. + +* Avoid unnecessary dependency for non MS platforms, thanks to Noah Vesely. + +* Make ``BaseImage.get_image()`` actually work. + + +5.2 (25 Jan 2016) +================= + +* Add ``--error-correction`` option to qr script. + +* Fix script piping to stdout in Python 3 and reading non-UTF-8 characters in + Python 3. + +* Fix script piping in Windows. + +* Add some useful behind-the-curtain methods for tinkerers. + +* Fix terminal output when using Python 2.6 + +* Fix terminal output to display correctly on MS command line. + +5.2.1 +----- + +* Small fix to terminal output in Python 3 (and fix tests) + +5.2.2 +----- + +* Revert some terminal changes from 5.2 that broke Python 3's real life tty + code generation and introduce a better way from Jacob Welsh. + + +5.1 (22 Oct 2014) +================= + +* Make ``qr`` script work in Windows. Thanks Ionel Cristian Mărieș + +* Fixed print_ascii function in Python 3. + +* Out-of-bounds code version numbers are handled more consistently with a + ValueError. + +* Much better test coverage (now only officially supporting Python 2.6+) + + +5.0 (17 Jun 2014) +================= + +* Speed optimizations. + +* Change the output when using the ``qr`` script to use ASCII rather than + just colors, better using the terminal real estate. + +* Fix a bug in passing bytecode data directly when in Python 3. + +* Substation speed optimizations to best-fit algorithm (thanks Jacob Welsh!). + +* Introduce a ``print_ascii`` method and use it as the default for the ``qr`` + script rather than ``print_tty``. + +5.0.1 +----- + +* Update version numbers correctly. + + +4.0 (4 Sep 2013) +================ + +* Made qrcode work on Python 2.4 - Thanks tcely. + Note: officially, qrcode only supports 2.5+. + +* Support pure-python PNG generation (via pymaging) for Python 2.6+ -- thanks + Adam Wisniewski! + +* SVG image generation now supports alternate sizing (the default box size of + 10 == 1mm per rectangle). + +* SVG path image generation allows cleaner SVG output by combining all QR rects + into a single path. Thank you, Viktor Stískala. + +* Added some extra simple SVG factories that fill the background white. + +4.0.1 +----- + +* Fix the pymaging backend not able to save the image to a buffer. Thanks ilj! + +4.0.2 +----- + +* Fix incorrect regex causing a comma to be considered part of the alphanumeric + set. + +* Switch to using setuptools for setup.py. + +4.0.3 +----- + +* Fix bad QR code generation due to the regex comma fix in version 4.0.2. + +4.0.4 +----- + +* Bad version number for previous hotfix release. + + +3.1 (12 Aug 2013) +================= + +* Important fixes for incorrect matches of the alphanumeric encoding mode. + Previously, the pattern would match if a single line was alphanumeric only + (even if others wern't). Also, the two characters ``{`` and ``}`` had snuck + in as valid characters. Thanks to Eran Tromer for the report and fix. + +* Optimized chunking -- if the parts of the data stream can be encoded more + efficiently, the data will be split into chunks of the most efficient modes. + +3.1.1 +----- + +* Update change log to contain version 3.1 changes. :P + +* Give the ``qr`` script an ``--optimize`` argument to control the chunk + optimization setting. + + +3.0 (25 Jun 2013) +================= + +* Python 3 support. + +* Add QRCode.get_matrix, an easy way to get the matrix array of a QR code + including the border. Thanks Hugh Rawlinson. + +* Add in a workaround so that Python 2.6 users can use SVG generation (they + must install ``lxml``). + +* Some initial tests! And tox support (``pip install tox``) for testing across + Python platforms. + + +2.7 (5 Mar 2013) +================ + +* Fix incorrect termination padding. + + +2.6 (2 Apr 2013) +================ + +* Fix the first four columns incorrectly shifted by one. Thanks to Josep + Gómez-Suay for the report and fix. + +* Fix strings within 4 bits of the QR version limit being incorrectly + terminated. Thanks to zhjie231 for the report. + + +2.5 (12 Mar 2013) +================= + +* The PilImage wrapper is more transparent - you can use any methods or + attributes available to the underlying PIL Image instance. + +* Fixed the first column of the QR Code coming up empty! Thanks to BecoKo. + +2.5.1 +----- + +* Fix installation error on Windows. + + +2.4 (23 Apr 2012) +================= + +* Use a pluggable backend system for generating images, thanks to Branko Čibej! + Comes with PIL and SVG backends built in. + +2.4.1 +----- + +* Fix a packaging issue + +2.4.2 +----- + +* Added a ``show`` method to the PIL image wrapper so the ``run_example`` + function actually works. + + +2.3 (29 Jan 2012) +================= + +* When adding data, auto-select the more efficient encoding methods for numbers + and alphanumeric data (KANJI still not supported). + +2.3.1 +----- + +* Encode unicode to utf-8 bytestrings when adding data to a QRCode. + + +2.2 (18 Jan 2012) +================= + +* Fixed tty output to work on both white and black backgrounds. + +* Added `border` parameter to allow customizing of the number of boxes used to + create the border of the QR code + + +2.1 (17 Jan 2012) +================= + +* Added a ``qr`` script which can be used to output a qr code to the tty using + background colors, or to a file via a pipe. diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/RECORD new file mode 100644 index 00000000..093c3992 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/RECORD @@ -0,0 +1,72 @@ +../../../bin/qr,sha256=-OrkWxN_dYqAQqKkMiMkMPmuhhkcNeOBM1lGSo-cFVk,234 +../../../share/man/man1/qr.1,sha256=1HjEKPDD7Y2hvYbwetA4Qs2ZOrF4kRKO6XUBnNL3YDE,1355 +qrcode-7.4.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +qrcode-7.4.2.dist-info/LICENSE,sha256=QN-5A8lO4_eJUAExMRGGVI7Lpc79NVdiPXcA4lIquZQ,2143 +qrcode-7.4.2.dist-info/METADATA,sha256=JsgthBQOLX0gB21FIZnpr2LwIMfE8KkwegPy19a_u8A,17096 +qrcode-7.4.2.dist-info/RECORD,, +qrcode-7.4.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +qrcode-7.4.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 +qrcode-7.4.2.dist-info/entry_points.txt,sha256=McEjM7MauRTGEVu8d0Ajy4F2Bex6qEawTYQsctfVbjk,51 +qrcode-7.4.2.dist-info/top_level.txt,sha256=lJ7l1nyDt4uWLvG9GVo-Zo-rlkBsRRkXe45ps_jmq3c,7 +qrcode/LUT.py,sha256=NjXKPfHSTFYoLlGkXhFjf2OUq_EGD6mrdyYHIG3dNck,3599 +qrcode/__init__.py,sha256=0C8jx3gDHSJ4yydlHN01ytyipNh2pMO3VYS9Dk-m4oU,645 +qrcode/__pycache__/LUT.cpython-312.pyc,, +qrcode/__pycache__/__init__.cpython-312.pyc,, +qrcode/__pycache__/base.cpython-312.pyc,, +qrcode/__pycache__/console_scripts.cpython-312.pyc,, +qrcode/__pycache__/constants.cpython-312.pyc,, +qrcode/__pycache__/exceptions.cpython-312.pyc,, +qrcode/__pycache__/main.cpython-312.pyc,, +qrcode/__pycache__/release.cpython-312.pyc,, +qrcode/__pycache__/util.cpython-312.pyc,, +qrcode/base.py,sha256=9J_1LynF5dXJK14Azs8XyHJY66FfTluYJ66F8ZjeStY,7288 +qrcode/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +qrcode/compat/__pycache__/__init__.cpython-312.pyc,, +qrcode/compat/__pycache__/etree.cpython-312.pyc,, +qrcode/compat/__pycache__/pil.cpython-312.pyc,, +qrcode/compat/etree.py,sha256=rEyWRA9QMsVFva_9rOdth3RAkRpFOmkF59c2EQM44gE,152 +qrcode/compat/pil.py,sha256=9fbuYrvq7AG4TURpBdi_dZ3_L_vqpFO7Qc0280vLIIY,362 +qrcode/console_scripts.py,sha256=W5ji79UtPxgVqngwztA3R17HylH_0D7Ve6y_WN5kZRA,5571 +qrcode/constants.py,sha256=0Csa8YYdeQ8NaFrRmt43maVg12O89d-oKgiKAVIO2s4,106 +qrcode/exceptions.py,sha256=L2fZuYOKscvdn72ra-wF8Gwsr2ZB9eRZWrp1f0IDx4E,45 +qrcode/image/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +qrcode/image/__pycache__/__init__.cpython-312.pyc,, +qrcode/image/__pycache__/base.cpython-312.pyc,, +qrcode/image/__pycache__/pil.cpython-312.pyc,, +qrcode/image/__pycache__/pure.cpython-312.pyc,, +qrcode/image/__pycache__/styledpil.cpython-312.pyc,, +qrcode/image/__pycache__/svg.cpython-312.pyc,, +qrcode/image/base.py,sha256=1xMhPfb8317m2Ysbl2p2rVtBwcx6bqmQEVDzkR55M9M,4984 +qrcode/image/pil.py,sha256=YahBtPLT_7EUFSEP3poWTtAlEZIB7zNU5JNtlWLQjYU,1524 +qrcode/image/pure.py,sha256=lpMH2i0hTzVsywEFkjifC_dBiICN3rjDEeeSKBePcBA,1412 +qrcode/image/styledpil.py,sha256=2qYcdZaPp0_x0byj2FDcZSzgZiQyKlJKfEwkiOXLB-w,4477 +qrcode/image/styles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +qrcode/image/styles/__pycache__/__init__.cpython-312.pyc,, +qrcode/image/styles/__pycache__/colormasks.cpython-312.pyc,, +qrcode/image/styles/colormasks.py,sha256=jb-uydg25fkKhiAO8xoTaAP8Q-Vz3Y9nnSliUKD0oik,7601 +qrcode/image/styles/moduledrawers/__init__.py,sha256=Mklw5SjYiGbs2Aym38jwwrKt0plJGzwIVgZ--jiOVBc,430 +qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc,, +qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc,, +qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc,, +qrcode/image/styles/moduledrawers/__pycache__/svg.cpython-312.pyc,, +qrcode/image/styles/moduledrawers/base.py,sha256=WL3uefhVeLIKyCVpJpKTkv_ihS9SJt-DCnnQwizasac,1067 +qrcode/image/styles/moduledrawers/pil.py,sha256=myZ-dmDiEhE7_KB9t1bbHFeDHs4-B6Vu8diY1xjX4zQ,9852 +qrcode/image/styles/moduledrawers/svg.py,sha256=_ZOb60IVmyT3_q6uORwgJo3H8-MgSdDsJ1Q01ZzMi4U,3952 +qrcode/image/svg.py,sha256=dNWQfIQ-_t07d4dPYWfd0ghg4KL9K8f0ho7zWn95SlQ,5246 +qrcode/main.py,sha256=jzPEBFIpr9EHV-v7jOh3NW1y53GJ8bbYyV7HhHK4L3w,16462 +qrcode/release.py,sha256=p5oZkhKDcc9fYxXpadhmByLYYhGFUoTyh_f1KzBLW5U,1079 +qrcode/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +qrcode/tests/__pycache__/__init__.cpython-312.pyc,, +qrcode/tests/__pycache__/test_example.cpython-312.pyc,, +qrcode/tests/__pycache__/test_qrcode.cpython-312.pyc,, +qrcode/tests/__pycache__/test_qrcode_svg.cpython-312.pyc,, +qrcode/tests/__pycache__/test_release.cpython-312.pyc,, +qrcode/tests/__pycache__/test_script.cpython-312.pyc,, +qrcode/tests/__pycache__/test_util.cpython-312.pyc,, +qrcode/tests/test_example.py,sha256=Z3rYh8MZaJT08YgJNEN1pEq5ezqNP9akpKnNlQY4His,333 +qrcode/tests/test_qrcode.py,sha256=SZZ461_IrIrEG5x30gRYlk2dTsSe-cxaaGKabVuKX_E,17398 +qrcode/tests/test_qrcode_svg.py,sha256=42-BoSUdIwU2XfxiZSS5jXDHrkscn9Cm_bTvLSPbCWE,1644 +qrcode/tests/test_release.py,sha256=lthfxR_oaSrMpx5rwD474i3_5aYuXW5vrtK8x0pYXSo,1468 +qrcode/tests/test_script.py,sha256=597ta1l-dFNuRp8FUhQ7ZovidxgG5uUNbMsmKXEBF-E,3807 +qrcode/tests/test_util.py,sha256=ZMapsEzOYMthbIx7DwZZGGPW2Vr-X-gLKWH2KArTnXo,277 +qrcode/util.py,sha256=dMBLr8VCsyY5sDlJg0MpggyO9wU-BodoIExQHiVaDlY,17128 diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/WHEEL new file mode 100644 index 00000000..57e3d840 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/entry_points.txt b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/entry_points.txt new file mode 100644 index 00000000..e45af900 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +qr = qrcode.console_scripts:main diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/top_level.txt new file mode 100644 index 00000000..15aaeb33 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode-7.4.2.dist-info/top_level.txt @@ -0,0 +1 @@ +qrcode diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/LUT.py b/Backend/venv/lib/python3.12/site-packages/qrcode/LUT.py new file mode 100644 index 00000000..115892f1 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/LUT.py @@ -0,0 +1,223 @@ +# Store all kinds of lookup table. + + +# # generate rsPoly lookup table. + +# from qrcode import base + +# def create_bytes(rs_blocks): +# for r in range(len(rs_blocks)): +# dcCount = rs_blocks[r].data_count +# ecCount = rs_blocks[r].total_count - dcCount +# rsPoly = base.Polynomial([1], 0) +# for i in range(ecCount): +# rsPoly = rsPoly * base.Polynomial([1, base.gexp(i)], 0) +# return ecCount, rsPoly + +# rsPoly_LUT = {} +# for version in range(1,41): +# for error_correction in range(4): +# rs_blocks_list = base.rs_blocks(version, error_correction) +# ecCount, rsPoly = create_bytes(rs_blocks_list) +# rsPoly_LUT[ecCount]=rsPoly.num +# print(rsPoly_LUT) + +# Result. Usage: input: ecCount, output: Polynomial.num +# e.g. rsPoly = base.Polynomial(LUT.rsPoly_LUT[ecCount], 0) +rsPoly_LUT = { + 7: [1, 127, 122, 154, 164, 11, 68, 117], + 10: [1, 216, 194, 159, 111, 199, 94, 95, 113, 157, 193], + 13: [1, 137, 73, 227, 17, 177, 17, 52, 13, 46, 43, 83, 132, 120], + 15: [1, 29, 196, 111, 163, 112, 74, 10, 105, 105, 139, 132, 151, 32, 134, 26], + 16: [1, 59, 13, 104, 189, 68, 209, 30, 8, 163, 65, 41, 229, 98, 50, 36, 59], + 17: [1, 119, 66, 83, 120, 119, 22, 197, 83, 249, 41, 143, 134, 85, 53, 125, 99, 79], + 18: [ + 1, + 239, + 251, + 183, + 113, + 149, + 175, + 199, + 215, + 240, + 220, + 73, + 82, + 173, + 75, + 32, + 67, + 217, + 146, + ], + 20: [ + 1, + 152, + 185, + 240, + 5, + 111, + 99, + 6, + 220, + 112, + 150, + 69, + 36, + 187, + 22, + 228, + 198, + 121, + 121, + 165, + 174, + ], + 22: [ + 1, + 89, + 179, + 131, + 176, + 182, + 244, + 19, + 189, + 69, + 40, + 28, + 137, + 29, + 123, + 67, + 253, + 86, + 218, + 230, + 26, + 145, + 245, + ], + 24: [ + 1, + 122, + 118, + 169, + 70, + 178, + 237, + 216, + 102, + 115, + 150, + 229, + 73, + 130, + 72, + 61, + 43, + 206, + 1, + 237, + 247, + 127, + 217, + 144, + 117, + ], + 26: [ + 1, + 246, + 51, + 183, + 4, + 136, + 98, + 199, + 152, + 77, + 56, + 206, + 24, + 145, + 40, + 209, + 117, + 233, + 42, + 135, + 68, + 70, + 144, + 146, + 77, + 43, + 94, + ], + 28: [ + 1, + 252, + 9, + 28, + 13, + 18, + 251, + 208, + 150, + 103, + 174, + 100, + 41, + 167, + 12, + 247, + 56, + 117, + 119, + 233, + 127, + 181, + 100, + 121, + 147, + 176, + 74, + 58, + 197, + ], + 30: [ + 1, + 212, + 246, + 77, + 73, + 195, + 192, + 75, + 98, + 5, + 70, + 103, + 177, + 22, + 217, + 138, + 51, + 181, + 246, + 72, + 25, + 18, + 46, + 228, + 74, + 216, + 195, + 11, + 106, + 130, + 150, + ], +} diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__init__.py b/Backend/venv/lib/python3.12/site-packages/qrcode/__init__.py new file mode 100644 index 00000000..6b238d33 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/__init__.py @@ -0,0 +1,30 @@ +from qrcode.main import QRCode +from qrcode.main import make # noqa +from qrcode.constants import ( # noqa + ERROR_CORRECT_L, + ERROR_CORRECT_M, + ERROR_CORRECT_Q, + ERROR_CORRECT_H, +) + +from qrcode import image # noqa + + +def run_example(data="http://www.lincolnloop.com", *args, **kwargs): + """ + Build an example QR Code and display it. + + There's an even easier way than the code here though: just use the ``make`` + shortcut. + """ + qr = QRCode(*args, **kwargs) + qr.add_data(data) + + im = qr.make_image() + im.show() + + +if __name__ == "__main__": # pragma: no cover + import sys + + run_example(*sys.argv[1:]) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc new file mode 100644 index 00000000..38ed49ee Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..75750b91 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc new file mode 100644 index 00000000..d0525638 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/console_scripts.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/console_scripts.cpython-312.pyc new file mode 100644 index 00000000..da7aee5c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/console_scripts.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc new file mode 100644 index 00000000..0b8ca410 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 00000000..560797d2 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc new file mode 100644 index 00000000..1e398422 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/release.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/release.cpython-312.pyc new file mode 100644 index 00000000..cd005230 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/release.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc new file mode 100644 index 00000000..0f2446f0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/base.py b/Backend/venv/lib/python3.12/site-packages/qrcode/base.py new file mode 100644 index 00000000..20f81f6f --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/base.py @@ -0,0 +1,313 @@ +from typing import NamedTuple +from qrcode import constants + +EXP_TABLE = list(range(256)) + +LOG_TABLE = list(range(256)) + +for i in range(8): + EXP_TABLE[i] = 1 << i + +for i in range(8, 256): + EXP_TABLE[i] = ( + EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8] + ) + +for i in range(255): + LOG_TABLE[EXP_TABLE[i]] = i + +RS_BLOCK_OFFSET = { + constants.ERROR_CORRECT_L: 0, + constants.ERROR_CORRECT_M: 1, + constants.ERROR_CORRECT_Q: 2, + constants.ERROR_CORRECT_H: 3, +} + +RS_BLOCK_TABLE = ( + # L + # M + # Q + # H + # 1 + (1, 26, 19), + (1, 26, 16), + (1, 26, 13), + (1, 26, 9), + # 2 + (1, 44, 34), + (1, 44, 28), + (1, 44, 22), + (1, 44, 16), + # 3 + (1, 70, 55), + (1, 70, 44), + (2, 35, 17), + (2, 35, 13), + # 4 + (1, 100, 80), + (2, 50, 32), + (2, 50, 24), + (4, 25, 9), + # 5 + (1, 134, 108), + (2, 67, 43), + (2, 33, 15, 2, 34, 16), + (2, 33, 11, 2, 34, 12), + # 6 + (2, 86, 68), + (4, 43, 27), + (4, 43, 19), + (4, 43, 15), + # 7 + (2, 98, 78), + (4, 49, 31), + (2, 32, 14, 4, 33, 15), + (4, 39, 13, 1, 40, 14), + # 8 + (2, 121, 97), + (2, 60, 38, 2, 61, 39), + (4, 40, 18, 2, 41, 19), + (4, 40, 14, 2, 41, 15), + # 9 + (2, 146, 116), + (3, 58, 36, 2, 59, 37), + (4, 36, 16, 4, 37, 17), + (4, 36, 12, 4, 37, 13), + # 10 + (2, 86, 68, 2, 87, 69), + (4, 69, 43, 1, 70, 44), + (6, 43, 19, 2, 44, 20), + (6, 43, 15, 2, 44, 16), + # 11 + (4, 101, 81), + (1, 80, 50, 4, 81, 51), + (4, 50, 22, 4, 51, 23), + (3, 36, 12, 8, 37, 13), + # 12 + (2, 116, 92, 2, 117, 93), + (6, 58, 36, 2, 59, 37), + (4, 46, 20, 6, 47, 21), + (7, 42, 14, 4, 43, 15), + # 13 + (4, 133, 107), + (8, 59, 37, 1, 60, 38), + (8, 44, 20, 4, 45, 21), + (12, 33, 11, 4, 34, 12), + # 14 + (3, 145, 115, 1, 146, 116), + (4, 64, 40, 5, 65, 41), + (11, 36, 16, 5, 37, 17), + (11, 36, 12, 5, 37, 13), + # 15 + (5, 109, 87, 1, 110, 88), + (5, 65, 41, 5, 66, 42), + (5, 54, 24, 7, 55, 25), + (11, 36, 12, 7, 37, 13), + # 16 + (5, 122, 98, 1, 123, 99), + (7, 73, 45, 3, 74, 46), + (15, 43, 19, 2, 44, 20), + (3, 45, 15, 13, 46, 16), + # 17 + (1, 135, 107, 5, 136, 108), + (10, 74, 46, 1, 75, 47), + (1, 50, 22, 15, 51, 23), + (2, 42, 14, 17, 43, 15), + # 18 + (5, 150, 120, 1, 151, 121), + (9, 69, 43, 4, 70, 44), + (17, 50, 22, 1, 51, 23), + (2, 42, 14, 19, 43, 15), + # 19 + (3, 141, 113, 4, 142, 114), + (3, 70, 44, 11, 71, 45), + (17, 47, 21, 4, 48, 22), + (9, 39, 13, 16, 40, 14), + # 20 + (3, 135, 107, 5, 136, 108), + (3, 67, 41, 13, 68, 42), + (15, 54, 24, 5, 55, 25), + (15, 43, 15, 10, 44, 16), + # 21 + (4, 144, 116, 4, 145, 117), + (17, 68, 42), + (17, 50, 22, 6, 51, 23), + (19, 46, 16, 6, 47, 17), + # 22 + (2, 139, 111, 7, 140, 112), + (17, 74, 46), + (7, 54, 24, 16, 55, 25), + (34, 37, 13), + # 23 + (4, 151, 121, 5, 152, 122), + (4, 75, 47, 14, 76, 48), + (11, 54, 24, 14, 55, 25), + (16, 45, 15, 14, 46, 16), + # 24 + (6, 147, 117, 4, 148, 118), + (6, 73, 45, 14, 74, 46), + (11, 54, 24, 16, 55, 25), + (30, 46, 16, 2, 47, 17), + # 25 + (8, 132, 106, 4, 133, 107), + (8, 75, 47, 13, 76, 48), + (7, 54, 24, 22, 55, 25), + (22, 45, 15, 13, 46, 16), + # 26 + (10, 142, 114, 2, 143, 115), + (19, 74, 46, 4, 75, 47), + (28, 50, 22, 6, 51, 23), + (33, 46, 16, 4, 47, 17), + # 27 + (8, 152, 122, 4, 153, 123), + (22, 73, 45, 3, 74, 46), + (8, 53, 23, 26, 54, 24), + (12, 45, 15, 28, 46, 16), + # 28 + (3, 147, 117, 10, 148, 118), + (3, 73, 45, 23, 74, 46), + (4, 54, 24, 31, 55, 25), + (11, 45, 15, 31, 46, 16), + # 29 + (7, 146, 116, 7, 147, 117), + (21, 73, 45, 7, 74, 46), + (1, 53, 23, 37, 54, 24), + (19, 45, 15, 26, 46, 16), + # 30 + (5, 145, 115, 10, 146, 116), + (19, 75, 47, 10, 76, 48), + (15, 54, 24, 25, 55, 25), + (23, 45, 15, 25, 46, 16), + # 31 + (13, 145, 115, 3, 146, 116), + (2, 74, 46, 29, 75, 47), + (42, 54, 24, 1, 55, 25), + (23, 45, 15, 28, 46, 16), + # 32 + (17, 145, 115), + (10, 74, 46, 23, 75, 47), + (10, 54, 24, 35, 55, 25), + (19, 45, 15, 35, 46, 16), + # 33 + (17, 145, 115, 1, 146, 116), + (14, 74, 46, 21, 75, 47), + (29, 54, 24, 19, 55, 25), + (11, 45, 15, 46, 46, 16), + # 34 + (13, 145, 115, 6, 146, 116), + (14, 74, 46, 23, 75, 47), + (44, 54, 24, 7, 55, 25), + (59, 46, 16, 1, 47, 17), + # 35 + (12, 151, 121, 7, 152, 122), + (12, 75, 47, 26, 76, 48), + (39, 54, 24, 14, 55, 25), + (22, 45, 15, 41, 46, 16), + # 36 + (6, 151, 121, 14, 152, 122), + (6, 75, 47, 34, 76, 48), + (46, 54, 24, 10, 55, 25), + (2, 45, 15, 64, 46, 16), + # 37 + (17, 152, 122, 4, 153, 123), + (29, 74, 46, 14, 75, 47), + (49, 54, 24, 10, 55, 25), + (24, 45, 15, 46, 46, 16), + # 38 + (4, 152, 122, 18, 153, 123), + (13, 74, 46, 32, 75, 47), + (48, 54, 24, 14, 55, 25), + (42, 45, 15, 32, 46, 16), + # 39 + (20, 147, 117, 4, 148, 118), + (40, 75, 47, 7, 76, 48), + (43, 54, 24, 22, 55, 25), + (10, 45, 15, 67, 46, 16), + # 40 + (19, 148, 118, 6, 149, 119), + (18, 75, 47, 31, 76, 48), + (34, 54, 24, 34, 55, 25), + (20, 45, 15, 61, 46, 16), +) + + +def glog(n): + if n < 1: # pragma: no cover + raise ValueError(f"glog({n})") + return LOG_TABLE[n] + + +def gexp(n): + return EXP_TABLE[n % 255] + + +class Polynomial: + def __init__(self, num, shift): + if not num: # pragma: no cover + raise Exception(f"{len(num)}/{shift}") + + offset = 0 + for offset in range(len(num)): + if num[offset] != 0: + break + + self.num = num[offset:] + [0] * shift + + def __getitem__(self, index): + return self.num[index] + + def __iter__(self): + return iter(self.num) + + def __len__(self): + return len(self.num) + + def __mul__(self, other): + num = [0] * (len(self) + len(other) - 1) + + for i, item in enumerate(self): + for j, other_item in enumerate(other): + num[i + j] ^= gexp(glog(item) + glog(other_item)) + + return Polynomial(num, 0) + + def __mod__(self, other): + difference = len(self) - len(other) + if difference < 0: + return self + + ratio = glog(self[0]) - glog(other[0]) + + num = [ + item ^ gexp(glog(other_item) + ratio) + for item, other_item in zip(self, other) + ] + if difference: + num.extend(self[-difference:]) + + # recursive call + return Polynomial(num, 0) % other + + +class RSBlock(NamedTuple): + total_count: int + data_count: int + + +def rs_blocks(version, error_correction): + if error_correction not in RS_BLOCK_OFFSET: # pragma: no cover + raise Exception( + "bad rs block @ version: %s / error_correction: %s" + % (version, error_correction) + ) + offset = RS_BLOCK_OFFSET[error_correction] + rs_block = RS_BLOCK_TABLE[(version - 1) * 4 + offset] + + blocks = [] + + for i in range(0, len(rs_block), 3): + count, total_count, data_count = rs_block[i : i + 3] + for _ in range(count): + blocks.append(RSBlock(total_count, data_count)) + + return blocks diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__init__.py b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..3e86aa1e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/etree.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/etree.cpython-312.pyc new file mode 100644 index 00000000..de88132e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/etree.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc new file mode 100644 index 00000000..53fcfdad Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/etree.py b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/etree.py new file mode 100644 index 00000000..6739d227 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/etree.py @@ -0,0 +1,4 @@ +try: + import lxml.etree as ET # type: ignore # noqa: F401 +except ImportError: + import xml.etree.ElementTree as ET # type: ignore # noqa: F401 diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/pil.py b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/pil.py new file mode 100644 index 00000000..de6c6cb7 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/pil.py @@ -0,0 +1,12 @@ +# Try to import PIL in either of the two ways it can be installed. +Image = None +ImageDraw = None + +try: + from PIL import Image, ImageDraw # type: ignore # noqa: F401 +except ImportError: # pragma: no cover + try: + import Image # type: ignore # noqa: F401 + import ImageDraw # type: ignore # noqa: F401 + except ImportError: + pass diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/console_scripts.py b/Backend/venv/lib/python3.12/site-packages/qrcode/console_scripts.py new file mode 100644 index 00000000..424fe6fd --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/console_scripts.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +""" +qr - Convert stdin (or the first argument) to a QR Code. + +When stdout is a tty the QR Code is printed to the terminal and when stdout is +a pipe to a file an image is written. The default image format is PNG. +""" +import optparse +import os +import sys +from typing import Dict, Iterable, NoReturn, Optional, Set, Type + +import qrcode +from qrcode.image.base import BaseImage, DrawerAliases + +# The next block is added to get the terminal to display properly on MS platforms +if sys.platform.startswith(("win", "cygwin")): # pragma: no cover + import colorama # type: ignore + + colorama.init() + +default_factories = { + "pil": "qrcode.image.pil.PilImage", + "png": "qrcode.image.pure.PyPNGImage", + "svg": "qrcode.image.svg.SvgImage", + "svg-fragment": "qrcode.image.svg.SvgFragmentImage", + "svg-path": "qrcode.image.svg.SvgPathImage", + # Keeping for backwards compatibility: + "pymaging": "qrcode.image.pure.PymagingImage", +} + +error_correction = { + "L": qrcode.ERROR_CORRECT_L, + "M": qrcode.ERROR_CORRECT_M, + "Q": qrcode.ERROR_CORRECT_Q, + "H": qrcode.ERROR_CORRECT_H, +} + + +def main(args=None): + if args is None: + args = sys.argv[1:] + from pkg_resources import get_distribution + + version = get_distribution("qrcode").version + parser = optparse.OptionParser(usage=(__doc__ or "").strip(), version=version) + + # Wrap parser.error in a typed NoReturn method for better typing. + def raise_error(msg: str) -> NoReturn: + parser.error(msg) + raise # pragma: no cover + + parser.add_option( + "--factory", + help="Full python path to the image factory class to " + "create the image with. You can use the following shortcuts to the " + f"built-in image factory classes: {commas(default_factories)}.", + ) + parser.add_option( + "--factory-drawer", + help=f"Use an alternate drawer. {get_drawer_help()}.", + ) + parser.add_option( + "--optimize", + type=int, + help="Optimize the data by looking for chunks " + "of at least this many characters that could use a more efficient " + "encoding method. Use 0 to turn off chunk optimization.", + ) + parser.add_option( + "--error-correction", + type="choice", + choices=sorted(error_correction.keys()), + default="M", + help="The error correction level to use. Choices are L (7%), " + "M (15%, default), Q (25%), and H (30%).", + ) + parser.add_option( + "--ascii", help="Print as ascii even if stdout is piped.", action="store_true" + ) + parser.add_option( + "--output", + help="The output file. If not specified, the image is sent to " + "the standard output.", + ) + + opts, args = parser.parse_args(args) + + if opts.factory: + module = default_factories.get(opts.factory, opts.factory) + try: + image_factory = get_factory(module) + except ValueError as e: + raise_error(str(e)) + else: + image_factory = None + + qr = qrcode.QRCode( + error_correction=error_correction[opts.error_correction], + image_factory=image_factory, + ) + + if args: + data = args[0] + data = data.encode(errors="surrogateescape") + else: + data = sys.stdin.buffer.read() + if opts.optimize is None: + qr.add_data(data) + else: + qr.add_data(data, optimize=opts.optimize) + + if opts.output: + img = qr.make_image() + with open(opts.output, "wb") as out: + img.save(out) + else: + if image_factory is None and (os.isatty(sys.stdout.fileno()) or opts.ascii): + qr.print_ascii(tty=not opts.ascii) + return + + kwargs = {} + aliases: Optional[DrawerAliases] = getattr( + qr.image_factory, "drawer_aliases", None + ) + if opts.factory_drawer: + if not aliases: + raise_error("The selected factory has no drawer aliases.") + if opts.factory_drawer not in aliases: + raise_error( + f"{opts.factory_drawer} factory drawer not found." + f" Expected {commas(aliases)}" + ) + drawer_cls, drawer_kwargs = aliases[opts.factory_drawer] + kwargs["module_drawer"] = drawer_cls(**drawer_kwargs) + img = qr.make_image(**kwargs) + + sys.stdout.flush() + img.save(sys.stdout.buffer) + + +def get_factory(module: str) -> Type[BaseImage]: + if "." not in module: + raise ValueError("The image factory is not a full python path") + module, name = module.rsplit(".", 1) + imp = __import__(module, {}, {}, [name]) + return getattr(imp, name) + + +def get_drawer_help() -> str: + help: Dict[str, Set] = {} + for alias, module in default_factories.items(): + try: + image = get_factory(module) + except ImportError: # pragma: no cover + continue + aliases: Optional[DrawerAliases] = getattr(image, "drawer_aliases", None) + if not aliases: + continue + factories = help.setdefault(commas(aliases), set()) + factories.add(alias) + + return ". ".join( + f"For {commas(factories, 'and')}, use: {aliases}" + for aliases, factories in help.items() + ) + + +def commas(items: Iterable[str], joiner="or") -> str: + items = tuple(items) + if not items: + return "" + if len(items) == 1: + return items[0] + return f"{', '.join(items[:-1])} {joiner} {items[-1]}" + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/constants.py b/Backend/venv/lib/python3.12/site-packages/qrcode/constants.py new file mode 100644 index 00000000..385dda08 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/constants.py @@ -0,0 +1,5 @@ +# QR error correct levels +ERROR_CORRECT_L = 1 +ERROR_CORRECT_M = 0 +ERROR_CORRECT_Q = 3 +ERROR_CORRECT_H = 2 diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/exceptions.py b/Backend/venv/lib/python3.12/site-packages/qrcode/exceptions.py new file mode 100644 index 00000000..b37bd30c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/exceptions.py @@ -0,0 +1,2 @@ +class DataOverflowError(Exception): + pass diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__init__.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e664d8e8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc new file mode 100644 index 00000000..32b4295b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pil.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pil.cpython-312.pyc new file mode 100644 index 00000000..ce3c07ea Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pil.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc new file mode 100644 index 00000000..9c28329d Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/styledpil.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/styledpil.cpython-312.pyc new file mode 100644 index 00000000..be59ab15 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/styledpil.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/svg.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/svg.cpython-312.pyc new file mode 100644 index 00000000..a55509bf Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/svg.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/base.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/base.py new file mode 100644 index 00000000..4e8468b2 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/base.py @@ -0,0 +1,164 @@ +import abc +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type, Union + +from qrcode.image.styles.moduledrawers.base import QRModuleDrawer + +if TYPE_CHECKING: + from qrcode.main import ActiveWithNeighbors, QRCode + + +DrawerAliases = Dict[str, Tuple[Type[QRModuleDrawer], Dict[str, Any]]] + + +class BaseImage: + """ + Base QRCode image output class. + """ + + kind: Optional[str] = None + allowed_kinds: Optional[Tuple[str]] = None + needs_context = False + needs_processing = False + needs_drawrect = True + + def __init__(self, border, width, box_size, *args, **kwargs): + self.border = border + self.width = width + self.box_size = box_size + self.pixel_size = (self.width + self.border * 2) * self.box_size + self.modules = kwargs.pop("qrcode_modules") + self._img = self.new_image(**kwargs) + self.init_new_image() + + @abc.abstractmethod + def drawrect(self, row, col): + """ + Draw a single rectangle of the QR code. + """ + + def drawrect_context(self, row: int, col: int, qr: "QRCode"): + """ + Draw a single rectangle of the QR code given the surrounding context + """ + raise NotImplementedError("BaseImage.drawrect_context") # pragma: no cover + + def process(self): + """ + Processes QR code after completion + """ + raise NotImplementedError("BaseImage.drawimage") # pragma: no cover + + @abc.abstractmethod + def save(self, stream, kind=None): + """ + Save the image file. + """ + + def pixel_box(self, row, col): + """ + A helper method for pixel-based image generators that specifies the + four pixel coordinates for a single rect. + """ + x = (col + self.border) * self.box_size + y = (row + self.border) * self.box_size + return ( + (x, y), + (x + self.box_size - 1, y + self.box_size - 1), + ) + + @abc.abstractmethod + def new_image(self, **kwargs) -> Any: + """ + Build the image class. Subclasses should return the class created. + """ + + def init_new_image(self): + pass + + def get_image(self, **kwargs): + """ + Return the image class for further processing. + """ + return self._img + + def check_kind(self, kind, transform=None): + """ + Get the image type. + """ + if kind is None: + kind = self.kind + allowed = not self.allowed_kinds or kind in self.allowed_kinds + if transform: + kind = transform(kind) + if not allowed: + allowed = kind in self.allowed_kinds + if not allowed: + raise ValueError(f"Cannot set {type(self).__name__} type to {kind}") + return kind + + def is_eye(self, row: int, col: int): + """ + Find whether the referenced module is in an eye. + """ + return ( + (row < 7 and col < 7) + or (row < 7 and self.width - col < 8) + or (self.width - row < 8 and col < 7) + ) + + +class BaseImageWithDrawer(BaseImage): + default_drawer_class: Type[QRModuleDrawer] + drawer_aliases: DrawerAliases = {} + + def get_default_module_drawer(self) -> QRModuleDrawer: + return self.default_drawer_class() + + def get_default_eye_drawer(self) -> QRModuleDrawer: + return self.default_drawer_class() + + needs_context = True + + module_drawer: "QRModuleDrawer" + eye_drawer: "QRModuleDrawer" + + def __init__( + self, + *args, + module_drawer: Union[QRModuleDrawer, str, None] = None, + eye_drawer: Union[QRModuleDrawer, str, None] = None, + **kwargs, + ): + self.module_drawer = ( + self.get_drawer(module_drawer) or self.get_default_module_drawer() + ) + # The eye drawer can be overridden by another module drawer as well, + # but you have to be more careful with these in order to make the QR + # code still parseable + self.eye_drawer = self.get_drawer(eye_drawer) or self.get_default_eye_drawer() + super().__init__(*args, **kwargs) + + def get_drawer( + self, drawer: Union[QRModuleDrawer, str, None] + ) -> Optional[QRModuleDrawer]: + if not isinstance(drawer, str): + return drawer + drawer_cls, kwargs = self.drawer_aliases[drawer] + return drawer_cls(**kwargs) + + def init_new_image(self): + self.module_drawer.initialize(img=self) + self.eye_drawer.initialize(img=self) + + return super().init_new_image() + + def drawrect_context(self, row: int, col: int, qr: "QRCode"): + box = self.pixel_box(row, col) + drawer = self.eye_drawer if self.is_eye(row, col) else self.module_drawer + is_active: Union[bool, ActiveWithNeighbors] = ( + qr.active_with_neighbors(row, col) + if drawer.needs_neighbors + else bool(qr.modules[row][col]) + ) + + drawer.drawrect(box, is_active) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/pil.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/pil.py new file mode 100644 index 00000000..5767148d --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/pil.py @@ -0,0 +1,54 @@ +import qrcode.image.base +from qrcode.compat.pil import Image, ImageDraw + + +class PilImage(qrcode.image.base.BaseImage): + """ + PIL image builder, default format is PNG. + """ + + kind = "PNG" + + def new_image(self, **kwargs): + back_color = kwargs.get("back_color", "white") + fill_color = kwargs.get("fill_color", "black") + + try: + fill_color = fill_color.lower() + except AttributeError: + pass + + try: + back_color = back_color.lower() + except AttributeError: + pass + + # L mode (1 mode) color = (r*299 + g*587 + b*114)//1000 + if fill_color == "black" and back_color == "white": + mode = "1" + fill_color = 0 + if back_color == "white": + back_color = 255 + elif back_color == "transparent": + mode = "RGBA" + back_color = None + else: + mode = "RGB" + + img = Image.new(mode, (self.pixel_size, self.pixel_size), back_color) + self.fill_color = fill_color + self._idr = ImageDraw.Draw(img) + return img + + def drawrect(self, row, col): + box = self.pixel_box(row, col) + self._idr.rectangle(box, fill=self.fill_color) + + def save(self, stream, format=None, **kwargs): + kind = kwargs.pop("kind", self.kind) + if format is None: + format = kind + self._img.save(stream, format=format, **kwargs) + + def __getattr__(self, name): + return getattr(self._img, name) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/pure.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/pure.py new file mode 100644 index 00000000..690ebe0c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/pure.py @@ -0,0 +1,54 @@ +from itertools import chain + +import png + +import qrcode.image.base + + +class PyPNGImage(qrcode.image.base.BaseImage): + """ + pyPNG image builder. + """ + + kind = "PNG" + allowed_kinds = ("PNG",) + needs_drawrect = False + + def new_image(self, **kwargs): + return png.Writer(self.pixel_size, self.pixel_size, greyscale=True, bitdepth=1) + + def drawrect(self, row, col): + """ + Not used. + """ + + def save(self, stream, kind=None): + if isinstance(stream, str): + stream = open(stream, "wb") + self._img.write(stream, self.rows_iter()) + + def rows_iter(self): + yield from self.border_rows_iter() + border_col = [1] * (self.box_size * self.border) + for module_row in self.modules: + row = ( + border_col + + list( + chain.from_iterable( + ([not point] * self.box_size) for point in module_row + ) + ) + + border_col + ) + for _ in range(self.box_size): + yield row + yield from self.border_rows_iter() + + def border_rows_iter(self): + border_row = [1] * (self.box_size * (self.width + self.border * 2)) + for _ in range(self.border * self.box_size): + yield border_row + + +# Keeping this for backwards compatibility. +PymagingImage = PyPNGImage diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styledpil.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styledpil.py new file mode 100644 index 00000000..7c9d9995 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styledpil.py @@ -0,0 +1,112 @@ +# Needed on case-insensitive filesystems +from __future__ import absolute_import + +import qrcode.image.base +from qrcode.compat.pil import Image +from qrcode.image.styles.colormasks import QRColorMask, SolidFillColorMask +from qrcode.image.styles.moduledrawers import SquareModuleDrawer + + +class StyledPilImage(qrcode.image.base.BaseImageWithDrawer): + """ + Styled PIL image builder, default format is PNG. + + This differs from the PilImage in that there is a module_drawer, a + color_mask, and an optional image + + The module_drawer should extend the QRModuleDrawer class and implement the + drawrect_context(self, box, active, context), and probably also the + initialize function. This will draw an individual "module" or square on + the QR code. + + The color_mask will extend the QRColorMask class and will at very least + implement the get_fg_pixel(image, x, y) function, calculating a color to + put on the image at the pixel location (x,y) (more advanced functionality + can be gotten by instead overriding other functions defined in the + QRColorMask class) + + The Image can be specified either by path or with a Pillow Image, and if it + is there will be placed in the middle of the QR code. No effort is done to + ensure that the QR code is still legible after the image has been placed + there; Q or H level error correction levels are recommended to maintain + data integrity A resampling filter can be specified (defaulting to + PIL.Image.Resampling.LANCZOS) for resizing; see PIL.Image.resize() for possible + options for this parameter. + """ + + kind = "PNG" + + needs_processing = True + color_mask: QRColorMask + default_drawer_class = SquareModuleDrawer + + def __init__(self, *args, **kwargs): + self.color_mask = kwargs.get("color_mask", SolidFillColorMask()) + embeded_image_path = kwargs.get("embeded_image_path", None) + self.embeded_image = kwargs.get("embeded_image", None) + self.embeded_image_resample = kwargs.get( + "embeded_image_resample", Image.Resampling.LANCZOS + ) + if not self.embeded_image and embeded_image_path: + self.embeded_image = Image.open(embeded_image_path) + + # the paint_color is the color the module drawer will use to draw upon + # a canvas During the color mask process, pixels that are paint_color + # are replaced by a newly-calculated color + self.paint_color = tuple(0 for i in self.color_mask.back_color) + if self.color_mask.has_transparency: + self.paint_color = tuple([*self.color_mask.back_color[:3], 255]) + + super().__init__(*args, **kwargs) + + def new_image(self, **kwargs): + mode = ( + "RGBA" + if ( + self.color_mask.has_transparency + or (self.embeded_image and "A" in self.embeded_image.getbands()) + ) + else "RGB" + ) + # This is the background color. Should be white or whiteish + back_color = self.color_mask.back_color + + return Image.new(mode, (self.pixel_size, self.pixel_size), back_color) + + def init_new_image(self): + self.color_mask.initialize(self, self._img) + super().init_new_image() + + def process(self): + self.color_mask.apply_mask(self._img) + if self.embeded_image: + self.draw_embeded_image() + + def draw_embeded_image(self): + if not self.embeded_image: + return + total_width, _ = self._img.size + total_width = int(total_width) + logo_width_ish = int(total_width / 4) + logo_offset = ( + int((int(total_width / 2) - int(logo_width_ish / 2)) / self.box_size) + * self.box_size + ) # round the offset to the nearest module + logo_position = (logo_offset, logo_offset) + logo_width = total_width - logo_offset * 2 + region = self.embeded_image + region = region.resize((logo_width, logo_width), self.embeded_image_resample) + if "A" in region.getbands(): + self._img.alpha_composite(region, logo_position) + else: + self._img.paste(region, logo_position) + + def save(self, stream, format=None, **kwargs): + if format is None: + format = kwargs.get("kind", self.kind) + if "kind" in kwargs: + del kwargs["kind"] + self._img.save(stream, format=format, **kwargs) + + def __getattr__(self, name): + return getattr(self._img, name) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__init__.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..8edc327e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/colormasks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/colormasks.cpython-312.pyc new file mode 100644 index 00000000..641c78a8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/colormasks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/colormasks.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/colormasks.py new file mode 100644 index 00000000..3b9a8084 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/colormasks.py @@ -0,0 +1,220 @@ +# Needed on case-insensitive filesystems +from __future__ import absolute_import + +import math + +from qrcode.compat.pil import Image + + +class QRColorMask: + """ + QRColorMask is used to color in the QRCode. + + By the time apply_mask is called, the QRModuleDrawer of the StyledPilImage + will have drawn all of the modules on the canvas (the color of these + modules will be mostly black, although antialiasing may result in + gradients) In the base class, apply_mask is implemented such that the + background color will remain, but the foreground pixels will be replaced by + a color determined by a call to get_fg_pixel. There is additional + calculation done to preserve the gradient artifacts of antialiasing. + + All QRColorMask objects should be careful about RGB vs RGBA color spaces. + + For examples of what these look like, see doc/color_masks.png + """ + + back_color = (255, 255, 255) + has_transparency = False + paint_color = back_color + + def initialize(self, styledPilImage, image): + self.paint_color = styledPilImage.paint_color + + def apply_mask(self, image): + width, height = image.size + for x in range(width): + for y in range(height): + norm = self.extrap_color( + self.back_color, self.paint_color, image.getpixel((x, y)) + ) + if norm is not None: + image.putpixel( + (x, y), + self.interp_color( + self.get_bg_pixel(image, x, y), + self.get_fg_pixel(image, x, y), + norm, + ), + ) + else: + image.putpixel((x, y), self.get_bg_pixel(image, x, y)) + + def get_fg_pixel(self, image, x, y): + raise NotImplementedError("QRModuleDrawer.paint_fg_pixel") + + def get_bg_pixel(self, image, x, y): + return self.back_color + + # The following functions are helpful for color calculation: + + # interpolate a number between two numbers + def interp_num(self, n1, n2, norm): + return int(n2 * norm + n1 * (1 - norm)) + + # interpolate a color between two colorrs + def interp_color(self, col1, col2, norm): + return tuple(self.interp_num(col1[i], col2[i], norm) for i in range(len(col1))) + + # find the interpolation coefficient between two numbers + def extrap_num(self, n1, n2, interped_num): + if n2 == n1: + return None + else: + return (interped_num - n1) / (n2 - n1) + + # find the interpolation coefficient between two numbers + def extrap_color(self, col1, col2, interped_color): + normed = [] + for c1, c2, ci in zip(col1, col2, interped_color): + extrap = self.extrap_num(c1, c2, ci) + if extrap is not None: + normed.append(extrap) + if not normed: + return None + return sum(normed) / len(normed) + + +class SolidFillColorMask(QRColorMask): + """ + Just fills in the background with one color and the foreground with another + """ + + def __init__(self, back_color=(255, 255, 255), front_color=(0, 0, 0)): + self.back_color = back_color + self.front_color = front_color + self.has_transparency = len(self.back_color) == 4 + + def apply_mask(self, image): + if self.back_color == (255, 255, 255) and self.front_color == (0, 0, 0): + # Optimization: the image is already drawn by QRModuleDrawer in + # black and white, so if these are also our mask colors we don't + # need to do anything. This is much faster than actually applying a + # mask. + pass + else: + # TODO there's probably a way to use PIL.ImageMath instead of doing + # the individual pixel comparisons that the base class uses, which + # would be a lot faster. (In fact doing this would probably remove + # the need for the B&W optimization above.) + QRColorMask.apply_mask(self, image) + + def get_fg_pixel(self, image, x, y): + return self.front_color + + +class RadialGradiantColorMask(QRColorMask): + """ + Fills in the foreground with a radial gradient from the center to the edge + """ + + def __init__( + self, back_color=(255, 255, 255), center_color=(0, 0, 0), edge_color=(0, 0, 255) + ): + self.back_color = back_color + self.center_color = center_color + self.edge_color = edge_color + self.has_transparency = len(self.back_color) == 4 + + def get_fg_pixel(self, image, x, y): + width, _ = image.size + normedDistanceToCenter = math.sqrt( + (x - width / 2) ** 2 + (y - width / 2) ** 2 + ) / (math.sqrt(2) * width / 2) + return self.interp_color( + self.center_color, self.edge_color, normedDistanceToCenter + ) + + +class SquareGradiantColorMask(QRColorMask): + """ + Fills in the foreground with a square gradient from the center to the edge + """ + + def __init__( + self, back_color=(255, 255, 255), center_color=(0, 0, 0), edge_color=(0, 0, 255) + ): + self.back_color = back_color + self.center_color = center_color + self.edge_color = edge_color + self.has_transparency = len(self.back_color) == 4 + + def get_fg_pixel(self, image, x, y): + width, _ = image.size + normedDistanceToCenter = max(abs(x - width / 2), abs(y - width / 2)) / ( + width / 2 + ) + return self.interp_color( + self.center_color, self.edge_color, normedDistanceToCenter + ) + + +class HorizontalGradiantColorMask(QRColorMask): + """ + Fills in the foreground with a gradient sweeping from the left to the right + """ + + def __init__( + self, back_color=(255, 255, 255), left_color=(0, 0, 0), right_color=(0, 0, 255) + ): + self.back_color = back_color + self.left_color = left_color + self.right_color = right_color + self.has_transparency = len(self.back_color) == 4 + + def get_fg_pixel(self, image, x, y): + width, _ = image.size + return self.interp_color(self.left_color, self.right_color, x / width) + + +class VerticalGradiantColorMask(QRColorMask): + """ + Fills in the forefround with a gradient sweeping from the top to the bottom + """ + + def __init__( + self, back_color=(255, 255, 255), top_color=(0, 0, 0), bottom_color=(0, 0, 255) + ): + self.back_color = back_color + self.top_color = top_color + self.bottom_color = bottom_color + self.has_transparency = len(self.back_color) == 4 + + def get_fg_pixel(self, image, x, y): + width, _ = image.size + return self.interp_color(self.top_color, self.bottom_color, y / width) + + +class ImageColorMask(QRColorMask): + """ + Fills in the foreground with pixels from another image, either passed by + path or passed by image object. + """ + + def __init__( + self, back_color=(255, 255, 255), color_mask_path=None, color_mask_image=None + ): + self.back_color = back_color + if color_mask_image: + self.color_img = color_mask_image + else: + self.color_img = Image.open(color_mask_path) + + self.has_transparency = len(self.back_color) == 4 + + def initialize(self, styledPilImage, image): + self.paint_color = styledPilImage.paint_color + self.color_img = self.color_img.resize(image.size) + + def get_fg_pixel(self, image, x, y): + width, _ = image.size + return self.color_img.getpixel((x, y)) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__init__.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__init__.py new file mode 100644 index 00000000..99217d49 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__init__.py @@ -0,0 +1,10 @@ +# For backwards compatibility, importing the PIL drawers here. +try: + from .pil import CircleModuleDrawer # noqa: F401 + from .pil import GappedSquareModuleDrawer # noqa: F401 + from .pil import HorizontalBarsDrawer # noqa: F401 + from .pil import RoundedModuleDrawer # noqa: F401 + from .pil import SquareModuleDrawer # noqa: F401 + from .pil import VerticalBarsDrawer # noqa: F401 +except ImportError: + pass diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..675f4e79 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc new file mode 100644 index 00000000..abb28557 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc new file mode 100644 index 00000000..498d7d68 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/svg.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/svg.cpython-312.pyc new file mode 100644 index 00000000..9608ad59 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/svg.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/base.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/base.py new file mode 100644 index 00000000..8de33059 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/base.py @@ -0,0 +1,36 @@ +from __future__ import absolute_import + +import abc +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from qrcode.image.base import BaseImage + + +class QRModuleDrawer(abc.ABC): + """ + QRModuleDrawer exists to draw the modules of the QR Code onto images. + + For this, technically all that is necessary is a ``drawrect(self, box, + is_active)`` function which takes in the box in which it is to draw, + whether or not the box is "active" (a module exists there). If + ``needs_neighbors`` is set to True, then the method should also accept a + ``neighbors`` kwarg (the neighboring pixels). + + It is frequently necessary to also implement an "initialize" function to + set up values that only the containing Image class knows about. + + For examples of what these look like, see doc/module_drawers.png + """ + + needs_neighbors = False + + def __init__(self, **kwargs): + pass + + def initialize(self, img: "BaseImage") -> None: + self.img = img + + @abc.abstractmethod + def drawrect(self, box, is_active) -> None: + ... diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/pil.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/pil.py new file mode 100644 index 00000000..398010c6 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/pil.py @@ -0,0 +1,266 @@ +# Needed on case-insensitive filesystems +from __future__ import absolute_import + +from typing import TYPE_CHECKING, List + +from qrcode.compat.pil import Image, ImageDraw +from qrcode.image.styles.moduledrawers.base import QRModuleDrawer + +if TYPE_CHECKING: + from qrcode.image.styledpil import StyledPilImage + from qrcode.main import ActiveWithNeighbors + +# When drawing antialiased things, make them bigger and then shrink them down +# to size after the geometry has been drawn. +ANTIALIASING_FACTOR = 4 + + +class StyledPilQRModuleDrawer(QRModuleDrawer): + """ + A base class for StyledPilImage module drawers. + + NOTE: the color that this draws in should be whatever is equivalent to + black in the color space, and the specified QRColorMask will handle adding + colors as necessary to the image + """ + + img: "StyledPilImage" + + +class SquareModuleDrawer(StyledPilQRModuleDrawer): + """ + Draws the modules as simple squares + """ + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + self.imgDraw = ImageDraw.Draw(self.img._img) + + def drawrect(self, box, is_active: bool): + if is_active: + self.imgDraw.rectangle(box, fill=self.img.paint_color) + + +class GappedSquareModuleDrawer(StyledPilQRModuleDrawer): + """ + Draws the modules as simple squares that are not contiguous. + + The size_ratio determines how wide the squares are relative to the width of + the space they are printed in + """ + + def __init__(self, size_ratio=0.8): + self.size_ratio = size_ratio + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + self.imgDraw = ImageDraw.Draw(self.img._img) + self.delta = (1 - self.size_ratio) * self.img.box_size / 2 + + def drawrect(self, box, is_active: bool): + if is_active: + smaller_box = ( + box[0][0] + self.delta, + box[0][1] + self.delta, + box[1][0] - self.delta, + box[1][1] - self.delta, + ) + self.imgDraw.rectangle(smaller_box, fill=self.img.paint_color) + + +class CircleModuleDrawer(StyledPilQRModuleDrawer): + """ + Draws the modules as circles + """ + + circle = None + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + box_size = self.img.box_size + fake_size = box_size * ANTIALIASING_FACTOR + self.circle = Image.new( + self.img.mode, + (fake_size, fake_size), + self.img.color_mask.back_color, + ) + ImageDraw.Draw(self.circle).ellipse( + (0, 0, fake_size, fake_size), fill=self.img.paint_color + ) + self.circle = self.circle.resize((box_size, box_size), Image.Resampling.LANCZOS) + + def drawrect(self, box, is_active: bool): + if is_active: + self.img._img.paste(self.circle, (box[0][0], box[0][1])) + + +class RoundedModuleDrawer(StyledPilQRModuleDrawer): + """ + Draws the modules with all 90 degree corners replaced with rounded edges. + + radius_ratio determines the radius of the rounded edges - a value of 1 + means that an isolated module will be drawn as a circle, while a value of 0 + means that the radius of the rounded edge will be 0 (and thus back to 90 + degrees again). + """ + + needs_neighbors = True + + def __init__(self, radius_ratio=1): + self.radius_ratio = radius_ratio + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + self.corner_width = int(self.img.box_size / 2) + self.setup_corners() + + def setup_corners(self): + mode = self.img.mode + back_color = self.img.color_mask.back_color + front_color = self.img.paint_color + self.SQUARE = Image.new( + mode, (self.corner_width, self.corner_width), front_color + ) + + fake_width = self.corner_width * ANTIALIASING_FACTOR + radius = self.radius_ratio * fake_width + diameter = radius * 2 + base = Image.new( + mode, (fake_width, fake_width), back_color + ) # make something 4x bigger for antialiasing + base_draw = ImageDraw.Draw(base) + base_draw.ellipse((0, 0, diameter, diameter), fill=front_color) + base_draw.rectangle((radius, 0, fake_width, fake_width), fill=front_color) + base_draw.rectangle((0, radius, fake_width, fake_width), fill=front_color) + self.NW_ROUND = base.resize( + (self.corner_width, self.corner_width), Image.Resampling.LANCZOS + ) + self.SW_ROUND = self.NW_ROUND.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + self.SE_ROUND = self.NW_ROUND.transpose(Image.Transpose.ROTATE_180) + self.NE_ROUND = self.NW_ROUND.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + + def drawrect(self, box: List[List[int]], is_active: "ActiveWithNeighbors"): + if not is_active: + return + # find rounded edges + nw_rounded = not is_active.W and not is_active.N + ne_rounded = not is_active.N and not is_active.E + se_rounded = not is_active.E and not is_active.S + sw_rounded = not is_active.S and not is_active.W + + nw = self.NW_ROUND if nw_rounded else self.SQUARE + ne = self.NE_ROUND if ne_rounded else self.SQUARE + se = self.SE_ROUND if se_rounded else self.SQUARE + sw = self.SW_ROUND if sw_rounded else self.SQUARE + self.img._img.paste(nw, (box[0][0], box[0][1])) + self.img._img.paste(ne, (box[0][0] + self.corner_width, box[0][1])) + self.img._img.paste( + se, (box[0][0] + self.corner_width, box[0][1] + self.corner_width) + ) + self.img._img.paste(sw, (box[0][0], box[0][1] + self.corner_width)) + + +class VerticalBarsDrawer(StyledPilQRModuleDrawer): + """ + Draws vertically contiguous groups of modules as long rounded rectangles, + with gaps between neighboring bands (the size of these gaps is inversely + proportional to the horizontal_shrink). + """ + + needs_neighbors = True + + def __init__(self, horizontal_shrink=0.8): + self.horizontal_shrink = horizontal_shrink + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + self.half_height = int(self.img.box_size / 2) + self.delta = int((1 - self.horizontal_shrink) * self.half_height) + self.setup_edges() + + def setup_edges(self): + mode = self.img.mode + back_color = self.img.color_mask.back_color + front_color = self.img.paint_color + + height = self.half_height + width = height * 2 + shrunken_width = int(width * self.horizontal_shrink) + self.SQUARE = Image.new(mode, (shrunken_width, height), front_color) + + fake_width = width * ANTIALIASING_FACTOR + fake_height = height * ANTIALIASING_FACTOR + base = Image.new( + mode, (fake_width, fake_height), back_color + ) # make something 4x bigger for antialiasing + base_draw = ImageDraw.Draw(base) + base_draw.ellipse((0, 0, fake_width, fake_height * 2), fill=front_color) + + self.ROUND_TOP = base.resize((shrunken_width, height), Image.Resampling.LANCZOS) + self.ROUND_BOTTOM = self.ROUND_TOP.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + + def drawrect(self, box, is_active: "ActiveWithNeighbors"): + if is_active: + # find rounded edges + top_rounded = not is_active.N + bottom_rounded = not is_active.S + + top = self.ROUND_TOP if top_rounded else self.SQUARE + bottom = self.ROUND_BOTTOM if bottom_rounded else self.SQUARE + self.img._img.paste(top, (box[0][0] + self.delta, box[0][1])) + self.img._img.paste( + bottom, (box[0][0] + self.delta, box[0][1] + self.half_height) + ) + + +class HorizontalBarsDrawer(StyledPilQRModuleDrawer): + """ + Draws horizontally contiguous groups of modules as long rounded rectangles, + with gaps between neighboring bands (the size of these gaps is inversely + proportional to the vertical_shrink). + """ + + needs_neighbors = True + + def __init__(self, vertical_shrink=0.8): + self.vertical_shrink = vertical_shrink + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + self.half_width = int(self.img.box_size / 2) + self.delta = int((1 - self.vertical_shrink) * self.half_width) + self.setup_edges() + + def setup_edges(self): + mode = self.img.mode + back_color = self.img.color_mask.back_color + front_color = self.img.paint_color + + width = self.half_width + height = width * 2 + shrunken_height = int(height * self.vertical_shrink) + self.SQUARE = Image.new(mode, (width, shrunken_height), front_color) + + fake_width = width * ANTIALIASING_FACTOR + fake_height = height * ANTIALIASING_FACTOR + base = Image.new( + mode, (fake_width, fake_height), back_color + ) # make something 4x bigger for antialiasing + base_draw = ImageDraw.Draw(base) + base_draw.ellipse((0, 0, fake_width * 2, fake_height), fill=front_color) + + self.ROUND_LEFT = base.resize((width, shrunken_height), Image.Resampling.LANCZOS) + self.ROUND_RIGHT = self.ROUND_LEFT.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + + def drawrect(self, box, is_active: "ActiveWithNeighbors"): + if is_active: + # find rounded edges + left_rounded = not is_active.W + right_rounded = not is_active.E + + left = self.ROUND_LEFT if left_rounded else self.SQUARE + right = self.ROUND_RIGHT if right_rounded else self.SQUARE + self.img._img.paste(left, (box[0][0], box[0][1] + self.delta)) + self.img._img.paste( + right, (box[0][0] + self.half_width, box[0][1] + self.delta) + ) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/svg.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/svg.py new file mode 100644 index 00000000..6e629759 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/svg.py @@ -0,0 +1,141 @@ +import abc +from decimal import Decimal +from typing import TYPE_CHECKING, NamedTuple + +from qrcode.image.styles.moduledrawers.base import QRModuleDrawer +from qrcode.compat.etree import ET + +if TYPE_CHECKING: + from qrcode.image.svg import SvgFragmentImage, SvgPathImage + +ANTIALIASING_FACTOR = 4 + + +class Coords(NamedTuple): + x0: Decimal + y0: Decimal + x1: Decimal + y1: Decimal + xh: Decimal + yh: Decimal + + +class BaseSvgQRModuleDrawer(QRModuleDrawer): + img: "SvgFragmentImage" + + def __init__(self, *, size_ratio: Decimal = Decimal(1), **kwargs): + self.size_ratio = size_ratio + + def initialize(self, *args, **kwargs) -> None: + super().initialize(*args, **kwargs) + self.box_delta = (1 - self.size_ratio) * self.img.box_size / 2 + self.box_size = Decimal(self.img.box_size) * self.size_ratio + self.box_half = self.box_size / 2 + + def coords(self, box) -> Coords: + row, col = box[0] + x = row + self.box_delta + y = col + self.box_delta + + return Coords( + x, + y, + x + self.box_size, + y + self.box_size, + x + self.box_half, + y + self.box_half, + ) + + +class SvgQRModuleDrawer(BaseSvgQRModuleDrawer): + tag = "rect" + + def initialize(self, *args, **kwargs) -> None: + super().initialize(*args, **kwargs) + self.tag_qname = ET.QName(self.img._SVG_namespace, self.tag) + + def drawrect(self, box, is_active: bool): + if not is_active: + return + self.img._img.append(self.el(box)) + + @abc.abstractmethod + def el(self, box): + ... + + +class SvgSquareDrawer(SvgQRModuleDrawer): + def initialize(self, *args, **kwargs) -> None: + super().initialize(*args, **kwargs) + self.unit_size = self.img.units(self.box_size) + + def el(self, box): + coords = self.coords(box) + return ET.Element( + self.tag_qname, # type: ignore + x=self.img.units(coords.x0), + y=self.img.units(coords.y0), + width=self.unit_size, + height=self.unit_size, + ) + + +class SvgCircleDrawer(SvgQRModuleDrawer): + tag = "circle" + + def initialize(self, *args, **kwargs) -> None: + super().initialize(*args, **kwargs) + self.radius = self.img.units(self.box_half) + + def el(self, box): + coords = self.coords(box) + return ET.Element( + self.tag_qname, # type: ignore + cx=self.img.units(coords.xh), + cy=self.img.units(coords.yh), + r=self.radius, + ) + + +class SvgPathQRModuleDrawer(BaseSvgQRModuleDrawer): + img: "SvgPathImage" + + def drawrect(self, box, is_active: bool): + if not is_active: + return + self.img._subpaths.append(self.subpath(box)) + + @abc.abstractmethod + def subpath(self, box) -> str: + ... + + +class SvgPathSquareDrawer(SvgPathQRModuleDrawer): + def subpath(self, box) -> str: + coords = self.coords(box) + x0 = self.img.units(coords.x0, text=False) + y0 = self.img.units(coords.y0, text=False) + x1 = self.img.units(coords.x1, text=False) + y1 = self.img.units(coords.y1, text=False) + + return f"M{x0},{y0}H{x1}V{y1}H{x0}z" + + +class SvgPathCircleDrawer(SvgPathQRModuleDrawer): + def initialize(self, *args, **kwargs) -> None: + super().initialize(*args, **kwargs) + + def subpath(self, box) -> str: + coords = self.coords(box) + x0 = self.img.units(coords.x0, text=False) + yh = self.img.units(coords.yh, text=False) + h = self.img.units(self.box_half - self.box_delta, text=False) + x1 = self.img.units(coords.x1, text=False) + + # rx,ry is the centerpoint of the arc + # 1? is the x-axis-rotation + # 2? is the large-arc-flag + # 3? is the sweep flag + # x,y is the point the arc is drawn to + + return f"M{x0},{yh}A{h},{h} 0 0 0 {x1},{yh}A{h},{h} 0 0 0 {x0},{yh}z" diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/svg.py b/Backend/venv/lib/python3.12/site-packages/qrcode/image/svg.py new file mode 100644 index 00000000..bf0ec870 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/image/svg.py @@ -0,0 +1,179 @@ +import decimal +from decimal import Decimal +from typing import List, Optional, Type, Union, overload + +from typing_extensions import Literal + +import qrcode.image.base +from qrcode.compat.etree import ET +from qrcode.image.styles.moduledrawers import svg as svg_drawers +from qrcode.image.styles.moduledrawers.base import QRModuleDrawer + + +class SvgFragmentImage(qrcode.image.base.BaseImageWithDrawer): + """ + SVG image builder + + Creates a QR-code image as a SVG document fragment. + """ + + _SVG_namespace = "http://www.w3.org/2000/svg" + kind = "SVG" + allowed_kinds = ("SVG",) + default_drawer_class: Type[QRModuleDrawer] = svg_drawers.SvgSquareDrawer + + def __init__(self, *args, **kwargs): + ET.register_namespace("svg", self._SVG_namespace) + super().__init__(*args, **kwargs) + # Save the unit size, for example the default box_size of 10 is '1mm'. + self.unit_size = self.units(self.box_size) + + @overload + def units(self, pixels: Union[int, Decimal], text: Literal[False]) -> Decimal: + ... + + @overload + def units(self, pixels: Union[int, Decimal], text: Literal[True] = True) -> str: + ... + + def units(self, pixels, text=True): + """ + A box_size of 10 (default) equals 1mm. + """ + units = Decimal(pixels) / 10 + if not text: + return units + units = units.quantize(Decimal("0.001")) + context = decimal.Context(traps=[decimal.Inexact]) + try: + for d in (Decimal("0.01"), Decimal("0.1"), Decimal("0")): + units = units.quantize(d, context=context) + except decimal.Inexact: + pass + return f"{units}mm" + + def save(self, stream, kind=None): + self.check_kind(kind=kind) + self._write(stream) + + def to_string(self, **kwargs): + return ET.tostring(self._img, **kwargs) + + def new_image(self, **kwargs): + return self._svg(**kwargs) + + def _svg(self, tag=None, version="1.1", **kwargs): + if tag is None: + tag = ET.QName(self._SVG_namespace, "svg") + dimension = self.units(self.pixel_size) + return ET.Element( + tag, # type: ignore + width=dimension, + height=dimension, + version=version, + **kwargs, + ) + + def _write(self, stream): + ET.ElementTree(self._img).write(stream, xml_declaration=False) + + +class SvgImage(SvgFragmentImage): + """ + Standalone SVG image builder + + Creates a QR-code image as a standalone SVG document. + """ + + background: Optional[str] = None + drawer_aliases: qrcode.image.base.DrawerAliases = { + "circle": (svg_drawers.SvgCircleDrawer, {}), + "gapped-circle": (svg_drawers.SvgCircleDrawer, {"size_ratio": Decimal(0.8)}), + "gapped-square": (svg_drawers.SvgSquareDrawer, {"size_ratio": Decimal(0.8)}), + } + + def _svg(self, tag="svg", **kwargs): + svg = super()._svg(tag=tag, **kwargs) + svg.set("xmlns", self._SVG_namespace) + if self.background: + svg.append( + ET.Element( + "rect", + fill=self.background, + x="0", + y="0", + width="100%", + height="100%", + ) + ) + return svg + + def _write(self, stream): + ET.ElementTree(self._img).write(stream, encoding="UTF-8", xml_declaration=True) + + +class SvgPathImage(SvgImage): + """ + SVG image builder with one single element (removes white spaces + between individual QR points). + """ + + QR_PATH_STYLE = { + "fill": "#000000", + "fill-opacity": "1", + "fill-rule": "nonzero", + "stroke": "none", + } + + needs_processing = True + path: Optional[ET.Element] = None + default_drawer_class: Type[QRModuleDrawer] = svg_drawers.SvgPathSquareDrawer + drawer_aliases = { + "circle": (svg_drawers.SvgPathCircleDrawer, {}), + "gapped-circle": ( + svg_drawers.SvgPathCircleDrawer, + {"size_ratio": Decimal(0.8)}, + ), + "gapped-square": ( + svg_drawers.SvgPathSquareDrawer, + {"size_ratio": Decimal(0.8)}, + ), + } + + def __init__(self, *args, **kwargs): + self._subpaths: List[str] = [] + super().__init__(*args, **kwargs) + + def _svg(self, viewBox=None, **kwargs): + if viewBox is None: + dimension = self.units(self.pixel_size, text=False) + viewBox = "0 0 {d} {d}".format(d=dimension) + return super()._svg(viewBox=viewBox, **kwargs) + + def process(self): + # Store the path just in case someone wants to use it again or in some + # unique way. + self.path = ET.Element( + ET.QName("path"), # type: ignore + d="".join(self._subpaths), + id="qr-path", + **self.QR_PATH_STYLE, + ) + self._subpaths = [] + self._img.append(self.path) + + +class SvgFillImage(SvgImage): + """ + An SvgImage that fills the background to white. + """ + + background = "white" + + +class SvgPathFillImage(SvgPathImage): + """ + An SvgPathImage that fills the background to white. + """ + + background = "white" diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/main.py b/Backend/venv/lib/python3.12/site-packages/qrcode/main.py new file mode 100644 index 00000000..0ac91bbb --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/main.py @@ -0,0 +1,547 @@ +import sys +from bisect import bisect_left +from typing import ( + Dict, + Generic, + List, + NamedTuple, + Optional, + Type, + TypeVar, + cast, + overload, +) + +from typing_extensions import Literal + +from qrcode import constants, exceptions, util +from qrcode.image.base import BaseImage +from qrcode.image.pure import PyPNGImage + +ModulesType = List[List[Optional[bool]]] +# Cache modules generated just based on the QR Code version +precomputed_qr_blanks: Dict[int, ModulesType] = {} + + +def make(data=None, **kwargs): + qr = QRCode(**kwargs) + qr.add_data(data) + return qr.make_image() + + +def _check_box_size(size): + if int(size) <= 0: + raise ValueError(f"Invalid box size (was {size}, expected larger than 0)") + + +def _check_border(size): + if int(size) < 0: + raise ValueError( + "Invalid border value (was %s, expected 0 or larger than that)" % size + ) + + +def _check_mask_pattern(mask_pattern): + if mask_pattern is None: + return + if not isinstance(mask_pattern, int): + raise TypeError( + f"Invalid mask pattern (was {type(mask_pattern)}, expected int)" + ) + if mask_pattern < 0 or mask_pattern > 7: + raise ValueError(f"Mask pattern should be in range(8) (got {mask_pattern})") + + +def copy_2d_array(x): + return [row[:] for row in x] + + +class ActiveWithNeighbors(NamedTuple): + NW: bool + N: bool + NE: bool + W: bool + me: bool + E: bool + SW: bool + S: bool + SE: bool + + def __bool__(self) -> bool: + return self.me + + +GenericImage = TypeVar("GenericImage", bound=BaseImage) +GenericImageLocal = TypeVar("GenericImageLocal", bound=BaseImage) + + +class QRCode(Generic[GenericImage]): + modules: ModulesType + _version: Optional[int] = None + + def __init__( + self, + version=None, + error_correction=constants.ERROR_CORRECT_M, + box_size=10, + border=4, + image_factory: Optional[Type[GenericImage]] = None, + mask_pattern=None, + ): + _check_box_size(box_size) + _check_border(border) + self.version = version + self.error_correction = int(error_correction) + self.box_size = int(box_size) + # Spec says border should be at least four boxes wide, but allow for + # any (e.g. for producing printable QR codes). + self.border = int(border) + self.mask_pattern = mask_pattern + self.image_factory = image_factory + if image_factory is not None: + assert issubclass(image_factory, BaseImage) + self.clear() + + @property + def version(self) -> int: + if self._version is None: + self.best_fit() + return cast(int, self._version) + + @version.setter + def version(self, value) -> None: + if value is not None: + value = int(value) + util.check_version(value) + self._version = value + + @property + def mask_pattern(self): + return self._mask_pattern + + @mask_pattern.setter + def mask_pattern(self, pattern): + _check_mask_pattern(pattern) + self._mask_pattern = pattern + + def clear(self): + """ + Reset the internal data. + """ + self.modules = [[]] + self.modules_count = 0 + self.data_cache = None + self.data_list = [] + + def add_data(self, data, optimize=20): + """ + Add data to this QR Code. + + :param optimize: Data will be split into multiple chunks to optimize + the QR size by finding to more compressed modes of at least this + length. Set to ``0`` to avoid optimizing at all. + """ + if isinstance(data, util.QRData): + self.data_list.append(data) + elif optimize: + self.data_list.extend(util.optimal_data_chunks(data, minimum=optimize)) + else: + self.data_list.append(util.QRData(data)) + self.data_cache = None + + def make(self, fit=True): + """ + Compile the data into a QR Code array. + + :param fit: If ``True`` (or if a size has not been provided), find the + best fit for the data to avoid data overflow errors. + """ + if fit or (self.version is None): + self.best_fit(start=self.version) + if self.mask_pattern is None: + self.makeImpl(False, self.best_mask_pattern()) + else: + self.makeImpl(False, self.mask_pattern) + + def makeImpl(self, test, mask_pattern): + self.modules_count = self.version * 4 + 17 + + if self.version in precomputed_qr_blanks: + self.modules = copy_2d_array(precomputed_qr_blanks[self.version]) + else: + self.modules = [ + [None] * self.modules_count for i in range(self.modules_count) + ] + self.setup_position_probe_pattern(0, 0) + self.setup_position_probe_pattern(self.modules_count - 7, 0) + self.setup_position_probe_pattern(0, self.modules_count - 7) + self.setup_position_adjust_pattern() + self.setup_timing_pattern() + + precomputed_qr_blanks[self.version] = copy_2d_array(self.modules) + + self.setup_type_info(test, mask_pattern) + + if self.version >= 7: + self.setup_type_number(test) + + if self.data_cache is None: + self.data_cache = util.create_data( + self.version, self.error_correction, self.data_list + ) + self.map_data(self.data_cache, mask_pattern) + + def setup_position_probe_pattern(self, row, col): + for r in range(-1, 8): + + if row + r <= -1 or self.modules_count <= row + r: + continue + + for c in range(-1, 8): + + if col + c <= -1 or self.modules_count <= col + c: + continue + + if ( + (0 <= r <= 6 and c in {0, 6}) + or (0 <= c <= 6 and r in {0, 6}) + or (2 <= r <= 4 and 2 <= c <= 4) + ): + self.modules[row + r][col + c] = True + else: + self.modules[row + r][col + c] = False + + def best_fit(self, start=None): + """ + Find the minimum size required to fit in the data. + """ + if start is None: + start = 1 + util.check_version(start) + + # Corresponds to the code in util.create_data, except we don't yet know + # version, so optimistically assume start and check later + mode_sizes = util.mode_sizes_for_version(start) + buffer = util.BitBuffer() + for data in self.data_list: + buffer.put(data.mode, 4) + buffer.put(len(data), mode_sizes[data.mode]) + data.write(buffer) + + needed_bits = len(buffer) + self.version = bisect_left( + util.BIT_LIMIT_TABLE[self.error_correction], needed_bits, start + ) + if self.version == 41: + raise exceptions.DataOverflowError() + + # Now check whether we need more bits for the mode sizes, recursing if + # our guess was too low + if mode_sizes is not util.mode_sizes_for_version(self.version): + self.best_fit(start=self.version) + return self.version + + def best_mask_pattern(self): + """ + Find the most efficient mask pattern. + """ + min_lost_point = 0 + pattern = 0 + + for i in range(8): + self.makeImpl(True, i) + + lost_point = util.lost_point(self.modules) + + if i == 0 or min_lost_point > lost_point: + min_lost_point = lost_point + pattern = i + + return pattern + + def print_tty(self, out=None): + """ + Output the QR Code only using TTY colors. + + If the data has not been compiled yet, make it first. + """ + if out is None: + import sys + + out = sys.stdout + + if not out.isatty(): + raise OSError("Not a tty") + + if self.data_cache is None: + self.make() + + modcount = self.modules_count + out.write("\x1b[1;47m" + (" " * (modcount * 2 + 4)) + "\x1b[0m\n") + for r in range(modcount): + out.write("\x1b[1;47m \x1b[40m") + for c in range(modcount): + if self.modules[r][c]: + out.write(" ") + else: + out.write("\x1b[1;47m \x1b[40m") + out.write("\x1b[1;47m \x1b[0m\n") + out.write("\x1b[1;47m" + (" " * (modcount * 2 + 4)) + "\x1b[0m\n") + out.flush() + + def print_ascii(self, out=None, tty=False, invert=False): + """ + Output the QR Code using ASCII characters. + + :param tty: use fixed TTY color codes (forces invert=True) + :param invert: invert the ASCII characters (solid <-> transparent) + """ + if out is None: + out = sys.stdout + + if tty and not out.isatty(): + raise OSError("Not a tty") + + if self.data_cache is None: + self.make() + + modcount = self.modules_count + codes = [bytes((code,)).decode("cp437") for code in (255, 223, 220, 219)] + if tty: + invert = True + if invert: + codes.reverse() + + def get_module(x, y) -> int: + if invert and self.border and max(x, y) >= modcount + self.border: + return 1 + if min(x, y) < 0 or max(x, y) >= modcount: + return 0 + return cast(int, self.modules[x][y]) + + for r in range(-self.border, modcount + self.border, 2): + if tty: + if not invert or r < modcount + self.border - 1: + out.write("\x1b[48;5;232m") # Background black + out.write("\x1b[38;5;255m") # Foreground white + for c in range(-self.border, modcount + self.border): + pos = get_module(r, c) + (get_module(r + 1, c) << 1) + out.write(codes[pos]) + if tty: + out.write("\x1b[0m") + out.write("\n") + out.flush() + + @overload + def make_image(self, image_factory: Literal[None] = None, **kwargs) -> GenericImage: + ... + + @overload + def make_image( + self, image_factory: Type[GenericImageLocal] = None, **kwargs + ) -> GenericImageLocal: + ... + + def make_image(self, image_factory=None, **kwargs): + """ + Make an image from the QR Code data. + + If the data has not been compiled yet, make it first. + """ + _check_box_size(self.box_size) + if self.data_cache is None: + self.make() + + if image_factory is not None: + assert issubclass(image_factory, BaseImage) + else: + image_factory = self.image_factory + if image_factory is None: + from qrcode.image.pil import Image, PilImage + + # Use PIL by default if available, otherwise use PyPNG. + image_factory = PilImage if Image else PyPNGImage + + im = image_factory( + self.border, + self.modules_count, + self.box_size, + qrcode_modules=self.modules, + **kwargs, + ) + + if im.needs_drawrect: + for r in range(self.modules_count): + for c in range(self.modules_count): + if im.needs_context: + im.drawrect_context(r, c, qr=self) + elif self.modules[r][c]: + im.drawrect(r, c) + if im.needs_processing: + im.process() + + return im + + # return true if and only if (row, col) is in the module + def is_constrained(self, row: int, col: int) -> bool: + return ( + row >= 0 + and row < len(self.modules) + and col >= 0 + and col < len(self.modules[row]) + ) + + def setup_timing_pattern(self): + for r in range(8, self.modules_count - 8): + if self.modules[r][6] is not None: + continue + self.modules[r][6] = r % 2 == 0 + + for c in range(8, self.modules_count - 8): + if self.modules[6][c] is not None: + continue + self.modules[6][c] = c % 2 == 0 + + def setup_position_adjust_pattern(self): + pos = util.pattern_position(self.version) + + for i in range(len(pos)): + + row = pos[i] + + for j in range(len(pos)): + + col = pos[j] + + if self.modules[row][col] is not None: + continue + + for r in range(-2, 3): + + for c in range(-2, 3): + + if ( + r == -2 + or r == 2 + or c == -2 + or c == 2 + or (r == 0 and c == 0) + ): + self.modules[row + r][col + c] = True + else: + self.modules[row + r][col + c] = False + + def setup_type_number(self, test): + bits = util.BCH_type_number(self.version) + + for i in range(18): + mod = not test and ((bits >> i) & 1) == 1 + self.modules[i // 3][i % 3 + self.modules_count - 8 - 3] = mod + + for i in range(18): + mod = not test and ((bits >> i) & 1) == 1 + self.modules[i % 3 + self.modules_count - 8 - 3][i // 3] = mod + + def setup_type_info(self, test, mask_pattern): + data = (self.error_correction << 3) | mask_pattern + bits = util.BCH_type_info(data) + + # vertical + for i in range(15): + + mod = not test and ((bits >> i) & 1) == 1 + + if i < 6: + self.modules[i][8] = mod + elif i < 8: + self.modules[i + 1][8] = mod + else: + self.modules[self.modules_count - 15 + i][8] = mod + + # horizontal + for i in range(15): + + mod = not test and ((bits >> i) & 1) == 1 + + if i < 8: + self.modules[8][self.modules_count - i - 1] = mod + elif i < 9: + self.modules[8][15 - i - 1 + 1] = mod + else: + self.modules[8][15 - i - 1] = mod + + # fixed module + self.modules[self.modules_count - 8][8] = not test + + def map_data(self, data, mask_pattern): + inc = -1 + row = self.modules_count - 1 + bitIndex = 7 + byteIndex = 0 + + mask_func = util.mask_func(mask_pattern) + + data_len = len(data) + + for col in range(self.modules_count - 1, 0, -2): + + if col <= 6: + col -= 1 + + col_range = (col, col - 1) + + while True: + + for c in col_range: + + if self.modules[row][c] is None: + + dark = False + + if byteIndex < data_len: + dark = ((data[byteIndex] >> bitIndex) & 1) == 1 + + if mask_func(row, c): + dark = not dark + + self.modules[row][c] = dark + bitIndex -= 1 + + if bitIndex == -1: + byteIndex += 1 + bitIndex = 7 + + row += inc + + if row < 0 or self.modules_count <= row: + row -= inc + inc = -inc + break + + def get_matrix(self): + """ + Return the QR Code as a multidimensional array, including the border. + + To return the array without a border, set ``self.border`` to 0 first. + """ + if self.data_cache is None: + self.make() + + if not self.border: + return self.modules + + width = len(self.modules) + self.border * 2 + code = [[False] * width] * self.border + x_border = [False] * self.border + for module in self.modules: + code.append(x_border + cast(List[bool], module) + x_border) + code += [[False] * width] * self.border + + return code + + def active_with_neighbors(self, row: int, col: int) -> ActiveWithNeighbors: + context: List[bool] = [] + for r in range(row - 1, row + 2): + for c in range(col - 1, col + 2): + context.append(self.is_constrained(r, c) and bool(self.modules[r][c])) + return ActiveWithNeighbors(*context) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/release.py b/Backend/venv/lib/python3.12/site-packages/qrcode/release.py new file mode 100644 index 00000000..b93de39c --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/release.py @@ -0,0 +1,41 @@ +""" +This file provides zest.releaser entrypoints using when releasing new +qrcode versions. +""" +import os +import re +import datetime + + +def update_manpage(data): + """ + Update the version in the manpage document. + """ + if data["name"] != "qrcode": + return + + base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + filename = os.path.join(base_dir, "doc", "qr.1") + with open(filename) as f: + lines = f.readlines() + + changed = False + for i, line in enumerate(lines): + if not line.startswith(".TH "): + continue + parts = re.split(r'"([^"]*)"', line) + if len(parts) < 5: + continue + changed = parts[3] != data["new_version"] + if changed: + # Update version + parts[3] = data["new_version"] + # Update date + parts[1] = datetime.datetime.now().strftime("%-d %b %Y") + lines[i] = '"'.join(parts) + break + + if changed: + with open(filename, "w") as f: + for line in lines: + f.write(line) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__init__.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..fa326024 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_example.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_example.cpython-312.pyc new file mode 100644 index 00000000..ae3388fc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_example.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_qrcode.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_qrcode.cpython-312.pyc new file mode 100644 index 00000000..957e1dd4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_qrcode.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_qrcode_svg.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_qrcode_svg.cpython-312.pyc new file mode 100644 index 00000000..ff1fb9bc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_qrcode_svg.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_release.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_release.cpython-312.pyc new file mode 100644 index 00000000..9cd4b78a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_release.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_script.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_script.cpython-312.pyc new file mode 100644 index 00000000..60bdafb7 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_script.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_util.cpython-312.pyc new file mode 100644 index 00000000..fbb4eb66 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/__pycache__/test_util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_example.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_example.py new file mode 100644 index 00000000..2e03dc53 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_example.py @@ -0,0 +1,13 @@ +import unittest +from unittest import mock + +from qrcode import run_example +from qrcode.compat.pil import Image + + +class ExampleTest(unittest.TestCase): + @unittest.skipIf(not Image, "Requires PIL") + @mock.patch("PIL.Image.Image.show") + def runTest(self, mock_show): + run_example() + mock_show.assert_called_with() diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_qrcode.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_qrcode.py new file mode 100644 index 00000000..5c1ea35b --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_qrcode.py @@ -0,0 +1,487 @@ +import io +import os +import unittest +import warnings +from tempfile import mkdtemp +from unittest import mock + +import png + +import qrcode +import qrcode.util +from qrcode.compat.pil import Image as pil_Image +from qrcode.exceptions import DataOverflowError +from qrcode.image.base import BaseImage +from qrcode.image.pure import PyPNGImage +from qrcode.image.styledpil import StyledPilImage +from qrcode.image.styles import colormasks, moduledrawers +from qrcode.util import MODE_8BIT_BYTE, MODE_ALPHA_NUM, MODE_NUMBER, QRData + +UNICODE_TEXT = "\u03b1\u03b2\u03b3" +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) +RED = (255, 0, 0) + + +class QRCodeTests(unittest.TestCase): + def setUp(self): + self.tmpdir = mkdtemp() + + def tearDown(self): + os.rmdir(self.tmpdir) + + def test_basic(self): + qr = qrcode.QRCode(version=1) + qr.add_data("a") + qr.make(fit=False) + + def test_large(self): + qr = qrcode.QRCode(version=27) + qr.add_data("a") + qr.make(fit=False) + + def test_invalid_version(self): + self.assertRaises(ValueError, qrcode.QRCode, version=41) + + def test_invalid_border(self): + self.assertRaises(ValueError, qrcode.QRCode, border=-1) + + def test_overflow(self): + qr = qrcode.QRCode(version=1) + qr.add_data("abcdefghijklmno") + self.assertRaises(DataOverflowError, qr.make, fit=False) + + def test_add_qrdata(self): + qr = qrcode.QRCode(version=1) + data = QRData("a") + qr.add_data(data) + qr.make(fit=False) + + def test_fit(self): + qr = qrcode.QRCode() + qr.add_data("a") + qr.make() + self.assertEqual(qr.version, 1) + qr.add_data("bcdefghijklmno") + qr.make() + self.assertEqual(qr.version, 2) + + def test_mode_number(self): + qr = qrcode.QRCode() + qr.add_data("1234567890123456789012345678901234", optimize=0) + qr.make() + self.assertEqual(qr.version, 1) + self.assertEqual(qr.data_list[0].mode, MODE_NUMBER) + + def test_mode_alpha(self): + qr = qrcode.QRCode() + qr.add_data("ABCDEFGHIJ1234567890", optimize=0) + qr.make() + self.assertEqual(qr.version, 1) + self.assertEqual(qr.data_list[0].mode, MODE_ALPHA_NUM) + + def test_regression_mode_comma(self): + qr = qrcode.QRCode() + qr.add_data(",", optimize=0) + qr.make() + self.assertEqual(qr.data_list[0].mode, MODE_8BIT_BYTE) + + def test_mode_8bit(self): + qr = qrcode.QRCode() + qr.add_data("abcABC" + UNICODE_TEXT, optimize=0) + qr.make() + self.assertEqual(qr.version, 1) + self.assertEqual(qr.data_list[0].mode, MODE_8BIT_BYTE) + + def test_mode_8bit_newline(self): + qr = qrcode.QRCode() + qr.add_data("ABCDEFGHIJ1234567890\n", optimize=0) + qr.make() + self.assertEqual(qr.data_list[0].mode, MODE_8BIT_BYTE) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_pil(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image() + img.save(io.BytesIO()) + self.assertIsInstance(img.get_image(), pil_Image.Image) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_pil_with_transparent_background(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(back_color="TransParent") + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_pil_with_red_background(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(back_color="red") + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_pil_with_rgb_color_tuples(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(back_color=(255, 195, 235), fill_color=(55, 95, 35)) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_with_pattern(self): + qr = qrcode.QRCode(mask_pattern=3) + qr.add_data(UNICODE_TEXT) + img = qr.make_image() + img.save(io.BytesIO()) + + def test_make_image_with_wrong_pattern(self): + with self.assertRaises(TypeError): + qrcode.QRCode(mask_pattern="string pattern") + + with self.assertRaises(ValueError): + qrcode.QRCode(mask_pattern=-1) + + with self.assertRaises(ValueError): + qrcode.QRCode(mask_pattern=42) + + def test_mask_pattern_setter(self): + qr = qrcode.QRCode() + + with self.assertRaises(TypeError): + qr.mask_pattern = "string pattern" + + with self.assertRaises(ValueError): + qr.mask_pattern = -1 + + with self.assertRaises(ValueError): + qr.mask_pattern = 8 + + def test_qrcode_bad_factory(self): + with self.assertRaises(TypeError): + qrcode.QRCode(image_factory="not_BaseImage") # type: ignore + + with self.assertRaises(AssertionError): + qrcode.QRCode(image_factory=dict) # type: ignore + + def test_qrcode_factory(self): + class MockFactory(BaseImage): + drawrect = mock.Mock() + new_image = mock.Mock() + + qr = qrcode.QRCode(image_factory=MockFactory) + qr.add_data(UNICODE_TEXT) + qr.make_image() + self.assertTrue(MockFactory.new_image.called) + self.assertTrue(MockFactory.drawrect.called) + + def test_render_pypng(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=PyPNGImage) + self.assertIsInstance(img.get_image(), png.Writer) + + print(img.width, img.box_size, img.border) + img.save(io.BytesIO()) + + def test_render_pypng_to_str(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=PyPNGImage) + self.assertIsInstance(img.get_image(), png.Writer) + + mock_open = mock.mock_open() + with mock.patch("qrcode.image.pure.open", mock_open, create=True): + img.save("test_file.png") + mock_open.assert_called_once_with("test_file.png", "wb") + mock_open("test_file.png", "wb").write.assert_called() + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_Image(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=StyledPilImage) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_embeded_image(self): + embeded_img = pil_Image.new("RGB", (10, 10), color="red") + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=StyledPilImage, embeded_image=embeded_img) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_embeded_image_path(self): + tmpfile = os.path.join(self.tmpdir, "test.png") + embeded_img = pil_Image.new("RGB", (10, 10), color="red") + embeded_img.save(tmpfile) + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=StyledPilImage, embeded_image_path=tmpfile) + img.save(io.BytesIO()) + os.remove(tmpfile) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_square_module_drawer(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image( + image_factory=StyledPilImage, + module_drawer=moduledrawers.SquareModuleDrawer(), + ) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_gapped_module_drawer(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image( + image_factory=StyledPilImage, + module_drawer=moduledrawers.GappedSquareModuleDrawer(), + ) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_circle_module_drawer(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image( + image_factory=StyledPilImage, + module_drawer=moduledrawers.CircleModuleDrawer(), + ) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_rounded_module_drawer(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image( + image_factory=StyledPilImage, + module_drawer=moduledrawers.RoundedModuleDrawer(), + ) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_vertical_bars_module_drawer(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image( + image_factory=StyledPilImage, + module_drawer=moduledrawers.VerticalBarsDrawer(), + ) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_horizontal_bars_module_drawer(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + img = qr.make_image( + image_factory=StyledPilImage, + module_drawer=moduledrawers.HorizontalBarsDrawer(), + ) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_default_solid_color_mask(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.SolidFillColorMask() + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_solid_color_mask(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.SolidFillColorMask(back_color=WHITE, front_color=RED) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_color_mask_with_transparency(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.SolidFillColorMask( + back_color=(255, 0, 255, 255), front_color=RED + ) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + assert img.mode == "RGBA" + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_radial_gradient_color_mask(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.RadialGradiantColorMask( + back_color=WHITE, center_color=BLACK, edge_color=RED + ) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_square_gradient_color_mask(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.SquareGradiantColorMask( + back_color=WHITE, center_color=BLACK, edge_color=RED + ) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_horizontal_gradient_color_mask(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.HorizontalGradiantColorMask( + back_color=WHITE, left_color=RED, right_color=BLACK + ) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_vertical_gradient_color_mask(self): + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.VerticalGradiantColorMask( + back_color=WHITE, top_color=RED, bottom_color=BLACK + ) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + @unittest.skipIf(not pil_Image, "Requires PIL") + def test_render_styled_with_image_color_mask(self): + img_mask = pil_Image.new("RGB", (10, 10), color="red") + qr = qrcode.QRCode(error_correction=qrcode.ERROR_CORRECT_L) + qr.add_data(UNICODE_TEXT) + mask = colormasks.ImageColorMask(back_color=WHITE, color_mask_image=img_mask) + img = qr.make_image(image_factory=StyledPilImage, color_mask=mask) + img.save(io.BytesIO()) + + def test_optimize(self): + qr = qrcode.QRCode() + text = "A1abc12345def1HELLOa" + qr.add_data(text, optimize=4) + qr.make() + self.assertEqual( + [d.mode for d in qr.data_list], + [ + MODE_8BIT_BYTE, + MODE_NUMBER, + MODE_8BIT_BYTE, + MODE_ALPHA_NUM, + MODE_8BIT_BYTE, + ], + ) + self.assertEqual(qr.version, 2) + + def test_optimize_short(self): + qr = qrcode.QRCode() + text = "A1abc1234567def1HELLOa" + qr.add_data(text, optimize=7) + qr.make() + self.assertEqual(len(qr.data_list), 3) + self.assertEqual( + [d.mode for d in qr.data_list], + [MODE_8BIT_BYTE, MODE_NUMBER, MODE_8BIT_BYTE], + ) + self.assertEqual(qr.version, 2) + + def test_optimize_longer_than_data(self): + qr = qrcode.QRCode() + text = "ABCDEFGHIJK" + qr.add_data(text, optimize=12) + self.assertEqual(len(qr.data_list), 1) + self.assertEqual(qr.data_list[0].mode, MODE_ALPHA_NUM) + + def test_optimize_size(self): + text = "A1abc12345123451234512345def1HELLOHELLOHELLOHELLOa" * 5 + + qr = qrcode.QRCode() + qr.add_data(text) + qr.make() + self.assertEqual(qr.version, 10) + + qr = qrcode.QRCode() + qr.add_data(text, optimize=0) + qr.make() + self.assertEqual(qr.version, 11) + + def test_qrdata_repr(self): + data = b"hello" + data_obj = qrcode.util.QRData(data) + self.assertEqual(repr(data_obj), repr(data)) + + def test_print_ascii_stdout(self): + qr = qrcode.QRCode() + with mock.patch("sys.stdout") as fake_stdout: + fake_stdout.isatty.return_value = None + self.assertRaises(OSError, qr.print_ascii, tty=True) + self.assertTrue(fake_stdout.isatty.called) + + def test_print_ascii(self): + qr = qrcode.QRCode(border=0) + f = io.StringIO() + qr.print_ascii(out=f) + printed = f.getvalue() + f.close() + expected = "\u2588\u2580\u2580\u2580\u2580\u2580\u2588" + self.assertEqual(printed[: len(expected)], expected) + + f = io.StringIO() + f.isatty = lambda: True + qr.print_ascii(out=f, tty=True) + printed = f.getvalue() + f.close() + expected = ( + "\x1b[48;5;232m\x1b[38;5;255m" + "\xa0\u2584\u2584\u2584\u2584\u2584\xa0" + ) + self.assertEqual(printed[: len(expected)], expected) + + def test_print_tty_stdout(self): + qr = qrcode.QRCode() + with mock.patch("sys.stdout") as fake_stdout: + fake_stdout.isatty.return_value = None + self.assertRaises(OSError, qr.print_tty) + self.assertTrue(fake_stdout.isatty.called) + + def test_print_tty(self): + qr = qrcode.QRCode() + f = io.StringIO() + f.isatty = lambda: True + qr.print_tty(out=f) + printed = f.getvalue() + f.close() + BOLD_WHITE_BG = "\x1b[1;47m" + BLACK_BG = "\x1b[40m" + WHITE_BLOCK = BOLD_WHITE_BG + " " + BLACK_BG + EOL = "\x1b[0m\n" + expected = ( + BOLD_WHITE_BG + " " * 23 + EOL + WHITE_BLOCK + " " * 7 + WHITE_BLOCK + ) + self.assertEqual(printed[: len(expected)], expected) + + def test_get_matrix(self): + qr = qrcode.QRCode(border=0) + qr.add_data("1") + self.assertEqual(qr.get_matrix(), qr.modules) + + def test_get_matrix_border(self): + qr = qrcode.QRCode(border=1) + qr.add_data("1") + matrix = [row[1:-1] for row in qr.get_matrix()[1:-1]] + self.assertEqual(matrix, qr.modules) + + def test_negative_size_at_construction(self): + self.assertRaises(ValueError, qrcode.QRCode, box_size=-1) + + def test_negative_size_at_usage(self): + qr = qrcode.QRCode() + qr.box_size = -1 + self.assertRaises(ValueError, qr.make_image) + + +class ShortcutTest(unittest.TestCase): + @unittest.skipIf(not pil_Image, "Requires PIL") + def runTest(self): + qrcode.make("image") diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_qrcode_svg.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_qrcode_svg.py new file mode 100644 index 00000000..74b5c553 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_qrcode_svg.py @@ -0,0 +1,60 @@ +import io +import os +import unittest +from tempfile import mkdtemp + +import qrcode +from qrcode.image import svg + +UNICODE_TEXT = "\u03b1\u03b2\u03b3" + + +class SvgImageWhite(svg.SvgImage): + background = "white" + + +class QRCodeSvgTests(unittest.TestCase): + def setUp(self): + self.tmpdir = mkdtemp() + + def tearDown(self): + os.rmdir(self.tmpdir) + + def test_render_svg(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=svg.SvgImage) + img.save(io.BytesIO()) + + def test_render_svg_path(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=svg.SvgPathImage) + img.save(io.BytesIO()) + + def test_render_svg_fragment(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=svg.SvgFragmentImage) + img.save(io.BytesIO()) + + def test_svg_string(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=svg.SvgFragmentImage) + file_like = io.BytesIO() + img.save(file_like) + file_like.seek(0) + assert file_like.read() in img.to_string() + + def test_render_svg_with_background(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=SvgImageWhite) + img.save(io.BytesIO()) + + def test_svg_circle_drawer(self): + qr = qrcode.QRCode() + qr.add_data(UNICODE_TEXT) + img = qr.make_image(image_factory=svg.SvgPathImage, module_drawer="circle") + img.save(io.BytesIO()) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_release.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_release.py new file mode 100644 index 00000000..39a267c8 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_release.py @@ -0,0 +1,40 @@ +import re +import builtins +import datetime +import unittest +from unittest import mock + +from qrcode.release import update_manpage + +OPEN = f"{builtins.__name__}.open" +DATA = 'test\n.TH "date" "version" "description"\nthis' + + +class UpdateManpageTests(unittest.TestCase): + @mock.patch(OPEN, new_callable=mock.mock_open, read_data=".TH invalid") + def test_invalid_data(self, mock_file): + update_manpage({"name": "qrcode", "new_version": "1.23"}) + mock_file.assert_called() + mock_file().write.assert_not_called() + + @mock.patch(OPEN, new_callable=mock.mock_open, read_data=DATA) + def test_not_qrcode(self, mock_file): + update_manpage({"name": "not-qrcode"}) + mock_file.assert_not_called() + + @mock.patch(OPEN, new_callable=mock.mock_open, read_data=DATA) + def test_no_change(self, mock_file): + update_manpage({"name": "qrcode", "new_version": "version"}) + mock_file.assert_called() + mock_file().write.assert_not_called() + + @mock.patch(OPEN, new_callable=mock.mock_open, read_data=DATA) + def test_change(self, mock_file): + update_manpage({"name": "qrcode", "new_version": "3.11"}) + expected = re.split(r"([^\n]*(?:\n|$))", DATA)[1::2] + expected[1] = ( + expected[1] + .replace("version", "3.11") + .replace("date", datetime.datetime.now().strftime("%-d %b %Y")) + ) + mock_file().write.has_calls([mock.call(line) for line in expected]) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_script.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_script.py new file mode 100644 index 00000000..4ae4ccbc --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_script.py @@ -0,0 +1,105 @@ +import io +import os +import sys +import unittest +from tempfile import mkdtemp +from unittest import mock + +from qrcode.compat.pil import Image +from qrcode.console_scripts import commas, main + + +def bad_read(): + raise UnicodeDecodeError("utf-8", b"0x80", 0, 1, "invalid start byte") + + +class ScriptTest(unittest.TestCase): + def setUp(self): + self.tmpdir = mkdtemp() + + def tearDown(self): + os.rmdir(self.tmpdir) + + @mock.patch("os.isatty", lambda *args: True) + @mock.patch("qrcode.main.QRCode.print_ascii") + def test_isatty(self, mock_print_ascii): + main(["testtext"]) + mock_print_ascii.assert_called_with(tty=True) + + @mock.patch("os.isatty", lambda *args: False) + @mock.patch("sys.stdout") + @unittest.skipIf(not Image, "Requires PIL") + def test_piped(self, mock_stdout): + main(["testtext"]) + + @mock.patch("os.isatty", lambda *args: True) + @mock.patch("qrcode.main.QRCode.print_ascii") + @mock.patch("sys.stdin") + def test_stdin(self, mock_stdin, mock_print_ascii): + mock_stdin.buffer.read.return_value = "testtext" + main([]) + self.assertTrue(mock_stdin.buffer.read.called) + mock_print_ascii.assert_called_with(tty=True) + + @mock.patch("os.isatty", lambda *args: True) + @mock.patch("qrcode.main.QRCode.print_ascii") + def test_stdin_py3_unicodedecodeerror(self, mock_print_ascii): + mock_stdin = mock.Mock(sys.stdin) + mock_stdin.buffer.read.return_value = "testtext" + mock_stdin.read.side_effect = bad_read + with mock.patch("sys.stdin", mock_stdin): + # sys.stdin.read() will raise an error... + self.assertRaises(UnicodeDecodeError, sys.stdin.read) + # ... but it won't be used now. + main([]) + mock_print_ascii.assert_called_with(tty=True) + + @mock.patch("os.isatty", lambda *args: True) + @mock.patch("qrcode.main.QRCode.print_ascii") + def test_optimize(self, mock_print_ascii): + main("testtext --optimize 0".split()) + + @mock.patch("sys.stdout") + def test_factory(self, mock_stdout): + main("testtext --factory svg".split()) + + @mock.patch("sys.stderr") + def test_bad_factory(self, mock_stderr): + self.assertRaises(SystemExit, main, "testtext --factory fish".split()) + + @mock.patch.object(sys, "argv", "qr testtext output".split()) + @unittest.skipIf(not Image, "Requires PIL") + def test_sys_argv(self): + main() + + @unittest.skipIf(not Image, "Requires PIL") + def test_output(self): + tmpfile = os.path.join(self.tmpdir, "test.png") + main(["testtext", "--output", tmpfile]) + os.remove(tmpfile) + + @mock.patch("sys.stderr", new_callable=io.StringIO) + @unittest.skipIf(not Image, "Requires PIL") + def test_factory_drawer_none(self, mock_stderr): + with self.assertRaises(SystemExit): + main("testtext --factory pil --factory-drawer nope".split()) + self.assertIn( + "The selected factory has no drawer aliases", mock_stderr.getvalue() + ) + + @mock.patch("sys.stderr", new_callable=io.StringIO) + def test_factory_drawer_bad(self, mock_stderr): + with self.assertRaises(SystemExit): + main("testtext --factory svg --factory-drawer sobad".split()) + self.assertIn("sobad factory drawer not found", mock_stderr.getvalue()) + + @mock.patch("sys.stderr", new_callable=io.StringIO) + def test_factory_drawer(self, mock_stderr): + main("testtext --factory svg --factory-drawer circle".split()) + + def test_commas(self): + self.assertEqual(commas([]), "") + self.assertEqual(commas(["A"]), "A") + self.assertEqual(commas("AB"), "A or B") + self.assertEqual(commas("ABC"), "A, B or C") + self.assertEqual(commas("ABC", joiner="and"), "A, B and C") diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_util.py b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_util.py new file mode 100644 index 00000000..1c98e425 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/tests/test_util.py @@ -0,0 +1,12 @@ +import unittest + +from qrcode import util + + +class UtilTests(unittest.TestCase): + def test_check_wrong_version(self): + with self.assertRaises(ValueError): + util.check_version(0) + + with self.assertRaises(ValueError): + util.check_version(41) diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/util.py b/Backend/venv/lib/python3.12/site-packages/qrcode/util.py new file mode 100644 index 00000000..8284003b --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/qrcode/util.py @@ -0,0 +1,586 @@ +import math +import re +from typing import List + +from qrcode import LUT, base, exceptions +from qrcode.base import RSBlock + +# QR encoding modes. +MODE_NUMBER = 1 << 0 +MODE_ALPHA_NUM = 1 << 1 +MODE_8BIT_BYTE = 1 << 2 +MODE_KANJI = 1 << 3 + +# Encoding mode sizes. +MODE_SIZE_SMALL = { + MODE_NUMBER: 10, + MODE_ALPHA_NUM: 9, + MODE_8BIT_BYTE: 8, + MODE_KANJI: 8, +} +MODE_SIZE_MEDIUM = { + MODE_NUMBER: 12, + MODE_ALPHA_NUM: 11, + MODE_8BIT_BYTE: 16, + MODE_KANJI: 10, +} +MODE_SIZE_LARGE = { + MODE_NUMBER: 14, + MODE_ALPHA_NUM: 13, + MODE_8BIT_BYTE: 16, + MODE_KANJI: 12, +} + +ALPHA_NUM = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" +RE_ALPHA_NUM = re.compile(b"^[" + re.escape(ALPHA_NUM) + rb"]*\Z") + +# The number of bits for numeric delimited data lengths. +NUMBER_LENGTH = {3: 10, 2: 7, 1: 4} + +PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], +] + +G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0) +G18 = ( + (1 << 12) + | (1 << 11) + | (1 << 10) + | (1 << 9) + | (1 << 8) + | (1 << 5) + | (1 << 2) + | (1 << 0) +) +G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) + +PAD0 = 0xEC +PAD1 = 0x11 + + +# Precompute bit count limits, indexed by error correction level and code size +def _data_count(block): + return block.data_count + + +BIT_LIMIT_TABLE = [ + [0] + + [ + 8 * sum(map(_data_count, base.rs_blocks(version, error_correction))) + for version in range(1, 41) + ] + for error_correction in range(4) +] + + +def BCH_type_info(data): + d = data << 10 + while BCH_digit(d) - BCH_digit(G15) >= 0: + d ^= G15 << (BCH_digit(d) - BCH_digit(G15)) + + return ((data << 10) | d) ^ G15_MASK + + +def BCH_type_number(data): + d = data << 12 + while BCH_digit(d) - BCH_digit(G18) >= 0: + d ^= G18 << (BCH_digit(d) - BCH_digit(G18)) + return (data << 12) | d + + +def BCH_digit(data): + digit = 0 + while data != 0: + digit += 1 + data >>= 1 + return digit + + +def pattern_position(version): + return PATTERN_POSITION_TABLE[version - 1] + + +def mask_func(pattern): + """ + Return the mask function for the given mask pattern. + """ + if pattern == 0: # 000 + return lambda i, j: (i + j) % 2 == 0 + if pattern == 1: # 001 + return lambda i, j: i % 2 == 0 + if pattern == 2: # 010 + return lambda i, j: j % 3 == 0 + if pattern == 3: # 011 + return lambda i, j: (i + j) % 3 == 0 + if pattern == 4: # 100 + return lambda i, j: (math.floor(i / 2) + math.floor(j / 3)) % 2 == 0 + if pattern == 5: # 101 + return lambda i, j: (i * j) % 2 + (i * j) % 3 == 0 + if pattern == 6: # 110 + return lambda i, j: ((i * j) % 2 + (i * j) % 3) % 2 == 0 + if pattern == 7: # 111 + return lambda i, j: ((i * j) % 3 + (i + j) % 2) % 2 == 0 + raise TypeError("Bad mask pattern: " + pattern) # pragma: no cover + + +def mode_sizes_for_version(version): + if version < 10: + return MODE_SIZE_SMALL + elif version < 27: + return MODE_SIZE_MEDIUM + else: + return MODE_SIZE_LARGE + + +def length_in_bits(mode, version): + if mode not in (MODE_NUMBER, MODE_ALPHA_NUM, MODE_8BIT_BYTE, MODE_KANJI): + raise TypeError(f"Invalid mode ({mode})") # pragma: no cover + + check_version(version) + + return mode_sizes_for_version(version)[mode] + + +def check_version(version): + if version < 1 or version > 40: + raise ValueError(f"Invalid version (was {version}, expected 1 to 40)") + + +def lost_point(modules): + modules_count = len(modules) + + lost_point = 0 + + lost_point = _lost_point_level1(modules, modules_count) + lost_point += _lost_point_level2(modules, modules_count) + lost_point += _lost_point_level3(modules, modules_count) + lost_point += _lost_point_level4(modules, modules_count) + + return lost_point + + +def _lost_point_level1(modules, modules_count): + lost_point = 0 + + modules_range = range(modules_count) + container = [0] * (modules_count + 1) + + for row in modules_range: + this_row = modules[row] + previous_color = this_row[0] + length = 0 + for col in modules_range: + if this_row[col] == previous_color: + length += 1 + else: + if length >= 5: + container[length] += 1 + length = 1 + previous_color = this_row[col] + if length >= 5: + container[length] += 1 + + for col in modules_range: + previous_color = modules[0][col] + length = 0 + for row in modules_range: + if modules[row][col] == previous_color: + length += 1 + else: + if length >= 5: + container[length] += 1 + length = 1 + previous_color = modules[row][col] + if length >= 5: + container[length] += 1 + + lost_point += sum( + container[each_length] * (each_length - 2) + for each_length in range(5, modules_count + 1) + ) + + return lost_point + + +def _lost_point_level2(modules, modules_count): + lost_point = 0 + + modules_range = range(modules_count - 1) + for row in modules_range: + this_row = modules[row] + next_row = modules[row + 1] + # use iter() and next() to skip next four-block. e.g. + # d a f if top-right a != b bottom-right, + # c b e then both abcd and abef won't lost any point. + modules_range_iter = iter(modules_range) + for col in modules_range_iter: + top_right = this_row[col + 1] + if top_right != next_row[col + 1]: + # reduce 33.3% of runtime via next(). + # None: raise nothing if there is no next item. + next(modules_range_iter, None) + elif top_right != this_row[col]: + continue + elif top_right != next_row[col]: + continue + else: + lost_point += 3 + + return lost_point + + +def _lost_point_level3(modules, modules_count): + # 1 : 1 : 3 : 1 : 1 ratio (dark:light:dark:light:dark) pattern in + # row/column, preceded or followed by light area 4 modules wide. From ISOIEC. + # pattern1: 10111010000 + # pattern2: 00001011101 + modules_range = range(modules_count) + modules_range_short = range(modules_count - 10) + lost_point = 0 + + for row in modules_range: + this_row = modules[row] + modules_range_short_iter = iter(modules_range_short) + col = 0 + for col in modules_range_short_iter: + if ( + not this_row[col + 1] + and this_row[col + 4] + and not this_row[col + 5] + and this_row[col + 6] + and not this_row[col + 9] + and ( + this_row[col + 0] + and this_row[col + 2] + and this_row[col + 3] + and not this_row[col + 7] + and not this_row[col + 8] + and not this_row[col + 10] + or not this_row[col + 0] + and not this_row[col + 2] + and not this_row[col + 3] + and this_row[col + 7] + and this_row[col + 8] + and this_row[col + 10] + ) + ): + lost_point += 40 + # horspool algorithm. + # if this_row[col + 10]: + # pattern1 shift 4, pattern2 shift 2. So min=2. + # else: + # pattern1 shift 1, pattern2 shift 1. So min=1. + if this_row[col + 10]: + next(modules_range_short_iter, None) + + for col in modules_range: + modules_range_short_iter = iter(modules_range_short) + row = 0 + for row in modules_range_short_iter: + if ( + not modules[row + 1][col] + and modules[row + 4][col] + and not modules[row + 5][col] + and modules[row + 6][col] + and not modules[row + 9][col] + and ( + modules[row + 0][col] + and modules[row + 2][col] + and modules[row + 3][col] + and not modules[row + 7][col] + and not modules[row + 8][col] + and not modules[row + 10][col] + or not modules[row + 0][col] + and not modules[row + 2][col] + and not modules[row + 3][col] + and modules[row + 7][col] + and modules[row + 8][col] + and modules[row + 10][col] + ) + ): + lost_point += 40 + if modules[row + 10][col]: + next(modules_range_short_iter, None) + + return lost_point + + +def _lost_point_level4(modules, modules_count): + dark_count = sum(map(sum, modules)) + percent = float(dark_count) / (modules_count**2) + # Every 5% departure from 50%, rating++ + rating = int(abs(percent * 100 - 50) / 5) + return rating * 10 + + +def optimal_data_chunks(data, minimum=4): + """ + An iterator returning QRData chunks optimized to the data content. + + :param minimum: The minimum number of bytes in a row to split as a chunk. + """ + data = to_bytestring(data) + num_pattern = rb"\d" + alpha_pattern = b"[" + re.escape(ALPHA_NUM) + b"]" + if len(data) <= minimum: + num_pattern = re.compile(b"^" + num_pattern + b"+$") + alpha_pattern = re.compile(b"^" + alpha_pattern + b"+$") + else: + re_repeat = b"{" + str(minimum).encode("ascii") + b",}" + num_pattern = re.compile(num_pattern + re_repeat) + alpha_pattern = re.compile(alpha_pattern + re_repeat) + num_bits = _optimal_split(data, num_pattern) + for is_num, chunk in num_bits: + if is_num: + yield QRData(chunk, mode=MODE_NUMBER, check_data=False) + else: + for is_alpha, sub_chunk in _optimal_split(chunk, alpha_pattern): + mode = MODE_ALPHA_NUM if is_alpha else MODE_8BIT_BYTE + yield QRData(sub_chunk, mode=mode, check_data=False) + + +def _optimal_split(data, pattern): + while data: + match = re.search(pattern, data) + if not match: + break + start, end = match.start(), match.end() + if start: + yield False, data[:start] + yield True, data[start:end] + data = data[end:] + if data: + yield False, data + + +def to_bytestring(data): + """ + Convert data to a (utf-8 encoded) byte-string if it isn't a byte-string + already. + """ + if not isinstance(data, bytes): + data = str(data).encode("utf-8") + return data + + +def optimal_mode(data): + """ + Calculate the optimal mode for this chunk of data. + """ + if data.isdigit(): + return MODE_NUMBER + if RE_ALPHA_NUM.match(data): + return MODE_ALPHA_NUM + return MODE_8BIT_BYTE + + +class QRData: + """ + Data held in a QR compatible format. + + Doesn't currently handle KANJI. + """ + + def __init__(self, data, mode=None, check_data=True): + """ + If ``mode`` isn't provided, the most compact QR data type possible is + chosen. + """ + if check_data: + data = to_bytestring(data) + + if mode is None: + self.mode = optimal_mode(data) + else: + self.mode = mode + if mode not in (MODE_NUMBER, MODE_ALPHA_NUM, MODE_8BIT_BYTE): + raise TypeError(f"Invalid mode ({mode})") # pragma: no cover + if check_data and mode < optimal_mode(data): # pragma: no cover + raise ValueError(f"Provided data can not be represented in mode {mode}") + + self.data = data + + def __len__(self): + return len(self.data) + + def write(self, buffer): + if self.mode == MODE_NUMBER: + for i in range(0, len(self.data), 3): + chars = self.data[i : i + 3] + bit_length = NUMBER_LENGTH[len(chars)] + buffer.put(int(chars), bit_length) + elif self.mode == MODE_ALPHA_NUM: + for i in range(0, len(self.data), 2): + chars = self.data[i : i + 2] + if len(chars) > 1: + buffer.put( + ALPHA_NUM.find(chars[0]) * 45 + ALPHA_NUM.find(chars[1]), 11 + ) + else: + buffer.put(ALPHA_NUM.find(chars), 6) + else: + # Iterating a bytestring in Python 3 returns an integer, + # no need to ord(). + data = self.data + for c in data: + buffer.put(c, 8) + + def __repr__(self): + return repr(self.data) + + +class BitBuffer: + def __init__(self): + self.buffer: List[int] = [] + self.length = 0 + + def __repr__(self): + return ".".join([str(n) for n in self.buffer]) + + def get(self, index): + buf_index = math.floor(index / 8) + return ((self.buffer[buf_index] >> (7 - index % 8)) & 1) == 1 + + def put(self, num, length): + for i in range(length): + self.put_bit(((num >> (length - i - 1)) & 1) == 1) + + def __len__(self): + return self.length + + def put_bit(self, bit): + buf_index = self.length // 8 + if len(self.buffer) <= buf_index: + self.buffer.append(0) + if bit: + self.buffer[buf_index] |= 0x80 >> (self.length % 8) + self.length += 1 + + +def create_bytes(buffer: BitBuffer, rs_blocks: List[RSBlock]): + offset = 0 + + maxDcCount = 0 + maxEcCount = 0 + + dcdata: List[List[int]] = [] + ecdata: List[List[int]] = [] + + for rs_block in rs_blocks: + dcCount = rs_block.data_count + ecCount = rs_block.total_count - dcCount + + maxDcCount = max(maxDcCount, dcCount) + maxEcCount = max(maxEcCount, ecCount) + + current_dc = [0xFF & buffer.buffer[i + offset] for i in range(dcCount)] + offset += dcCount + + # Get error correction polynomial. + if ecCount in LUT.rsPoly_LUT: + rsPoly = base.Polynomial(LUT.rsPoly_LUT[ecCount], 0) + else: + rsPoly = base.Polynomial([1], 0) + for i in range(ecCount): + rsPoly = rsPoly * base.Polynomial([1, base.gexp(i)], 0) + + rawPoly = base.Polynomial(current_dc, len(rsPoly) - 1) + + modPoly = rawPoly % rsPoly + current_ec = [] + mod_offset = len(modPoly) - ecCount + for i in range(ecCount): + modIndex = i + mod_offset + current_ec.append(modPoly[modIndex] if (modIndex >= 0) else 0) + + dcdata.append(current_dc) + ecdata.append(current_ec) + + data = [] + for i in range(maxDcCount): + for dc in dcdata: + if i < len(dc): + data.append(dc[i]) + for i in range(maxEcCount): + for ec in ecdata: + if i < len(ec): + data.append(ec[i]) + + return data + + +def create_data(version, error_correction, data_list): + + buffer = BitBuffer() + for data in data_list: + buffer.put(data.mode, 4) + buffer.put(len(data), length_in_bits(data.mode, version)) + data.write(buffer) + + # Calculate the maximum number of bits for the given version. + rs_blocks = base.rs_blocks(version, error_correction) + bit_limit = sum(block.data_count * 8 for block in rs_blocks) + if len(buffer) > bit_limit: + raise exceptions.DataOverflowError( + "Code length overflow. Data size (%s) > size available (%s)" + % (len(buffer), bit_limit) + ) + + # Terminate the bits (add up to four 0s). + for _ in range(min(bit_limit - len(buffer), 4)): + buffer.put_bit(False) + + # Delimit the string into 8-bit words, padding with 0s if necessary. + delimit = len(buffer) % 8 + if delimit: + for _ in range(8 - delimit): + buffer.put_bit(False) + + # Add special alternating padding bitstrings until buffer is full. + bytes_to_fill = (bit_limit - len(buffer)) // 8 + for i in range(bytes_to_fill): + if i % 2 == 0: + buffer.put(PAD0, 8) + else: + buffer.put(PAD1, 8) + + return create_bytes(buffer, rs_blocks) diff --git a/Backend/venv/share/man/man1/qr.1 b/Backend/venv/share/man/man1/qr.1 new file mode 100644 index 00000000..71be0892 --- /dev/null +++ b/Backend/venv/share/man/man1/qr.1 @@ -0,0 +1,50 @@ +.\" Manpage for qr +.TH QR 1 "6 Feb 2023" "7.4.2" "Python QR tool" +.SH NAME +qr \- script to create QR codes at the command line +.SH SYNOPSIS +qr [\-\-help] [\-\-factory=FACTORY] [\-\-optimize=OPTIMIZE] [\-\-error\-correction=LEVEL] [data] +.SH DESCRIPTION +This script uses the python qrcode module. It can take data from stdin or from the commandline and generate a QR code. +Normally it will output the QR code as ascii art to the terminal. If the output is piped to a file, it will output the image (default type of PNG). +.SH OPTIONS +.PP +\fB\ \-h, \-\-help\fR +.RS 4 +Show a help message. +.RE + +.PP +\fB\ \-\-factory=FACTORY\fR +.RS 4 +Full python path to the image factory class to create the +image with. You can use the following shortcuts to the +built-in image factory classes: pil (default), png ( +default if pillow is not installed), svg, svg-fragment, +svg-path. +.RE + +.PP +\fB\ \-\-optimize=OPTIMIZE\fR +.RS 4 +Optimize the data by looking for chunks of at least this +many characters that could use a more efficient encoding +method. Use 0 to turn off chunk optimization. +.RE + +.PP +\fB\ \-\-error\-correction=LEVEL\fR +.RS 4 +The error correction level to use. Choices are L (7%), +M (15%, default), Q (25%), and H (30%). +.RE + +.PP +\fB\ data\fR +.RS 4 +The data from which the QR code will be generated. +.RE + +.SH SEE ALSO +https://github.com/lincolnloop/python-qrcode/ + diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 53b9c52f..b9d3700b 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@hookform/resolvers": "^3.3.2", + "@paypal/react-paypal-js": "^8.1.3", "@stripe/react-stripe-js": "^2.9.0", "@stripe/stripe-js": "^2.4.0", "@types/react-datepicker": "^6.2.0", @@ -1103,6 +1104,38 @@ "node": ">= 8" } }, + "node_modules/@paypal/paypal-js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@paypal/paypal-js/-/paypal-js-9.0.1.tgz", + "integrity": "sha512-6A5hkyYBSuloO7rPwnoPoy/0PPNH0pZZ1TBSTuEaSVqJ9h9t13PiP1ZarffirsO/e5t4hKjUmUORjgKV9sTMLw==", + "license": "Apache-2.0", + "dependencies": { + "promise-polyfill": "^8.3.0" + } + }, + "node_modules/@paypal/react-paypal-js": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/@paypal/react-paypal-js/-/react-paypal-js-8.9.2.tgz", + "integrity": "sha512-z1GoA7KAkhFCSmpIsRxe9aseXRvfOMgF6vCJ2Mym0VOSYJm8bZSC3Ui4SjONnglfV8S4P8djpe5QB7FtKsDXrQ==", + "license": "Apache-2.0", + "dependencies": { + "@paypal/paypal-js": "^9.0.0", + "@paypal/sdk-constants": "^1.0.122" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/@paypal/sdk-constants": { + "version": "1.0.157", + "resolved": "https://registry.npmjs.org/@paypal/sdk-constants/-/sdk-constants-1.0.157.tgz", + "integrity": "sha512-BjxWT9rK6dM1AOffSpvHYY47/8BY775jgEYYiwH6eL4YaqU5Epcw7zOtwQ8L4UaEn4FCAjZ2EWxaS83dCN7SpA==", + "license": "Apache-2.0", + "dependencies": { + "hi-base32": "^0.5.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3140,6 +3173,12 @@ "node": ">= 0.4" } }, + "node_modules/hi-base32": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", + "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4012,6 +4051,12 @@ "node": ">= 0.8.0" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index a2d8b81e..07ee193f 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -23,6 +23,7 @@ "react-hook-form": "^7.48.2", "react-router-dom": "^6.20.0", "react-toastify": "^9.1.3", + "@paypal/react-paypal-js": "^8.1.3", "yup": "^1.3.3", "zustand": "^4.4.7" }, diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx index 96973d43..c2feaa6f 100644 --- a/Frontend/src/App.tsx +++ b/Frontend/src/App.tsx @@ -45,6 +45,8 @@ const DepositPaymentPage = lazy(() => import('./pages/customer/DepositPaymentPag const FullPaymentPage = lazy(() => import('./pages/customer/FullPaymentPage')); const PaymentConfirmationPage = lazy(() => import('./pages/customer/PaymentConfirmationPage')); const PaymentResultPage = lazy(() => import('./pages/customer/PaymentResultPage')); +const PayPalReturnPage = lazy(() => import('./pages/customer/PayPalReturnPage')); +const PayPalCancelPage = lazy(() => import('./pages/customer/PayPalCancelPage')); const InvoicePage = lazy(() => import('./pages/customer/InvoicePage')); const ProfilePage = lazy(() => import('./pages/customer/ProfilePage')); const AboutPage = lazy(() => import('./pages/AboutPage')); @@ -163,6 +165,14 @@ function App() { path="payment-result" element={} /> + } + /> + } + /> = ({ > {userInfo?.avatar ? ( {userInfo.name} = ({ const [isMobile, setIsMobile] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false); const location = useLocation(); + const navigate = useNavigate(); + const { logout } = useAuthStore(); + + const handleLogout = async () => { + try { + await logout(); + navigate('/login'); + if (isMobile) { + setIsMobileOpen(false); + } + } catch (error) { + console.error('Logout error:', error); + } + }; // Check if mobile on mount and resize useEffect(() => { @@ -246,6 +262,33 @@ const SidebarAdmin: React.FC = ({ + {/* Logout Button */} +
+ +
+ {/* Luxury Sidebar Footer */}
{(!isCollapsed || isMobile) ? ( diff --git a/Frontend/src/components/payments/PayPalPaymentWrapper.tsx b/Frontend/src/components/payments/PayPalPaymentWrapper.tsx new file mode 100644 index 00000000..696d29ac --- /dev/null +++ b/Frontend/src/components/payments/PayPalPaymentWrapper.tsx @@ -0,0 +1,161 @@ +import React, { useState, useEffect } from 'react'; +import { createPayPalOrder } from '../../services/api/paymentService'; +import { Loader2, AlertCircle } from 'lucide-react'; + +interface PayPalPaymentWrapperProps { + bookingId: number; + amount: number; + currency?: string; + onError?: (error: string) => void; +} + +const PayPalPaymentWrapper: React.FC = ({ + bookingId, + amount, + currency = 'USD', + onError, +}) => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [approvalUrl, setApprovalUrl] = useState(null); + + // Initialize PayPal order + useEffect(() => { + const initializePayPal = async () => { + try { + setLoading(true); + setError(null); + + // Get current URL for return/cancel URLs + const currentUrl = window.location.origin; + const returnUrl = `${currentUrl}/payment/paypal/return?bookingId=${bookingId}`; + const cancelUrl = `${currentUrl}/payment/paypal/cancel?bookingId=${bookingId}`; + + const response = await createPayPalOrder( + bookingId, + amount, + currency, + returnUrl, + cancelUrl + ); + + if (response.success && response.data) { + const { approval_url } = response.data; + + if (!approval_url) { + throw new Error('Approval URL not received from server'); + } + + setApprovalUrl(approval_url); + } else { + throw new Error(response.message || 'Failed to initialize PayPal payment'); + } + } catch (err: any) { + console.error('Error initializing PayPal:', err); + const errorMessage = err.response?.data?.message || err.message || 'Failed to initialize PayPal payment'; + setError(errorMessage); + if (onError) { + onError(errorMessage); + } + } finally { + setLoading(false); + } + }; + + initializePayPal(); + }, [bookingId, amount, currency, onError]); + + const handlePayPalClick = () => { + if (approvalUrl) { + // Redirect to PayPal approval page + window.location.href = approvalUrl; + } + }; + + if (loading) { + return ( +
+ + Initializing PayPal payment... +
+ ); + } + + if (error) { + return ( +
+
+ +
+

+ Payment Initialization Failed +

+

+ {error || 'Unable to initialize PayPal payment. Please try again.'} +

+
+
+
+ ); + } + + if (!approvalUrl) { + return ( +
+ + Loading PayPal... +
+ ); + } + + return ( +
+
+
+ + + +
+

+ Complete Payment with PayPal +

+

+ You will be redirected to PayPal to securely complete your payment of{' '} + + {new Intl.NumberFormat('en-US', { + style: 'currency', + currency: currency, + }).format(amount)} + +

+ +

+ Secure payment powered by PayPal +

+
+
+ ); +}; + +export default PayPalPaymentWrapper; + diff --git a/Frontend/src/components/payments/StripePaymentWrapper.tsx b/Frontend/src/components/payments/StripePaymentWrapper.tsx index 997e1a29..81715540 100644 --- a/Frontend/src/components/payments/StripePaymentWrapper.tsx +++ b/Frontend/src/components/payments/StripePaymentWrapper.tsx @@ -4,7 +4,6 @@ import { Elements } from '@stripe/react-stripe-js'; import StripePaymentForm from './StripePaymentForm'; import { createStripePaymentIntent, confirmStripePayment } from '../../services/api/paymentService'; import { Loader2, AlertCircle } from 'lucide-react'; -import Loading from '../common/Loading'; interface StripePaymentWrapperProps { bookingId: number; diff --git a/Frontend/src/pages/admin/BusinessDashboardPage.tsx b/Frontend/src/pages/admin/BusinessDashboardPage.tsx index 337158a1..b5ca3cad 100644 --- a/Frontend/src/pages/admin/BusinessDashboardPage.tsx +++ b/Frontend/src/pages/admin/BusinessDashboardPage.tsx @@ -199,6 +199,7 @@ const BusinessDashboardPage: React.FC = () => { cash: { bg: 'bg-gradient-to-r from-emerald-50 to-green-50', text: 'text-emerald-800', label: 'Cash', border: 'border-emerald-200' }, bank_transfer: { bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', text: 'text-blue-800', label: 'Bank transfer', border: 'border-blue-200' }, stripe: { bg: 'bg-gradient-to-r from-indigo-50 to-purple-50', text: 'text-indigo-800', label: 'Stripe', border: 'border-indigo-200' }, + paypal: { bg: 'bg-gradient-to-r from-blue-50 to-cyan-50', text: 'text-blue-800', label: 'PayPal', border: 'border-blue-200' }, credit_card: { bg: 'bg-gradient-to-r from-purple-50 to-pink-50', text: 'text-purple-800', label: 'Credit card', border: 'border-purple-200' }, }; const badge = badges[method] || badges.cash; diff --git a/Frontend/src/pages/admin/DashboardPage.tsx b/Frontend/src/pages/admin/DashboardPage.tsx index aeac6fca..a38ca780 100644 --- a/Frontend/src/pages/admin/DashboardPage.tsx +++ b/Frontend/src/pages/admin/DashboardPage.tsx @@ -7,7 +7,8 @@ import { TrendingUp, RefreshCw, TrendingDown, - CreditCard + CreditCard, + LogOut } from 'lucide-react'; import { reportService, ReportData, paymentService, Payment } from '../../services/api'; import { toast } from 'react-toastify'; @@ -17,16 +18,27 @@ import { formatDate } from '../../utils/format'; import { useFormatCurrency } from '../../hooks/useFormatCurrency'; import { useAsync } from '../../hooks/useAsync'; import { useNavigate } from 'react-router-dom'; +import useAuthStore from '../../store/useAuthStore'; const DashboardPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const navigate = useNavigate(); + const { logout } = useAuthStore(); const [dateRange, setDateRange] = useState({ from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], to: new Date().toISOString().split('T')[0], }); const [recentPayments, setRecentPayments] = useState([]); const [loadingPayments, setLoadingPayments] = useState(false); + + const handleLogout = async () => { + try { + await logout(); + navigate('/login'); + } catch (error) { + console.error('Logout error:', error); + } + }; const fetchDashboardData = async () => { const response = await reportService.getReports({ @@ -91,6 +103,8 @@ const DashboardPage: React.FC = () => { case 'stripe': case 'credit_card': return 'Card'; + case 'paypal': + return 'PayPal'; case 'bank_transfer': return 'Bank Transfer'; case 'cash': @@ -133,7 +147,7 @@ const DashboardPage: React.FC = () => {

Hotel operations overview and analytics

- {/* Date Range Filter */} + {/* Date Range Filter & Actions */}
{ className="px-4 py-2.5 bg-white border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100 transition-all duration-200 text-slate-700 font-medium shadow-sm hover:shadow-md" />
- +
+ + +
diff --git a/Frontend/src/pages/admin/PaymentManagementPage.tsx b/Frontend/src/pages/admin/PaymentManagementPage.tsx index b4f7cea7..fcba12e7 100644 --- a/Frontend/src/pages/admin/PaymentManagementPage.tsx +++ b/Frontend/src/pages/admin/PaymentManagementPage.tsx @@ -70,6 +70,12 @@ const PaymentManagementPage: React.FC = () => { label: 'Stripe', border: 'border-indigo-200' }, + paypal: { + bg: 'bg-gradient-to-r from-blue-50 to-cyan-50', + text: 'text-blue-800', + label: 'PayPal', + border: 'border-blue-200' + }, credit_card: { bg: 'bg-gradient-to-r from-purple-50 to-pink-50', text: 'text-purple-800', diff --git a/Frontend/src/pages/admin/SettingsPage.tsx b/Frontend/src/pages/admin/SettingsPage.tsx index 0c051c7d..81d7ac15 100644 --- a/Frontend/src/pages/admin/SettingsPage.tsx +++ b/Frontend/src/pages/admin/SettingsPage.tsx @@ -29,6 +29,8 @@ import adminPrivacyService, { import systemSettingsService, { StripeSettingsResponse, UpdateStripeSettingsRequest, + PayPalSettingsResponse, + UpdatePayPalSettingsRequest, SmtpSettingsResponse, UpdateSmtpSettingsRequest, CompanySettingsResponse, @@ -71,6 +73,15 @@ const SettingsPage: React.FC = () => { const [showSecretKey, setShowSecretKey] = useState(false); const [showWebhookSecret, setShowWebhookSecret] = useState(false); + // PayPal Settings State + const [paypalSettings, setPaypalSettings] = useState(null); + const [paypalFormData, setPaypalFormData] = useState({ + paypal_client_id: '', + paypal_client_secret: '', + paypal_mode: 'sandbox', + }); + const [showPayPalSecret, setShowPayPalSecret] = useState(false); + // SMTP Settings State const [smtpSettings, setSmtpSettings] = useState(null); const [smtpFormData, setSmtpFormData] = useState({ @@ -144,11 +155,12 @@ const SettingsPage: React.FC = () => { const loadAllSettings = async () => { try { setLoading(true); - const [policyRes, integrationRes, currencyRes, stripeRes] = await Promise.all([ + const [policyRes, integrationRes, currencyRes, stripeRes, paypalRes] = await Promise.all([ adminPrivacyService.getCookiePolicy(), adminPrivacyService.getIntegrations(), systemSettingsService.getPlatformCurrency(), systemSettingsService.getStripeSettings(), + systemSettingsService.getPayPalSettings(), ]); setPolicy(policyRes.data); @@ -166,6 +178,12 @@ const SettingsPage: React.FC = () => { stripe_publishable_key: stripeRes.data.stripe_publishable_key || '', stripe_webhook_secret: '', }); + setPaypalSettings(paypalRes.data); + setPaypalFormData({ + paypal_client_id: '', + paypal_client_secret: '', + paypal_mode: paypalRes.data.paypal_mode || 'sandbox', + }); } catch (error: any) { toast.error(error.message || 'Failed to load settings'); } finally { @@ -307,6 +325,45 @@ const SettingsPage: React.FC = () => { } }; + // PayPal Settings Handlers + const handleSavePayPal = async () => { + try { + setSaving(true); + const updateData: UpdatePayPalSettingsRequest = {}; + + if (paypalFormData.paypal_client_id && paypalFormData.paypal_client_id.trim()) { + updateData.paypal_client_id = paypalFormData.paypal_client_id.trim(); + } + + if (paypalFormData.paypal_client_secret && paypalFormData.paypal_client_secret.trim()) { + updateData.paypal_client_secret = paypalFormData.paypal_client_secret.trim(); + } + + if (paypalFormData.paypal_mode) { + updateData.paypal_mode = paypalFormData.paypal_mode; + } + + await systemSettingsService.updatePayPalSettings(updateData); + await loadAllSettings(); + + setPaypalFormData({ + ...paypalFormData, + paypal_client_id: '', + paypal_client_secret: '', + }); + + toast.success('PayPal settings updated successfully'); + } catch (error: any) { + toast.error( + error.response?.data?.message || + error.response?.data?.detail || + 'Failed to update PayPal settings' + ); + } finally { + setSaving(false); + } + }; + // SMTP Settings Handlers const handleSaveSmtp = async () => { try { @@ -1305,6 +1362,180 @@ const SettingsPage: React.FC = () => { + + {/* PayPal Payment Settings Section */} +
+
+
+
+
+ +
+

PayPal Payment Settings

+
+

+ Configure your PayPal account credentials to enable PayPal payments. All PayPal payments will be processed through your PayPal account. +

+
+ +
+ + {/* Info Card */} +
+
+
+
+
+ +
+
+
+

+ How PayPal payments work +

+

+ PayPal handles all PayPal payments securely. You need to provide your PayPal API credentials from your PayPal Developer Dashboard. The client ID and client secret are used to process payments. You can use sandbox mode for testing or live mode for production. +

+
+

+ Note: Leave fields empty to keep existing values. Only enter new values when you want to update them. +

+
+
+
+
+ + {/* PayPal Settings Form */} +
+
+
+ +
+
+

PayPal API Credentials

+

+ Get these credentials from your{' '} + + PayPal Developer Dashboard + +

+
+
+ +
+ {/* Client ID */} +
+ + + setPaypalFormData({ ...paypalFormData, paypal_client_id: e.target.value }) + } + placeholder={ + paypalSettings?.has_client_id + ? `Current: ${paypalSettings.paypal_client_id || '****'}` + : 'Client ID from PayPal Dashboard' + } + className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm" + /> +
+ {paypalSettings?.has_client_id && ( + + + Currently configured + + )} +
+
+ + {/* Client Secret */} +
+ +
+ + setPaypalFormData({ ...paypalFormData, paypal_client_secret: e.target.value }) + } + placeholder={ + paypalSettings?.has_client_secret + ? `Current: ${paypalSettings.paypal_client_secret_masked || '****'}` + : 'Client Secret from PayPal Dashboard' + } + className="w-full px-4 py-3.5 pr-12 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm font-mono" + /> + +
+
+ {paypalSettings?.has_client_secret && ( + + + Currently configured + + )} +
+
+ + {/* Mode */} +
+ + +
+

Use sandbox mode for testing with test credentials, or live mode for production payments.

+
+
+
+
+
)} diff --git a/Frontend/src/pages/auth/ForgotPasswordPage.tsx b/Frontend/src/pages/auth/ForgotPasswordPage.tsx index 714e1d0f..6d71370f 100644 --- a/Frontend/src/pages/auth/ForgotPasswordPage.tsx +++ b/Frontend/src/pages/auth/ForgotPasswordPage.tsx @@ -9,6 +9,7 @@ import { Loader2, CheckCircle, Hotel, + Home, } from 'lucide-react'; import useAuthStore from '../../store/useAuthStore'; import { @@ -29,6 +30,12 @@ const ForgotPasswordPage: React.FC = () => { const supportEmail = settings.company_email || 'support@hotel.com'; const supportPhone = settings.company_phone || '1900-xxxx'; + // Update page title + useEffect(() => { + const companyName = settings.company_name || 'Luxury Hotel'; + document.title = `Forgot Password - ${companyName}`; + }, [settings.company_name]); + // React Hook Form setup const { register, @@ -60,65 +67,82 @@ const ForgotPasswordPage: React.FC = () => {
-
+
{/* Header */}
-
-
- -
+
+ {settings.company_logo_url ? ( + {settings.company_name + ) : ( +
+ +
+ )}
-

+ {settings.company_tagline && ( +

+ {settings.company_tagline} +

+ )} +

Forgot Password?

-

- Enter your email to receive a password reset link +

+ Enter your email to receive a password reset link for {settings.company_name || 'Luxury Hotel'}

{/* Form Container */} -
+
{isSuccess ? ( // Success State -
+
-
+

Email Sent!

-

+

We have sent a password reset link to

-

+

{submittedEmail}

-

+

Note:

  • Link is valid for 1 hour
  • @@ -129,32 +153,32 @@ const ForgotPasswordPage: React.FC = () => {
-
+
Back to Login
+ + {/* Back to Home Button */} +
+ + + + Back to Homepage + +
) : ( // Form State
{/* Error Message */} {error && (
{error}
@@ -189,8 +225,8 @@ const ForgotPasswordPage: React.FC = () => {
@@ -201,7 +237,7 @@ const ForgotPasswordPage: React.FC = () => { pointer-events-none" >
{ type="email" autoComplete="email" autoFocus - className={`block w-full pl-10 pr-3 - py-3 border rounded-lg + className={`block w-full pl-9 sm:pl-10 pr-3 + py-2.5 sm:py-3 border rounded-lg focus:outline-none focus:ring-2 - transition-colors + transition-colors text-sm sm:text-base ${ errors.email ? 'border-red-300 ' + @@ -225,7 +261,7 @@ const ForgotPasswordPage: React.FC = () => { />
{errors.email && ( -

+

{errors.email.message}

)} @@ -236,9 +272,9 @@ const ForgotPasswordPage: React.FC = () => { type="submit" disabled={isLoading} className="w-full flex items-center - justify-center py-3 px-4 border + justify-center py-2.5 sm:py-3 px-4 border border-transparent rounded-lg - shadow-sm text-sm font-medium + shadow-sm text-xs sm:text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 @@ -251,13 +287,13 @@ const ForgotPasswordPage: React.FC = () => { <> Processing... ) : ( <> - + Send Reset Link )} @@ -268,22 +304,34 @@ const ForgotPasswordPage: React.FC = () => { Back to Login
+ + {/* Back to Home Button */} +
+ + + + Back to Homepage + +
)}
{/* Footer Info */} {!isSuccess && ( -
+

Don't have an account?{' '} { {/* Help Section */}

Need Help?

-

+

If you're having trouble resetting your password, please contact our support team via email{' '} {supportEmail} diff --git a/Frontend/src/pages/auth/LoginPage.tsx b/Frontend/src/pages/auth/LoginPage.tsx index add1648c..9640205f 100644 --- a/Frontend/src/pages/auth/LoginPage.tsx +++ b/Frontend/src/pages/auth/LoginPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Link, useNavigate, useLocation } from 'react-router-dom'; @@ -9,21 +9,56 @@ import { Loader2, Mail, Lock, - Hotel + Hotel, + Home, + Shield, + ArrowLeft } from 'lucide-react'; import useAuthStore from '../../store/useAuthStore'; import { loginSchema, LoginFormData } from '../../utils/validationSchemas'; +import { useCompanySettings } from '../../contexts/CompanySettingsContext'; +import * as yup from 'yup'; + +const mfaTokenSchema = yup.object().shape({ + mfaToken: yup + .string() + .required('MFA token is required') + .min(6, 'MFA token must be 6 digits') + .max(8, 'MFA token must be 6-8 characters') + .matches(/^\d+$|^[A-Z0-9]{8}$/, 'Invalid token format'), +}); + +type MFATokenFormData = yup.InferType; const LoginPage: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); - const { login, isLoading, error, clearError } = + const { login, verifyMFA, isLoading, error, clearError, requiresMFA, clearMFA } = useAuthStore(); + const { settings } = useCompanySettings(); const [showPassword, setShowPassword] = useState(false); + + // MFA form setup + const { + register: registerMFA, + handleSubmit: handleSubmitMFA, + formState: { errors: mfaErrors }, + } = useForm({ + resolver: yupResolver(mfaTokenSchema), + defaultValues: { + mfaToken: '', + }, + }); + + // Update page title + useEffect(() => { + const companyName = settings.company_name || 'Luxury Hotel'; + document.title = requiresMFA ? `Verify Identity - ${companyName}` : `Login - ${companyName}`; + }, [settings.company_name, requiresMFA]); // React Hook Form setup const { @@ -49,231 +84,384 @@ const LoginPage: React.FC = () => { rememberMe: data.rememberMe, }); - // Redirect to previous page or dashboard - const from = location.state?.from?.pathname || - '/dashboard'; - navigate(from, { replace: true }); + // If MFA is required, don't redirect yet + if (!requiresMFA) { + // Redirect to previous page or dashboard + const from = location.state?.from?.pathname || + '/dashboard'; + navigate(from, { replace: true }); + } } catch (error) { // Error has been handled in store console.error('Login error:', error); } }; + // Handle MFA verification + const onSubmitMFA = async (data: MFATokenFormData) => { + try { + clearError(); + await verifyMFA(data.mfaToken); + + // Redirect to previous page or dashboard + const from = location.state?.from?.pathname || + '/dashboard'; + navigate(from, { replace: true }); + } catch (error) { + // Error has been handled in store + console.error('MFA verification error:', error); + } + }; + + // Handle back to login + const handleBackToLogin = () => { + clearMFA(); + clearError(); + }; + return (

{/* Luxury background pattern */}
-
+
{/* Header */}
-
-
- -
-
-
-

- Welcome Back -

-

- Sign in to Luxury Hotel -

-
- - {/* Login Form */} -
-
- {/* Error Message */} - {error && ( -
- {error} +
+ {settings.company_logo_url ? ( + {settings.company_name + ) : ( +
+ +
)} - - {/* Email Field */} -
- -
-
- -
- -
- {errors.email && ( -

- {errors.email.message} -

- )} -
- - {/* Password Field */} -
- -
-
- -
- - -
- {errors.password && ( -

- {errors.password.message} -

- )} -
- - {/* Remember Me & Forgot Password */} -
-
- - -
- - - Forgot password? - -
- - {/* Submit Button */} - - - - {/* Register Link */} -
-

- Don't have an account?{' '} - - Register now - -

-
- - {/* Footer Info */} -
-

- By logging in, you agree to our{' '} - - Terms of Service - {' '} - and{' '} - - Privacy Policy - + {settings.company_tagline && ( +

+ {settings.company_tagline} +

+ )} +

+ {requiresMFA ? 'Verify Your Identity' : 'Welcome Back'} +

+

+ {requiresMFA + ? 'Enter the 6-digit code from your authenticator app' + : `Sign in to ${settings.company_name || 'Luxury Hotel'}`}

+ + {requiresMFA ? ( + /* MFA Verification Form */ +
+
+ {/* Error Message */} + {error && ( +
+ {error} +
+ )} + + {/* MFA Token Field */} +
+ +
+
+ +
+ +
+ {mfaErrors.mfaToken && ( +

+ {mfaErrors.mfaToken.message} +

+ )} +

+ Enter the 6-digit code from your authenticator app or an 8-character backup code +

+
+ + {/* Submit Button */} + + + {/* Back to Login Link */} +
+ +
+
+
+ ) : ( + /* Login Form */ + <> +
+
+ {/* Error Message */} + {error && ( +
+ {error} +
+ )} + + {/* Email Field */} +
+ +
+
+ +
+ +
+ {errors.email && ( +

+ {errors.email.message} +

+ )} +
+ + {/* Password Field */} +
+ +
+
+ +
+ + +
+ {errors.password && ( +

+ {errors.password.message} +

+ )} +
+ + {/* Remember Me & Forgot Password */} +
+
+ + +
+ + + Forgot password? + +
+ + {/* Submit Button */} + + + {/* Back to Home Button */} +
+ + + + Back to Homepage + +
+
+ + {/* Register Link */} +
+

+ Don't have an account?{' '} + + Register now + +

+
+
+ + {/* Footer Info */} +
+

+ By logging in, you agree to our{' '} + + Terms of Service + {' '} + and{' '} + + Privacy Policy + +

+
+ + )}
); diff --git a/Frontend/src/pages/auth/RegisterPage.tsx b/Frontend/src/pages/auth/RegisterPage.tsx index 114bc1f4..4bb2d45e 100644 --- a/Frontend/src/pages/auth/RegisterPage.tsx +++ b/Frontend/src/pages/auth/RegisterPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Link, useNavigate } from 'react-router-dom'; @@ -14,22 +14,31 @@ import { Hotel, CheckCircle2, XCircle, + Home, } from 'lucide-react'; import useAuthStore from '../../store/useAuthStore'; import { registerSchema, RegisterFormData, } from '../../utils/validationSchemas'; +import { useCompanySettings } from '../../contexts/CompanySettingsContext'; const RegisterPage: React.FC = () => { const navigate = useNavigate(); const { register: registerUser, isLoading, error, clearError } = useAuthStore(); + const { settings } = useCompanySettings(); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); + // Update page title + useEffect(() => { + const companyName = settings.company_name || 'Luxury Hotel'; + document.title = `Register - ${companyName}`; + }, [settings.company_name]); + // React Hook Form setup const { register, @@ -97,35 +106,52 @@ const RegisterPage: React.FC = () => {
{/* Luxury background pattern */}
-
+
{/* Header */}
-
-
- -
-
+
+ {settings.company_logo_url ? ( + {settings.company_name + ) : ( +
+ +
+
+ )}
-

+ {settings.company_tagline && ( +

+ {settings.company_tagline} +

+ )} +

Create Account

-

- Join Luxury Hotel for exclusive benefits +

+ Join {settings.company_name || 'Luxury Hotel'} for exclusive benefits

{/* Register Form */} -
+
{/* Error Message */} {error && ( @@ -142,8 +168,8 @@ const RegisterPage: React.FC = () => {
@@ -153,14 +179,14 @@ const RegisterPage: React.FC = () => { pl-3 flex items-center pointer-events-none" > - +
{ />
{errors.name && ( -

+

{errors.name.message}

)} @@ -179,8 +205,8 @@ const RegisterPage: React.FC = () => {
@@ -190,14 +216,14 @@ const RegisterPage: React.FC = () => { pl-3 flex items-center pointer-events-none" > - +
{ />
{errors.email && ( -

+

{errors.email.message}

)} @@ -216,8 +242,8 @@ const RegisterPage: React.FC = () => {
@@ -227,14 +253,14 @@ const RegisterPage: React.FC = () => { pl-3 flex items-center pointer-events-none" > - +
{ />
{errors.phone && ( -

+

{errors.phone.message}

)} @@ -253,8 +279,8 @@ const RegisterPage: React.FC = () => {
@@ -264,14 +290,14 @@ const RegisterPage: React.FC = () => { pl-3 flex items-center pointer-events-none" > - +
{ > {showPassword ? ( ) : ( )}
{errors.password && ( -

+

{errors.password.message}

)} @@ -325,7 +351,7 @@ const RegisterPage: React.FC = () => { }} />
- {passwordStrength.label} @@ -363,8 +389,8 @@ const RegisterPage: React.FC = () => {
@@ -374,7 +400,7 @@ const RegisterPage: React.FC = () => { pl-3 flex items-center pointer-events-none" > - +
{ showConfirmPassword ? 'text' : 'password' } autoComplete="new-password" - className={`luxury-input pl-10 pr-10 ${ + className={`luxury-input pl-9 sm:pl-10 pr-9 sm:pr-10 py-2.5 sm:py-3 text-sm sm:text-base ${ errors.confirmPassword ? 'border-red-300 focus:ring-red-500 focus:border-red-500' : '' @@ -401,17 +427,17 @@ const RegisterPage: React.FC = () => { > {showConfirmPassword ? ( ) : ( )}
{errors.confirmPassword && ( -

+

{errors.confirmPassword.message}

)} @@ -422,18 +448,18 @@ const RegisterPage: React.FC = () => { type="submit" disabled={isLoading} className="btn-luxury-primary w-full flex items-center - justify-center py-3 px-4 text-sm relative" + justify-center py-2.5 sm:py-3 px-4 text-xs sm:text-sm relative" > {isLoading ? ( <> Processing... ) : ( <> - + Register )} @@ -441,8 +467,8 @@ const RegisterPage: React.FC = () => { {/* Login Link */} -
-

+

+

Already have an account?{' '} {

+ + {/* Back to Home Button */} +
+ + + + Back to Homepage + +
{/* Footer Info */} -
+

By registering, you agree to our{' '} = ({ met, text }) => ( -

+
{met ? ( - + ) : ( - + )} {text} diff --git a/Frontend/src/pages/auth/ResetPasswordPage.tsx b/Frontend/src/pages/auth/ResetPasswordPage.tsx index 92ed7b87..f63315c0 100644 --- a/Frontend/src/pages/auth/ResetPasswordPage.tsx +++ b/Frontend/src/pages/auth/ResetPasswordPage.tsx @@ -12,24 +12,33 @@ import { AlertCircle, KeyRound, Hotel, + Home, } from 'lucide-react'; import useAuthStore from '../../store/useAuthStore'; import { resetPasswordSchema, ResetPasswordFormData, } from '../../utils/validationSchemas'; +import { useCompanySettings } from '../../contexts/CompanySettingsContext'; const ResetPasswordPage: React.FC = () => { const navigate = useNavigate(); const { token } = useParams<{ token: string }>(); const { resetPassword, isLoading, error, clearError } = useAuthStore(); + const { settings } = useCompanySettings(); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isSuccess, setIsSuccess] = useState(false); + // Update page title + useEffect(() => { + const companyName = settings.company_name || 'Luxury Hotel'; + document.title = `Reset Password - ${companyName}`; + }, [settings.company_name]); + // React Hook Form setup const { register, @@ -118,68 +127,85 @@ const ResetPasswordPage: React.FC = () => {
-
+
{/* Header */}
-
-
- -
+
+ {settings.company_logo_url ? ( + {settings.company_name + ) : ( +
+ +
+ )}
-

+ {settings.company_tagline && ( +

+ {settings.company_tagline} +

+ )} +

{isSuccess ? 'Complete!' : 'Reset Password'}

-

+

{isSuccess ? 'Password has been reset successfully' - : 'Enter a new password for your account'} + : `Enter a new password for your ${settings.company_name || 'Luxury Hotel'} account`}

{/* Form Container */} -
+
{isSuccess ? ( // Success State -
+
-
+

Password reset successful!

-

+

Your password has been updated.

-

+

You can now login with your new password.

-

+

Redirecting to login page...

@@ -188,30 +214,42 @@ const ResetPasswordPage: React.FC = () => { - + Login Now + + {/* Back to Home Button */} +
+ + + + Back to Homepage + +
) : ( // Form State
{/* Error Message */} {error && (
{ 'text-red-700' }`} > - +

{isReuseError @@ -230,7 +268,7 @@ const ResetPasswordPage: React.FC = () => { {isTokenError && ( @@ -245,8 +283,8 @@ const ResetPasswordPage: React.FC = () => {

@@ -257,7 +295,7 @@ const ResetPasswordPage: React.FC = () => { pointer-events-none" >
{ type={showPassword ? 'text' : 'password'} autoComplete="new-password" autoFocus - className={`block w-full pl-10 pr-10 - py-3 border rounded-lg + className={`block w-full pl-9 sm:pl-10 pr-9 sm:pr-10 + py-2.5 sm:py-3 border rounded-lg focus:outline-none focus:ring-2 - transition-colors + transition-colors text-sm sm:text-base ${ errors.password ? 'border-red-300 ' + @@ -289,19 +327,19 @@ const ResetPasswordPage: React.FC = () => { > {showPassword ? ( ) : ( )}
{errors.password && ( -

+

{errors.password.message}

)} @@ -327,7 +365,7 @@ const ResetPasswordPage: React.FC = () => { />
{passwordStrength.label} @@ -365,8 +403,8 @@ const ResetPasswordPage: React.FC = () => {
@@ -377,7 +415,7 @@ const ResetPasswordPage: React.FC = () => { pointer-events-none" >
{ showConfirmPassword ? 'text' : 'password' } autoComplete="new-password" - className={`block w-full pl-10 pr-10 - py-3 border rounded-lg + className={`block w-full pl-9 sm:pl-10 pr-9 sm:pr-10 + py-2.5 sm:py-3 border rounded-lg focus:outline-none focus:ring-2 - transition-colors + transition-colors text-sm sm:text-base ${ errors.confirmPassword ? 'border-red-300 ' + @@ -412,19 +450,19 @@ const ResetPasswordPage: React.FC = () => { > {showConfirmPassword ? ( ) : ( )}
{errors.confirmPassword && ( -

+

{errors.confirmPassword.message}

)} @@ -435,9 +473,9 @@ const ResetPasswordPage: React.FC = () => { type="submit" disabled={isLoading} className="w-full flex items-center - justify-center py-3 px-4 border + justify-center py-2.5 sm:py-3 px-4 border border-transparent rounded-lg - shadow-sm text-sm font-medium + shadow-sm text-xs sm:text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 @@ -451,14 +489,14 @@ const ResetPasswordPage: React.FC = () => { <> Processing... ) : ( <> Reset Password @@ -469,13 +507,25 @@ const ResetPasswordPage: React.FC = () => {
Back to Login
+ + {/* Back to Home Button */} +
+ + + + Back to Homepage + +
)}
@@ -484,18 +534,18 @@ const ResetPasswordPage: React.FC = () => { {!isSuccess && (

- + Security

  • Reset link is valid for 1 hour only
  • @@ -516,11 +566,11 @@ const PasswordRequirement: React.FC<{ met: boolean; text: string; }> = ({ met, text }) => ( -
    +
    {met ? ( - + ) : ( - + )} {text} diff --git a/Frontend/src/pages/customer/BookingDetailPage.tsx b/Frontend/src/pages/customer/BookingDetailPage.tsx index 7dad735c..3381cb3c 100644 --- a/Frontend/src/pages/customer/BookingDetailPage.tsx +++ b/Frontend/src/pages/customer/BookingDetailPage.tsx @@ -514,6 +514,8 @@ const BookingDetailPage: React.FC = () => { ? '💵 Pay at hotel' : booking.payment_method === 'stripe' ? '💳 Pay with Card (Stripe)' + : booking.payment_method === 'paypal' + ? '💳 PayPal' : booking.payment_method || 'N/A'}

    diff --git a/Frontend/src/pages/customer/BookingPage.tsx b/Frontend/src/pages/customer/BookingPage.tsx index 5737313b..bba97562 100644 --- a/Frontend/src/pages/customer/BookingPage.tsx +++ b/Frontend/src/pages/customer/BookingPage.tsx @@ -34,6 +34,7 @@ import { type BookingData, } from '../../services/api/bookingService'; import { serviceService, Service } from '../../services/api'; +import { createPayPalOrder } from '../../services/api/paymentService'; import useAuthStore from '../../store/useAuthStore'; import { bookingValidationSchema, @@ -306,6 +307,43 @@ const BookingPage: React.FC = () => { ) { const bookingId = response.data.booking.id; + // If PayPal payment, redirect to PayPal payment flow + if (paymentMethod === 'paypal') { + try { + // Get current URL for return/cancel URLs + const currentUrl = window.location.origin; + const returnUrl = `${currentUrl}/payment/paypal/return?bookingId=${bookingId}`; + const cancelUrl = `${currentUrl}/payment/paypal/cancel?bookingId=${bookingId}`; + + const paypalResponse = await createPayPalOrder( + bookingId, + totalPrice, + 'USD', + returnUrl, + cancelUrl + ); + + if (paypalResponse.success && paypalResponse.data?.approval_url) { + // Redirect to PayPal for payment + window.location.href = paypalResponse.data.approval_url; + return; // Don't navigate to success page + } else { + throw new Error(paypalResponse.message || 'Failed to initialize PayPal payment'); + } + } catch (paypalError: any) { + console.error('Error initializing PayPal payment:', paypalError); + toast.error( + paypalError.response?.data?.message || + paypalError.message || + 'Failed to initialize PayPal payment. Please try again or contact support.' + ); + // Still navigate to booking success page so user can see their booking + navigate(`/booking-success/${bookingId}`); + return; + } + } + + // For other payment methods, navigate to success page toast.success( '🎉 Booking successful!', { icon: } @@ -1077,6 +1115,49 @@ const BookingPage: React.FC = () => {

    )} + {/* PayPal Payment */} + + {/* Stripe Payment Info */} {paymentMethod === 'stripe' && (
    {

    )} + + {/* PayPal Payment Info */} + {paymentMethod === 'paypal' && ( +
    +
    + +

    + Secure PayPal Payment +

    +
    +

    + You will be redirected to PayPal to securely complete your payment. +

    +
    + )}
    diff --git a/Frontend/src/pages/customer/DashboardPage.tsx b/Frontend/src/pages/customer/DashboardPage.tsx index c66d4a7f..42d3d438 100644 --- a/Frontend/src/pages/customer/DashboardPage.tsx +++ b/Frontend/src/pages/customer/DashboardPage.tsx @@ -80,6 +80,8 @@ const DashboardPage: React.FC = () => { case 'stripe': case 'credit_card': return 'Card'; + case 'paypal': + return 'PayPal'; case 'bank_transfer': return 'Bank Transfer'; case 'cash': diff --git a/Frontend/src/pages/customer/DepositPaymentPage.tsx b/Frontend/src/pages/customer/DepositPaymentPage.tsx index 9c2af784..fcd28649 100644 --- a/Frontend/src/pages/customer/DepositPaymentPage.tsx +++ b/Frontend/src/pages/customer/DepositPaymentPage.tsx @@ -16,6 +16,7 @@ import { import Loading from '../../components/common/Loading'; import { useFormatCurrency } from '../../hooks/useFormatCurrency'; import StripePaymentWrapper from '../../components/payments/StripePaymentWrapper'; +import PayPalPaymentWrapper from '../../components/payments/PayPalPaymentWrapper'; const DepositPaymentPage: React.FC = () => { const { bookingId } = useParams<{ bookingId: string }>(); @@ -294,7 +295,22 @@ const DepositPaymentPage: React.FC = () => {
    )} - {/* VNPay removed */} + {/* PayPal Payment Panel */} + {!paymentSuccess && booking && depositPayment && ( +
    +

    + + PayPal Payment +

    + { + toast.error(error || 'Payment failed'); + }} + /> +
    + )}
diff --git a/Frontend/src/pages/customer/PayPalCancelPage.tsx b/Frontend/src/pages/customer/PayPalCancelPage.tsx new file mode 100644 index 00000000..674335bc --- /dev/null +++ b/Frontend/src/pages/customer/PayPalCancelPage.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { XCircle, ArrowLeft } from 'lucide-react'; + +const PayPalCancelPage: React.FC = () => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const bookingId = searchParams.get('bookingId'); + + return ( +
+
+ +

+ Payment Cancelled +

+

+ You cancelled the PayPal payment. No charges were made. +

+
+ {bookingId && ( + + )} + +
+
+
+ ); +}; + +export default PayPalCancelPage; + diff --git a/Frontend/src/pages/customer/PayPalReturnPage.tsx b/Frontend/src/pages/customer/PayPalReturnPage.tsx new file mode 100644 index 00000000..eec773fb --- /dev/null +++ b/Frontend/src/pages/customer/PayPalReturnPage.tsx @@ -0,0 +1,116 @@ +import React, { useEffect, useState } from 'react'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { capturePayPalPayment } from '../../services/api/paymentService'; +import { toast } from 'react-toastify'; +import { CheckCircle, XCircle, Loader2 } from 'lucide-react'; +import Loading from '../../components/common/Loading'; + +const PayPalReturnPage: React.FC = () => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + + const orderId = searchParams.get('token'); + const bookingId = searchParams.get('bookingId'); + + useEffect(() => { + const capturePayment = async () => { + if (!orderId || !bookingId) { + setError('Missing payment information'); + setLoading(false); + return; + } + + try { + setLoading(true); + const response = await capturePayPalPayment(orderId, Number(bookingId)); + + if (response.success) { + setSuccess(true); + toast.success('Payment confirmed successfully!'); + // Redirect to booking details after a short delay + setTimeout(() => { + navigate(`/bookings/${bookingId}`); + }, 2000); + } else { + setError(response.message || 'Payment capture failed'); + toast.error(response.message || 'Payment capture failed'); + } + } catch (err: any) { + const errorMessage = err.response?.data?.message || err.message || 'Failed to capture payment'; + setError(errorMessage); + toast.error(errorMessage); + } finally { + setLoading(false); + } + }; + + capturePayment(); + }, [orderId, bookingId, navigate]); + + if (loading) { + return ( +
+
+ +

Processing your payment...

+
+
+ ); + } + + if (success) { + return ( +
+
+ +

+ Payment Successful! +

+

+ Your payment has been confirmed. Redirecting to booking details... +

+ +
+
+ ); + } + + return ( +
+
+ +

+ Payment Failed +

+

+ {error || 'Unable to process your payment. Please try again.'} +

+
+ + +
+
+
+ ); +}; + +export default PayPalReturnPage; + diff --git a/Frontend/src/pages/customer/ProfilePage.tsx b/Frontend/src/pages/customer/ProfilePage.tsx index 58879cb1..9d767a1c 100644 --- a/Frontend/src/pages/customer/ProfilePage.tsx +++ b/Frontend/src/pages/customer/ProfilePage.tsx @@ -11,7 +11,15 @@ import { CheckCircle, AlertCircle, Lock, - Camera + Camera, + Shield, + ShieldCheck, + ShieldOff, + Copy, + Eye, + EyeOff, + RefreshCw, + KeyRound } from 'lucide-react'; import { toast } from 'react-toastify'; import authService from '../../services/api/authService'; @@ -19,6 +27,7 @@ import useAuthStore from '../../store/useAuthStore'; import { Loading, EmptyState } from '../../components/common'; import { useAsync } from '../../hooks/useAsync'; import { useGlobalLoading } from '../../contexts/GlobalLoadingContext'; +import { normalizeImageUrl } from '../../utils/imageUtils'; // Validation schema const profileValidationSchema = yup.object().shape({ @@ -60,8 +69,25 @@ type PasswordFormData = yup.InferType; const ProfilePage: React.FC = () => { const { userInfo, setUser } = useAuthStore(); const { setLoading } = useGlobalLoading(); - const [activeTab, setActiveTab] = useState<'profile' | 'password'>('profile'); + const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'mfa'>('profile'); const [avatarPreview, setAvatarPreview] = useState(null); + const [showPassword, setShowPassword] = useState<{ + current: boolean; + new: boolean; + confirm: boolean; + }>({ + current: false, + new: false, + confirm: false, + }); + + // MFA state + const [mfaStatus, setMfaStatus] = useState<{mfa_enabled: boolean; backup_codes_count: number} | null>(null); + const [mfaSecret, setMfaSecret] = useState(null); + const [mfaQrCode, setMfaQrCode] = useState(null); + const [mfaVerificationToken, setMfaVerificationToken] = useState(''); + const [showBackupCodes, setShowBackupCodes] = useState(null); + const [showMfaSecret, setShowMfaSecret] = useState(false); // Fetch profile data const fetchProfile = async () => { @@ -113,6 +139,22 @@ const ProfilePage: React.FC = () => { resolver: yupResolver(passwordValidationSchema), }); + // Fetch MFA status + const fetchMFAStatus = async () => { + try { + const response = await authService.getMFAStatus(); + // Response is now directly the status data, not wrapped in data + if (response) { + setMfaStatus({ + mfa_enabled: response.mfa_enabled || false, + backup_codes_count: response.backup_codes_count || 0, + }); + } + } catch (error) { + console.error('Failed to fetch MFA status:', error); + } + }; + // Update form when profile data loads useEffect(() => { if (profileData || userInfo) { @@ -123,17 +165,27 @@ const ProfilePage: React.FC = () => { phone: data?.phone || '', }); if (data?.avatar) { - setAvatarPreview(data.avatar); + // Normalize avatar URL when loading + setAvatarPreview(normalizeImageUrl(data.avatar)); + } else { + setAvatarPreview(null); } } }, [profileData, userInfo, resetProfile]); + // Fetch MFA status when MFA tab is active + useEffect(() => { + if (activeTab === 'mfa') { + fetchMFAStatus(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeTab]); + // Handle profile update const onSubmitProfile = async (data: ProfileFormData) => { try { setLoading(true, 'Updating profile...'); - // Check if updateProfile exists in authService, otherwise use userService if ('updateProfile' in authService) { const response = await (authService as any).updateProfile({ full_name: data.name, @@ -150,7 +202,6 @@ const ProfilePage: React.FC = () => { } } } else { - // Fallback: use userService if updateProfile doesn't exist const { updateUser } = await import('../../services/api/userService'); const response = await updateUser(userInfo!.id, { full_name: data.name, @@ -190,7 +241,6 @@ const ProfilePage: React.FC = () => { try { setLoading(true, 'Changing password...'); - // Use updateProfile with password fields if available if ('updateProfile' in authService) { const response = await (authService as any).updateProfile({ currentPassword: data.currentPassword, @@ -202,7 +252,6 @@ const ProfilePage: React.FC = () => { resetPassword(); } } else { - // Fallback: use userService const { updateUser } = await import('../../services/api/userService'); const response = await updateUser(userInfo!.id, { password: data.newPassword, @@ -224,31 +273,63 @@ const ProfilePage: React.FC = () => { } }; - // Handle avatar upload (placeholder - would need backend support) - const handleAvatarChange = (e: React.ChangeEvent) => { + // Handle avatar upload + const handleAvatarChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; - if (file) { - // Validate file type - if (!file.type.startsWith('image/')) { - toast.error('Please select an image file'); - return; + if (!file) return; + + // Validate file type + if (!file.type.startsWith('image/')) { + toast.error('Please select an image file'); + return; + } + + // Validate file size (max 2MB) + if (file.size > 2 * 1024 * 1024) { + toast.error('Image size must be less than 2MB'); + return; + } + + // Create preview + const reader = new FileReader(); + reader.onloadend = () => { + setAvatarPreview(reader.result as string); + }; + reader.readAsDataURL(file); + + // Upload to backend + try { + setLoading(true, 'Uploading avatar...'); + const response = await authService.uploadAvatar(file); + + if (response.status === 'success' && response.data?.user) { + const updatedUser = response.data.user; + // Use full_url if available, otherwise normalize the avatar URL + const avatarUrl = response.data.full_url || normalizeImageUrl(updatedUser.avatar); + setUser({ + id: updatedUser.id, + name: updatedUser.name, + email: updatedUser.email, + phone: updatedUser.phone, + avatar: avatarUrl, + role: updatedUser.role, + }); + setAvatarPreview(avatarUrl || null); + toast.success('Avatar uploaded successfully!'); + refetchProfile(); } - - // Validate file size (max 2MB) - if (file.size > 2 * 1024 * 1024) { - toast.error('Image size must be less than 2MB'); - return; - } - - // Create preview - const reader = new FileReader(); - reader.onloadend = () => { - setAvatarPreview(reader.result as string); - }; - reader.readAsDataURL(file); - - // TODO: Upload to backend - toast.info('Avatar upload feature coming soon'); + } catch (error: any) { + const errorMessage = + error.response?.data?.detail || + error.message || + 'Failed to upload avatar'; + toast.error(errorMessage); + // Reset preview on error + setAvatarPreview(userInfo?.avatar ? normalizeImageUrl(userInfo.avatar) : null); + } finally { + setLoading(false); + // Reset file input + e.target.value = ''; } }; @@ -258,7 +339,7 @@ const ProfilePage: React.FC = () => { if (profileError && !userInfo) { return ( -
+
{ } return ( -
-
-

- Profile Settings -

-

- Manage your account information and preferences -

-
- - {/* Tabs */} -
-
- - +
+
+ {/* Header */} +
+

+ Profile Settings +

+

+ Manage your account information and security preferences +

-
- {/* Profile Tab */} - {activeTab === 'profile' && ( -
-
- {/* Avatar Section */} -
-
- {avatarPreview || userInfo?.avatar ? ( - Profile - ) : ( -
- -
- )} - -
-
-

- {userInfo?.name || 'User'} -

-

{userInfo?.email}

-

- {userInfo?.role?.charAt(0).toUpperCase() + userInfo?.role?.slice(1)} -

-
-
- - {/* Full Name */} -
- - - {profileErrors.name && ( -

- - {profileErrors.name.message} -

- )} -
- - {/* Email */} -
- - - {profileErrors.email && ( -

- - {profileErrors.email.message} -

- )} -
- - {/* Phone */} -
- - - {profileErrors.phone && ( -

- - {profileErrors.phone.message} -

- )} -
- - {/* Submit Button */} -
- -
-
+ {/* Tabs */} +
+
+ + + +
- )} - {/* Password Tab */} - {activeTab === 'password' && ( -
-
-
-
- -
-

- Password Requirements -

-
    -
  • • At least 6 characters long
  • -
  • • Use a combination of letters and numbers for better security
  • -
+ {/* Profile Tab */} + {activeTab === 'profile' && ( +
+ + {/* Avatar Section */} +
+
+ {avatarPreview || userInfo?.avatar ? ( + Profile + ) : ( +
+ +
+ )} + +
+
+

+ {userInfo?.name || 'User'} +

+

{userInfo?.email}

+

+ {userInfo?.role?.charAt(0).toUpperCase() + userInfo?.role?.slice(1)} +

-
- {/* Current Password */} + {/* Full Name */} +
+ + + {profileErrors.name && ( +

+ + {profileErrors.name.message} +

+ )} +
+ + {/* Email */} +
+ + + {profileErrors.email && ( +

+ + {profileErrors.email.message} +

+ )} +
+ + {/* Phone */} +
+ + + {profileErrors.phone && ( +

+ + {profileErrors.phone.message} +

+ )} +
+ + {/* Submit Button */} +
+ +
+ +
+ )} + + {/* Password Tab */} + {activeTab === 'password' && ( +
+
+ {/* Info Banner */} +
+
+
+ +
+
+

+ Password Requirements +

+
    +
  • + + At least 6 characters long +
  • +
  • + + Use a combination of letters and numbers for better security +
  • +
+
+
+
+ + {/* Current Password */} +
+ +
+ + +
+ {passwordErrors.currentPassword && ( +

+ + {passwordErrors.currentPassword.message} +

+ )} +
+ + {/* New Password */} +
+ +
+ + +
+ {passwordErrors.newPassword && ( +

+ + {passwordErrors.newPassword.message} +

+ )} +
+ + {/* Confirm Password */} +
+ +
+ + +
+ {passwordErrors.confirmPassword && ( +

+ + {passwordErrors.confirmPassword.message} +

+ )} +
+ + {/* Submit Button */} +
+ +
+
+
+ )} + + {/* MFA Tab */} + {activeTab === 'mfa' && ( +
+ {/* Header */}
- - - {passwordErrors.currentPassword && ( -

- - {passwordErrors.currentPassword.message} -

- )} +

+ + Two-Factor Authentication +

+

+ Add an extra layer of security to your account by requiring a verification code in addition to your password. +

- {/* New Password */} -
- - - {passwordErrors.newPassword && ( -

- - {passwordErrors.newPassword.message} -

- )} -
+ {mfaStatus?.mfa_enabled ? ( + /* MFA Enabled State */ +
+ {/* Status Card */} +
+
+
+ +
+
+

MFA is Enabled

+

+ Your account is protected with two-factor authentication. +

+

+ Remaining backup codes: {mfaStatus.backup_codes_count} +

+
+
+
- {/* Confirm Password */} -
- - - {passwordErrors.confirmPassword && ( -

- - {passwordErrors.confirmPassword.message} -

- )} -
+ {/* Backup Codes */} + {showBackupCodes && ( +
+
+

+ + Save Your Backup Codes +

+

+ Store these codes in a safe place. Each code can only be used once. +

+
+
+ {showBackupCodes.map((code, index) => ( +
+ {code} + +
+ ))} +
+ +
+ )} - {/* Submit Button */} -
- -
- -
- )} + {/* Regenerate Backup Codes */} +
+

+ + Backup Codes +

+

+ If you lose access to your authenticator app, you can use backup codes to access your account. +

+ +
+ + {/* Disable MFA */} +
+

+ + Disable MFA +

+

+ Disabling MFA will reduce the security of your account. We recommend keeping it enabled. +

+ +
+
+ ) : ( + /* MFA Setup Flow */ +
+ {!mfaSecret ? ( + /* Step 1: Initialize MFA */ +
+ +
+ ) : ( + /* Step 2: Verify and Enable */ +
+ {/* QR Code Section */} +
+

+ + Step 1: Scan QR Code +

+

+ Use your authenticator app (Google Authenticator, Authy, etc.) to scan this QR code. +

+ {mfaQrCode && ( +
+ MFA QR Code +
+ )} +
+
+ Manual Entry Key: + +
+
+ + {showMfaSecret ? mfaSecret : '••••••••••••••••'} + + +
+
+
+ + {/* Verification Section */} +
+

+ + Step 2: Verify Setup +

+

+ Enter the 6-digit code from your authenticator app to complete the setup. +

+
+
+ + setMfaVerificationToken(e.target.value.replace(/\D/g, '').slice(0, 6))} + placeholder="000000" + maxLength={6} + className="luxury-input text-center text-lg sm:text-xl tracking-widest font-mono" + /> +
+
+ + +
+
+
+
+ )} +
+ )} +
+ )} +
); }; export default ProfilePage; - diff --git a/Frontend/src/services/api/authService.ts b/Frontend/src/services/api/authService.ts index b3250182..7d96ae8e 100644 --- a/Frontend/src/services/api/authService.ts +++ b/Frontend/src/services/api/authService.ts @@ -5,6 +5,7 @@ export interface LoginCredentials { email: string; password: string; rememberMe?: boolean; + mfaToken?: string; } export interface RegisterData { @@ -19,6 +20,8 @@ export interface AuthResponse { status?: string; success?: boolean; message?: string; + requires_mfa?: boolean; + user_id?: number; data?: { token?: string; refreshToken?: string; @@ -32,6 +35,11 @@ export interface AuthResponse { role: string; createdAt?: string; }; + secret?: string; + qr_code?: string; + backup_codes?: string[]; + mfa_enabled?: boolean; + backup_codes_count?: number; }; } @@ -153,6 +161,68 @@ const authService = { ); return response.data; }, + + /** + * MFA - Initialize MFA setup + */ + initMFA: async (): Promise => { + const response = await apiClient.get('/api/auth/mfa/init'); + return response.data; + }, + + /** + * MFA - Enable MFA + */ + enableMFA: async (secret: string, verificationToken: string): Promise => { + const response = await apiClient.post('/api/auth/mfa/enable', { + secret, + verification_token: verificationToken + }); + return response.data; + }, + + /** + * MFA - Disable MFA + */ + disableMFA: async (): Promise => { + const response = await apiClient.post('/api/auth/mfa/disable'); + return response.data; + }, + + /** + * MFA - Get MFA status + */ + getMFAStatus: async (): Promise<{ mfa_enabled: boolean; backup_codes_count: number }> => { + const response = await apiClient.get<{ mfa_enabled: boolean; backup_codes_count: number }>('/api/auth/mfa/status'); + return response.data; + }, + + /** + * MFA - Regenerate backup codes + */ + regenerateBackupCodes: async (): Promise => { + const response = await apiClient.post('/api/auth/mfa/regenerate-backup-codes'); + return response.data; + }, + + /** + * Upload avatar + */ + uploadAvatar: async (file: File): Promise => { + const formData = new FormData(); + formData.append('image', file); + + const response = await apiClient.post( + '/api/auth/avatar/upload', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ); + return response.data; + }, }; export default authService; diff --git a/Frontend/src/services/api/bookingService.ts b/Frontend/src/services/api/bookingService.ts index f553bd87..48e5f2dc 100644 --- a/Frontend/src/services/api/bookingService.ts +++ b/Frontend/src/services/api/bookingService.ts @@ -7,7 +7,7 @@ export interface BookingData { check_out_date: string; // YYYY-MM-DD guest_count: number; notes?: string; - payment_method: 'cash' | 'stripe'; + payment_method: 'cash' | 'stripe' | 'paypal'; total_price: number; guest_info: { full_name: string; @@ -35,7 +35,7 @@ export interface Booking { | 'cancelled' | 'checked_in' | 'checked_out'; - payment_method: 'cash' | 'stripe'; + payment_method: 'cash' | 'stripe' | 'paypal'; payment_status: | 'unpaid' | 'paid' @@ -233,16 +233,27 @@ export const checkRoomAvailability = async ( }, } ); + + // Handle new response format when roomId is provided + if (response.data?.data?.available !== undefined) { + return { + available: response.data.data.available, + message: response.data.data.message, + }; + } + + // Fallback for old format return { available: true, - message: response.data.message, + message: response.data?.message || 'Room is available', }; } catch (error: any) { - if (error.response?.status === 409) { + if (error.response?.status === 409 || error.response?.status === 404) { return { available: false, message: - error.response.data.message || + error.response.data?.message || + error.response.data?.detail || 'Room already booked during this time', }; } diff --git a/Frontend/src/services/api/paymentService.ts b/Frontend/src/services/api/paymentService.ts index 31146587..9f39a98a 100644 --- a/Frontend/src/services/api/paymentService.ts +++ b/Frontend/src/services/api/paymentService.ts @@ -4,7 +4,7 @@ import apiClient from './apiClient'; export interface PaymentData { booking_id: number; amount: number; - payment_method: 'cash' | 'bank_transfer' | 'stripe'; + payment_method: 'cash' | 'bank_transfer' | 'stripe' | 'paypal'; transaction_id?: string; notes?: string; } @@ -13,7 +13,7 @@ export interface Payment { id: number; booking_id: number; amount: number; - payment_method: 'cash' | 'bank_transfer' | 'credit_card' | 'debit_card' | 'e_wallet' | 'stripe'; + payment_method: 'cash' | 'bank_transfer' | 'credit_card' | 'debit_card' | 'e_wallet' | 'stripe' | 'paypal'; payment_type: 'full' | 'deposit' | 'remaining'; deposit_percentage?: number; payment_status: 'pending' | 'completed' | 'failed' | 'refunded'; @@ -22,6 +22,14 @@ export interface Payment { notes?: string; createdAt: string; updatedAt: string; + booking?: { + id: number; + booking_number: string; + user?: { + name: string; + email?: string; + }; + }; } export interface BankInfo { @@ -284,6 +292,79 @@ export const confirmStripePayment = async ( }; }; +/** + * Create PayPal order + * POST /api/payments/paypal/create-order + */ +export const createPayPalOrder = async ( + bookingId: number, + amount: number, + currency: string = 'USD', + returnUrl?: string, + cancelUrl?: string +): Promise<{ + success: boolean; + data: { + order_id: string; + approval_url: string; + status: string; + }; + message?: string; +}> => { + const response = await apiClient.post( + '/payments/paypal/create-order', + { + booking_id: bookingId, + amount, + currency, + return_url: returnUrl, + cancel_url: cancelUrl, + } + ); + // Map backend response format (status: "success") to frontend format (success: true) + const data = response.data; + return { + success: data.status === "success" || data.success === true, + data: data.data || {}, + message: data.message, + }; +}; + +/** + * Capture PayPal payment + * POST /api/payments/paypal/capture + */ +export const capturePayPalPayment = async ( + orderId: string, + bookingId: number +): Promise<{ + success: boolean; + data: { + payment: Payment; + booking: { + id: number; + booking_number: string; + status: string; + }; + }; + message?: string; +}> => { + const response = await apiClient.post( + '/payments/paypal/capture', + { + order_id: orderId, + booking_id: bookingId, + } + ); + // Map backend response format (status: "success") to frontend format (success: true) + const data = response.data; + return { + success: data.status === "success" || data.success === true, + data: data.data || {}, + message: data.message, + }; +}; + export default { createPayment, getPayments, @@ -295,4 +376,6 @@ export default { getPaymentsByBookingId, createStripePaymentIntent, confirmStripePayment, + createPayPalOrder, + capturePayPalPayment, }; diff --git a/Frontend/src/services/api/systemSettingsService.ts b/Frontend/src/services/api/systemSettingsService.ts index 68d6c30a..7ccd2ad1 100644 --- a/Frontend/src/services/api/systemSettingsService.ts +++ b/Frontend/src/services/api/systemSettingsService.ts @@ -36,6 +36,24 @@ export interface UpdateStripeSettingsRequest { stripe_webhook_secret?: string; } +export interface PayPalSettingsResponse { + status: string; + data: { + paypal_client_id: string; + paypal_client_secret: string; + paypal_mode: string; + paypal_client_secret_masked: string; + has_client_id: boolean; + has_client_secret: boolean; + }; +} + +export interface UpdatePayPalSettingsRequest { + paypal_client_id?: string; + paypal_client_secret?: string; + paypal_mode?: string; +} + export interface SmtpSettingsResponse { status: string; data: { @@ -168,6 +186,29 @@ const systemSettingsService = { return response.data; }, + /** + * Get PayPal settings (Admin only) + */ + getPayPalSettings: async (): Promise => { + const response = await apiClient.get( + '/api/admin/system-settings/paypal' + ); + return response.data; + }, + + /** + * Update PayPal settings (Admin only) + */ + updatePayPalSettings: async ( + settings: UpdatePayPalSettingsRequest + ): Promise => { + const response = await apiClient.put( + '/api/admin/system-settings/paypal', + settings + ); + return response.data; + }, + /** * Get SMTP settings (Admin only) */ @@ -277,6 +318,8 @@ export type { UpdateCurrencyRequest, StripeSettingsResponse, UpdateStripeSettingsRequest, + PayPalSettingsResponse, + UpdatePayPalSettingsRequest, SmtpSettingsResponse, UpdateSmtpSettingsRequest, TestSmtpEmailRequest, diff --git a/Frontend/src/store/useAuthStore.ts b/Frontend/src/store/useAuthStore.ts index 659cc740..3b4bb405 100644 --- a/Frontend/src/store/useAuthStore.ts +++ b/Frontend/src/store/useAuthStore.ts @@ -25,9 +25,13 @@ interface AuthState { isAuthenticated: boolean; isLoading: boolean; error: string | null; + requiresMFA: boolean; + mfaUserId: number | null; + pendingCredentials: LoginCredentials | null; // Actions login: (credentials: LoginCredentials) => Promise; + verifyMFA: (mfaToken: string) => Promise; register: (data: RegisterData) => Promise; logout: () => Promise; setUser: (user: UserInfo) => void; @@ -40,6 +44,7 @@ interface AuthState { ) => Promise; initializeAuth: () => void; clearError: () => void; + clearMFA: () => void; } /** @@ -55,6 +60,9 @@ const useAuthStore = create((set, get) => ({ isAuthenticated: !!localStorage.getItem('token'), isLoading: false, error: null, + requiresMFA: false, + mfaUserId: null, + pendingCredentials: null, /** * Login - User login @@ -64,6 +72,18 @@ const useAuthStore = create((set, get) => ({ try { const response = await authService.login(credentials); + // Check if MFA is required + if (response.requires_mfa) { + set({ + isLoading: false, + requiresMFA: true, + mfaUserId: response.user_id || null, + pendingCredentials: credentials, + error: null, + }); + return; // Stop here, waiting for MFA verification + } + // Accept either boolean `success` (client) or `status: 'success'` (server) if (response.success || response.status === 'success') { const token = response.data?.token; @@ -85,6 +105,9 @@ const useAuthStore = create((set, get) => ({ isAuthenticated: true, isLoading: false, error: null, + requiresMFA: false, + mfaUserId: null, + pendingCredentials: null, }); toast.success('Login successful!'); @@ -97,7 +120,10 @@ const useAuthStore = create((set, get) => ({ set({ isLoading: false, error: errorMessage, - isAuthenticated: false + isAuthenticated: false, + requiresMFA: false, + mfaUserId: null, + pendingCredentials: null, }); toast.error(errorMessage); @@ -105,6 +131,76 @@ const useAuthStore = create((set, get) => ({ } }, + /** + * Verify MFA - Complete login with MFA token + */ + verifyMFA: async (mfaToken: string) => { + const state = get(); + if (!state.pendingCredentials) { + throw new Error('No pending login credentials'); + } + + set({ isLoading: true, error: null }); + try { + const credentials = { + ...state.pendingCredentials, + mfaToken, + }; + + const response = await authService.login(credentials); + + if (response.success || response.status === 'success') { + const token = response.data?.token; + const user = response.data?.user ?? null; + + if (!token || !user) { + throw new Error(response.message || 'MFA verification failed.'); + } + + // Save to localStorage + localStorage.setItem('token', token); + localStorage.setItem('userInfo', JSON.stringify(user)); + + // Update state + set({ + token, + userInfo: user, + isAuthenticated: true, + isLoading: false, + error: null, + requiresMFA: false, + mfaUserId: null, + pendingCredentials: null, + }); + + toast.success('Login successful!'); + } + } catch (error: any) { + const errorMessage = + error.response?.data?.message || + 'MFA verification failed. Please try again.'; + + set({ + isLoading: false, + error: errorMessage, + }); + + toast.error(errorMessage); + throw error; + } + }, + + /** + * Clear MFA - Clear MFA state + */ + clearMFA: () => { + set({ + requiresMFA: false, + mfaUserId: null, + pendingCredentials: null, + }); + }, + /** * Register - Register new account */ diff --git a/Frontend/src/utils/imageUtils.ts b/Frontend/src/utils/imageUtils.ts new file mode 100644 index 00000000..cd997fa6 --- /dev/null +++ b/Frontend/src/utils/imageUtils.ts @@ -0,0 +1,27 @@ +/** + * Utility functions for image URL normalization + */ + +/** + * Normalize image URL to absolute URL + * @param imageUrl - The image URL (can be relative or absolute) + * @returns Normalized absolute URL + */ +export const normalizeImageUrl = (imageUrl: string | null | undefined): string => { + if (!imageUrl) { + return ''; + } + + // If already a full URL, return as-is + if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { + return imageUrl; + } + + // Get API base URL from environment or default + const apiBaseUrl = import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:8000'; + + // Normalize relative paths + const cleanPath = imageUrl.startsWith('/') ? imageUrl : `/${imageUrl}`; + return `${apiBaseUrl}${cleanPath}`; +}; + diff --git a/Frontend/src/validators/bookingValidator.ts b/Frontend/src/validators/bookingValidator.ts index eb216132..945ac5d9 100644 --- a/Frontend/src/validators/bookingValidator.ts +++ b/Frontend/src/validators/bookingValidator.ts @@ -33,10 +33,10 @@ export const bookingValidationSchema = yup.object().shape({ .optional(), paymentMethod: yup - .mixed<'cash' | 'stripe'>() + .mixed<'cash' | 'stripe' | 'paypal'>() .required('Please select payment method') .oneOf( - ['cash', 'stripe'], + ['cash', 'stripe', 'paypal'], 'Invalid payment method' ),