03075f1ef1
Closes TG-4 Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
6.2 KiB
Python
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}")
|