Files
WebAisMap/ssl_utils.py
T
Grigo 03075f1ef1 Initial import: WebAisMap
Closes TG-4

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 07:56:45 +03:00

149 lines
6.2 KiB
Python

import os
import ssl
import socket
import datetime
def _get_local_ips():
"""Возвращает список локальных IP-адресов машины."""
ips = []
try:
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
ip = info[4][0]
if ip not in ips:
ips.append(ip)
except Exception:
pass
return ips
def get_ssl_context():
"""
Создаёт собственный мини-CA и подписывает серверный сертификат.
CA-сертификат можно установить на телефон один раз — и предупреждения исчезнут.
Файлы: ca.pem (корневой, для установки на клиенты), cert.pem + key.pem (сервер).
"""
cert_dir = os.path.dirname(os.path.abspath(__file__))
ca_cert_file = os.path.join(cert_dir, "ca.pem")
ca_key_file = os.path.join(cert_dir, "ca_key.pem")
cert_file = os.path.join(cert_dir, "cert.pem")
key_file = os.path.join(cert_dir, "key.pem")
need_regen = not all(os.path.exists(f) for f in [ca_cert_file, ca_key_file, cert_file, key_file])
if need_regen:
try:
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import ipaddress
except ImportError:
print("[SSL] Библиотека cryptography не установлена.")
print("[SSL] Установите: pip install cryptography")
print("[SSL] Без HTTPS GPS телефона работать не будет.")
return None
print("[SSL] Генерация корневого CA и серверного сертификата...")
# === 1. Корневой CA ===
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
ca_name = x509.Name([
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "AIS Map CA"),
x509.NameAttribute(NameOID.COMMON_NAME, "AIS Map Root CA"),
])
ca_cert = (
x509.CertificateBuilder()
.subject_name(ca_name)
.issuer_name(ca_name)
.public_key(ca_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))
.add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True)
.add_extension(
x509.KeyUsage(
digital_signature=True, key_cert_sign=True, crl_sign=True,
content_commitment=False, key_encipherment=False,
data_encipherment=False, key_agreement=False,
encipher_only=False, decipher_only=False,
),
critical=True,
)
.sign(ca_key, hashes.SHA256())
)
with open(ca_cert_file, "wb") as f:
f.write(ca_cert.public_bytes(serialization.Encoding.PEM))
with open(ca_key_file, "wb") as f:
f.write(ca_key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption(),
))
print(f"[SSL] CA сертификат: {ca_cert_file}")
# === 2. Серверный сертификат, подписанный CA ===
srv_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
srv_name = x509.Name([
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "AIS Map"),
x509.NameAttribute(NameOID.COMMON_NAME, "aismap.local"),
])
san_entries = [x509.DNSName("aismap.local"), x509.DNSName("localhost")]
for iface_ip in _get_local_ips():
try:
san_entries.append(x509.IPAddress(ipaddress.IPv4Address(iface_ip)))
except Exception:
pass
san_entries.append(x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")))
srv_cert = (
x509.CertificateBuilder()
.subject_name(srv_name)
.issuer_name(ca_name)
.public_key(srv_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(x509.SubjectAlternativeName(san_entries), critical=False)
.add_extension(
x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]),
critical=False,
)
.sign(ca_key, hashes.SHA256())
)
with open(cert_file, "wb") as f:
f.write(srv_cert.public_bytes(serialization.Encoding.PEM))
with open(key_file, "wb") as f:
f.write(srv_key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption(),
))
print(f"[SSL] Серверный сертификат: {cert_file}")
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(cert_file, key_file)
return ctx
def run_http_redirect(https_port):
"""Простой HTTP-сервер на порту 80, который редиректит на HTTPS."""
from flask import Flask, redirect, request as flask_request
redir_app = Flask("redirect")
@redir_app.route("/", defaults={"path": ""})
@redir_app.route("/<path:path>")
def redir(path):
host = flask_request.host.split(":")[0]
port_suffix = f":{https_port}" if https_port != 443 else ""
return redirect(f"https://{host}{port_suffix}/{path}", code=301)
try:
redir_app.run(host="0.0.0.0", port=80, threaded=True)
except Exception as e:
print(f"[HTTP] Не удалось запустить редирект на порту 80: {e}")