125 lines
4.3 KiB
Python
125 lines
4.3 KiB
Python
"""
|
|
Přihlášení na portál ČPZP pomocí certifikátu.
|
|
|
|
Flow:
|
|
1. GET /app/login/ → session cookie + csrfCert token + challenge (certificateLoginKey)
|
|
2. Podepiš challenge certifikátem (PKCS7 detached, RSA + SHA-256)
|
|
3. POST /app/ s form-data: csrfCert=<token>&sign=<podpis_base64_der>
|
|
4. Ulož cookies do cpzp_cookies.json
|
|
|
|
POUŽITÍ:
|
|
pip install requests cryptography beautifulsoup4
|
|
python 01_prihlaseni.py
|
|
"""
|
|
|
|
import base64
|
|
import json
|
|
import os
|
|
import re
|
|
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.serialization import pkcs7, pkcs12
|
|
|
|
def _parse_js_str(js_expr: str) -> str:
|
|
"""Převede JS string expression ('a' + String.fromCharCode(13,10) + 'b') na Python string."""
|
|
parts = re.findall(r"'([^']*)'|String\.fromCharCode\(([\d,\s]+)\)", js_expr)
|
|
result = []
|
|
for str_part, chars_part in parts:
|
|
if str_part is not None and (str_part or not chars_part):
|
|
result.append(str_part)
|
|
elif chars_part:
|
|
result.append("".join(chr(int(c.strip())) for c in chars_part.split(",")))
|
|
return "".join(result)
|
|
|
|
|
|
PFX_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../Certificates/MBQualifiedCert.pfx"))
|
|
PFX_PASSWORD = b"Vlado7309208104++"
|
|
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "cpzp_cookies.json"))
|
|
|
|
BASE_URL = "https://portal.cpzp.cz"
|
|
LOGIN_URL = f"{BASE_URL}/app/login/"
|
|
POST_URL = f"{BASE_URL}/app/"
|
|
|
|
|
|
def main() -> None:
|
|
session = requests.Session()
|
|
session.headers.update({
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
"Origin": BASE_URL,
|
|
"Referer": LOGIN_URL,
|
|
})
|
|
|
|
# 1. Načti login stránku → session cookie + HTML s tokenem a challengem
|
|
r = session.get(LOGIN_URL)
|
|
r.raise_for_status()
|
|
|
|
soup = BeautifulSoup(r.content, "html.parser", from_encoding="utf-8")
|
|
|
|
# Extrahuj csrfCert z formuláře frmPrihlasCert
|
|
csrf_input = soup.find("form", {"name": "frmPrihlasCert"})
|
|
if not csrf_input:
|
|
raise RuntimeError("Formulář frmPrihlasCert nenalezen")
|
|
csrf_cert = csrf_input.find("input", {"name": "csrfCert"})["value"]
|
|
|
|
# Extrahuj challenge z JS — formát: 'část'+ String.fromCharCode(13, 10) +'část'
|
|
html = r.content.decode("utf-8") # vynutit UTF-8
|
|
match = re.search(r"certificateLoginKey\s*:\s*(.+?)(?=,\s*\n|\n\s*maxHeight)", html, re.DOTALL)
|
|
if not match:
|
|
raise RuntimeError("certificateLoginKey nenalezen v HTML")
|
|
challenge = _parse_js_str(match.group(1))
|
|
print(f"Challenge: {challenge[:80]}...")
|
|
|
|
# 2. Podepis certifikátem (PKCS7 detached, RSA + SHA-256, bez CA řetězu)
|
|
with open(PFX_PATH, "rb") as f:
|
|
private_key, cert, _ = pkcs12.load_key_and_certificates(f.read(), PFX_PASSWORD)
|
|
|
|
pem_podpis = (
|
|
pkcs7.PKCS7SignatureBuilder()
|
|
.set_data(challenge.encode("utf-8"))
|
|
.add_signer(cert, private_key, hashes.SHA256())
|
|
.sign(serialization.Encoding.PEM, [pkcs7.PKCS7Options.DetachedSignature])
|
|
)
|
|
|
|
podpis_pem = pem_podpis.decode("ascii").strip()
|
|
|
|
# 3. Přihlas se (POST form-data)
|
|
r = session.post(POST_URL, data={
|
|
"csrfCert": csrf_cert,
|
|
"sign": podpis_pem,
|
|
}, headers={
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Referer": LOGIN_URL,
|
|
})
|
|
r.raise_for_status()
|
|
|
|
# Ověř přihlášení — po úspěchu je v URL /app/ a stránka neobsahuje login formu
|
|
if "frmPrihlasCert" in r.text:
|
|
print("Přihlášení selhalo — server vrátil login stránku znovu")
|
|
return
|
|
|
|
print("Přihlášení úspěšné!")
|
|
|
|
# 4. Ulož cookies
|
|
cookies = [
|
|
{
|
|
"name": c.name,
|
|
"value": c.value,
|
|
"domain": c.domain if c.domain.startswith(".") else "." + c.domain,
|
|
"path": c.path or "/",
|
|
"expires": int(c.expires) if c.expires else -1,
|
|
"secure": bool(c.secure),
|
|
"httpOnly": False,
|
|
"sameSite": "Lax",
|
|
}
|
|
for c in session.cookies
|
|
]
|
|
with open(COOKIES_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(cookies, f, indent=2, ensure_ascii=False)
|
|
print(f"Ulozeno {len(cookies)} cookies -> {COOKIES_FILE}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|