Update documentation and organize test files
- Fix JSON syntax errors in api.md - Fix typos in verifactu.md and formato_datos.md - Update README.md with current status (anulation implemented) - Move all Python test files to test/ folder - Remove certificates from data/certs/ and test/certs/
This commit is contained in:
parent
1ad84a3b14
commit
4225af5b05
|
|
@ -69,20 +69,19 @@ crypto:
|
|||
- [x] Tokens para certificados
|
||||
- [x] Registro de certificados
|
||||
- [x] Cifrado RSA de contraseñas
|
||||
- [x] Fallback a local cuando AEAT devuelve error
|
||||
- [ ] Anulación de facturas (básico)
|
||||
- [x] Anulación de facturas (básico, sin comunicación AEAT)
|
||||
- [ ] Consultas
|
||||
- [ ] Subsanación
|
||||
- [ ] Conexión real con AEAT (certificado necesario en servidor con Python cryptography)
|
||||
- [ ] Conexión real con AEAT (necesita certificado FNMT registrado)
|
||||
|
||||
## Pruebas
|
||||
|
||||
```bash
|
||||
# Tests de certificados
|
||||
# Tests de certificados (en test/)
|
||||
python test/run_tests.py
|
||||
|
||||
# Test de factura
|
||||
python test_invoice.py
|
||||
python test/test_invoice.py
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ Registra y valida un certificado digital.
|
|||
{
|
||||
"success": false,
|
||||
"error": "certificate_expired",
|
||||
"cert": {...}
|
||||
"cert": {
|
||||
"subject": "...",
|
||||
"issuer": "...",
|
||||
"expired": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -85,7 +89,7 @@ Registra una factura en VeriFactu. No requiere token (el certificado se seleccio
|
|||
"fecha_expedicion": "17-04-2026",
|
||||
"tipo_factura": "F1",
|
||||
"descripcion": "Factura de prueba",
|
||||
"iva": [
|
||||
"IVA": [
|
||||
{"base": 100.00, "cuota": 21.00, "tipo": 21.0}
|
||||
],
|
||||
"importe_total": 121.00
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Ejemplo: `17-04-2026`
|
|||
| R2 | Rectificativa por sustitución |
|
||||
| R3 | Rectificativa por descuento |
|
||||
| R4 | Rectificativa por devolución |
|
||||
| R5 | Rectificativa por的其他原因 |
|
||||
| R5 | Rectificativa por otros motivos |
|
||||
|
||||
## Sistema Informático
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Sistema mandatory de facturación electrónica de la AEAT (Agencia Estatal de Ad
|
|||
|
||||
## Obligatoriedad
|
||||
|
||||
A partir de certain fecha, todas las facturasemitidas deben registrarse en VeriFactu, independientemente del formato (紙 o digital).
|
||||
A partir de cierta fecha, todas las facturas emitidas deben registrarse en VeriFactu, independientemente del formato (digital).
|
||||
|
||||
## Operaciones
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import sys
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# Try multiple password variants
|
||||
passwords = [
|
||||
"Mecedora12@",
|
||||
"MECEDORA12@",
|
||||
"mecedora12@",
|
||||
"53950250R",
|
||||
"1752317947215",
|
||||
"MECEDORA12",
|
||||
]
|
||||
|
||||
cert_path = r"D:\Importante\53950250R_JOSEP VICENT_MESTRE__1752317947215 - copia.p12"
|
||||
|
||||
for pw in passwords:
|
||||
try:
|
||||
with open(cert_path, "rb") as f:
|
||||
pkcs12.load_key_and_certificates(f.read(), pw.encode(), default_backend())
|
||||
print(f"OK: Password is '{pw}'")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"FAIL: '{pw}' - {e}")
|
||||
|
||||
print("None of the passwords worked!")
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
import sys
|
||||
|
||||
p12_path = sys.argv[1] if len(sys.argv) > 1 else "data/certs/personal.p12"
|
||||
password = sys.argv[2] if len(sys.argv) > 2 else "Mecedora12"
|
||||
key_path = sys.argv[3] if len(sys.argv) > 3 else "data/certs/cert_key.pem"
|
||||
cert_path = sys.argv[4] if len(sys.argv) > 4 else "data/certs/cert_cert.pem"
|
||||
|
||||
with open(p12_path, "rb") as f:
|
||||
p12_data = f.read()
|
||||
|
||||
private_key, certificate, additional_certs = pkcs12.load_key_and_certificates(
|
||||
p12_data,
|
||||
password.encode(),
|
||||
default_backend()
|
||||
)
|
||||
|
||||
key_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
)
|
||||
|
||||
cert_pem = certificate.public_bytes(serialization.Encoding.PEM)
|
||||
|
||||
with open(key_path, "wb") as f:
|
||||
f.write(key_pem)
|
||||
|
||||
with open(cert_path, "wb") as f:
|
||||
f.write(cert_pem)
|
||||
|
||||
print(f"OK: {key_path} {cert_path}")
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to generate test certificates for VeriFactu API testing.
|
||||
Each certificate has a DIFFERENT password for testing purposes.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
PASSWORDS = {
|
||||
"valid_365days": "password365",
|
||||
"valid_60days": "password60",
|
||||
"expired": "password_expired",
|
||||
"expiring_soon": "password_expiring",
|
||||
"not_yet_valid": "password_future",
|
||||
}
|
||||
|
||||
base_dir = os.path.join(os.path.dirname(__file__), "certs")
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
|
||||
def generate_cert(output_path, password, days_offset, test_name):
|
||||
private_key = rsa.generate_private_key(65537, 2048, default_backend())
|
||||
|
||||
subject = issuer = x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, test_name),
|
||||
])
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
not_valid_before = now + datetime.timedelta(days=days_offset[0])
|
||||
not_valid_after = now + datetime.timedelta(days=days_offset[1])
|
||||
|
||||
cert = x509.CertificateBuilder().subject_name(subject).issuer_name(
|
||||
issuer
|
||||
).public_key(private_key.public_key()).serial_number(
|
||||
x509.random_serial_number()
|
||||
).not_valid_before(not_valid_before).not_valid_after(
|
||||
not_valid_after
|
||||
).sign(private_key, hashes.SHA256(), default_backend())
|
||||
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
|
||||
p12_data = pkcs12.serialize_key_and_certificates(
|
||||
name=test_name.encode(),
|
||||
key=private_key,
|
||||
cert=cert,
|
||||
cas=None,
|
||||
encryption_algorithm=serialization.BestAvailableEncryption(password.encode())
|
||||
)
|
||||
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(p12_data)
|
||||
|
||||
print(f"[OK] Generated: {os.path.basename(output_path)}")
|
||||
print(f" Password: {password}")
|
||||
print(f" Days valid: {days_offset[1] - days_offset[0]}")
|
||||
return password
|
||||
|
||||
print("=" * 60)
|
||||
print("Generating Certificates with UNIQUE passwords")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
generate_cert(os.path.join(base_dir, "valid_365days.p12"), PASSWORDS["valid_365days"], (0, 365), "Valid 365 days")
|
||||
print()
|
||||
generate_cert(os.path.join(base_dir, "valid_60days.p12"), PASSWORDS["valid_60days"], (0, 60), "Valid 60 days")
|
||||
print()
|
||||
generate_cert(os.path.join(base_dir, "expired.p12"), PASSWORDS["expired"], (-20, -5), "Expired")
|
||||
print()
|
||||
generate_cert(os.path.join(base_dir, "expiring_soon.p12"), PASSWORDS["expiring_soon"], (0, 15), "Expiring Soon")
|
||||
print()
|
||||
generate_cert(os.path.join(base_dir, "not_yet_valid.p12"), PASSWORDS["not_yet_valid"], (30, 395), "Not Yet Valid")
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
print("Password Reference:")
|
||||
print("=" * 60)
|
||||
for k, v in PASSWORDS.items():
|
||||
print(f" {k}: {v}")
|
||||
print()
|
||||
|
||||
with open("test_passwords.json", "w") as f:
|
||||
json.dump(PASSWORDS, f, indent=2)
|
||||
print("Saved: test_passwords.json")
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"tipo": "alta",
|
||||
"factura": {
|
||||
"emisor_nif": "53950250R",
|
||||
"num_serie": "FV2026/001",
|
||||
"fecha_expedicion": "17-04-2026",
|
||||
"tipo_factura": "F1",
|
||||
"descripcion": "Factura de prueba",
|
||||
"destinatario": {
|
||||
"nombre": "Cliente Test SL",
|
||||
"nif": "B12345678"
|
||||
},
|
||||
"iva": [
|
||||
{
|
||||
"base": 100.00,
|
||||
"cuota": 21.00,
|
||||
"tipo": 21.0
|
||||
}
|
||||
],
|
||||
"importe_total": 121.00
|
||||
},
|
||||
"sistema": {
|
||||
"nombre": "VeriFactu API",
|
||||
"nif_proveedor": "53950250R",
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Main test runner for VeriFactu API certificate validation.
|
||||
Each test case has its own certificate and password.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from test_simulate import VeriFactuTester
|
||||
|
||||
def clear_cert_storage():
|
||||
"""Clear certificate storage before tests."""
|
||||
storage_path = "data/certs"
|
||||
if os.path.exists(storage_path):
|
||||
try:
|
||||
shutil.rmtree(storage_path)
|
||||
except:
|
||||
pass
|
||||
os.makedirs(storage_path, exist_ok=True)
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("VeriFactu API - Certificate Validation Tests")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
with open("test_passwords.json", "r") as f:
|
||||
passwords = json.load(f)
|
||||
|
||||
tester = VeriFactuTester()
|
||||
|
||||
tests = [
|
||||
{
|
||||
"name": "Valid 365 days",
|
||||
"cert_file": "test/certs/valid_365days.p12",
|
||||
"password": passwords["valid_365days"],
|
||||
"expected_success": True,
|
||||
"expected_error": None,
|
||||
"expected_warning": False,
|
||||
"description": "Certificado valido, caduca en 365 dias"
|
||||
},
|
||||
{
|
||||
"name": "Valid 60 days",
|
||||
"cert_file": "test/certs/valid_60days.p12",
|
||||
"password": passwords["valid_60days"],
|
||||
"expected_success": True,
|
||||
"expected_error": None,
|
||||
"expected_warning": False,
|
||||
"description": "Certificado valido, caduca en 60 dias"
|
||||
},
|
||||
{
|
||||
"name": "Expired",
|
||||
"cert_file": "test/certs/expired.p12",
|
||||
"password": passwords["expired"],
|
||||
"expected_success": False,
|
||||
"expected_error": "certificate_expired",
|
||||
"expected_warning": False,
|
||||
"description": "Certificado expirado"
|
||||
},
|
||||
{
|
||||
"name": "Expiring soon",
|
||||
"cert_file": "test/certs/expiring_soon.p12",
|
||||
"password": passwords["expiring_soon"],
|
||||
"expected_success": True,
|
||||
"expected_error": None,
|
||||
"expected_warning": True,
|
||||
"description": "Certificado caduca en menos de 30 dias"
|
||||
},
|
||||
{
|
||||
"name": "Not yet valid",
|
||||
"cert_file": "test/certs/not_yet_valid.p12",
|
||||
"password": passwords["not_yet_valid"],
|
||||
"expected_success": False,
|
||||
"expected_error": "certificate_not_yet_valid",
|
||||
"expected_warning": False,
|
||||
"description": "Certificado no valido aun (fecha futura)"
|
||||
},
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
print(f"{'#':<3} {'Test':<20} {'Expected':<10} {'Result':<10} {'Status'}")
|
||||
print("-" * 60)
|
||||
|
||||
for i, test in enumerate(tests, 1):
|
||||
clear_cert_storage()
|
||||
time.sleep(0.5)
|
||||
|
||||
result = tester.test_certificate(
|
||||
test["cert_file"],
|
||||
test["password"],
|
||||
test["expected_success"] and "PASS" or "FAIL",
|
||||
test["name"]
|
||||
)
|
||||
|
||||
actual_success = result.get("success", False)
|
||||
actual_error = result.get("error", "")
|
||||
actual_warning = len(result.get("warnings", [])) > 0
|
||||
|
||||
expected_str = "PASS" if test["expected_success"] else "FAIL"
|
||||
actual_str = "PASS" if actual_success else "FAIL"
|
||||
|
||||
passed_test = True
|
||||
|
||||
if actual_success != test["expected_success"]:
|
||||
passed_test = False
|
||||
if test["expected_error"] and actual_error != test["expected_error"]:
|
||||
passed_test = False
|
||||
if test["expected_warning"] != actual_warning:
|
||||
passed_test = False
|
||||
|
||||
if passed_test:
|
||||
status = "[PASS]"
|
||||
passed += 1
|
||||
else:
|
||||
status = "[FAIL]"
|
||||
failed += 1
|
||||
|
||||
print(f"{i:<3} {test['name']:<20} {expected_str:<10} {actual_str:<10} {status}")
|
||||
|
||||
if not passed_test:
|
||||
print(f" Error: {actual_error or 'none'}")
|
||||
print(f" Warning: {actual_warning}")
|
||||
|
||||
print("-" * 60)
|
||||
print(f"RESULTS: {passed} passed, {failed} failed")
|
||||
print("=" * 70)
|
||||
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simulation script for VeriFactu API certificate validation.
|
||||
Simulates a real user making API calls to register certificates.
|
||||
"""
|
||||
|
||||
# ==================== CONFIGURABLE PASSWORD ====================
|
||||
# THIS MUST MATCH THE PASSWORD IN generate_certs.py
|
||||
CERT_PASSWORD = "Mecedora12@"
|
||||
|
||||
# RUTA DEL CERTIFICADO REAL
|
||||
REAL_CERT_PATH = r"D:\Importante\53950250R_JOSEP VICENT_MESTRE__1752317947215 - copia.p12"
|
||||
# ============================================================
|
||||
|
||||
import os
|
||||
import sys
|
||||
import base64
|
||||
import json
|
||||
import datetime
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError
|
||||
|
||||
API_URL = "http://localhost:6789"
|
||||
CERTS_DIR = Path(__file__).parent / "certs"
|
||||
|
||||
# Try to import cryptography for RSA encryption
|
||||
try:
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
HAS_CRYPTO = True
|
||||
except ImportError:
|
||||
HAS_CRYPTO = False
|
||||
|
||||
|
||||
def call_api(endpoint, data=None, method="GET"):
|
||||
"""Make HTTP call to API."""
|
||||
url = f"{API_URL}{endpoint}"
|
||||
|
||||
try:
|
||||
if method == "GET":
|
||||
req = Request(url, method="GET")
|
||||
else:
|
||||
req = Request(url, data=json.dumps(data).encode(), method="POST")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
|
||||
with urlopen(req, timeout=10) as response:
|
||||
return json.loads(response.read().decode())
|
||||
except URLError as e:
|
||||
return {"error": str(e)}
|
||||
except Exception as e:
|
||||
return {"error": f"API call failed: {e}"}
|
||||
|
||||
|
||||
def get_public_key():
|
||||
"""Step 1: Get public key from API."""
|
||||
print("\n" + "=" * 60)
|
||||
print("STEP 1: Get Public Key from API")
|
||||
print("=" * 60)
|
||||
|
||||
result = call_api("/api/v1/health")
|
||||
print(f"Health check: {result}")
|
||||
|
||||
if "error" in result:
|
||||
print(f"ERROR: API not running - {result}")
|
||||
return None
|
||||
|
||||
result = call_api("/api/v1/auth/public-key")
|
||||
|
||||
if "public_key" not in result:
|
||||
print(f"ERROR: No public key in response")
|
||||
return None
|
||||
|
||||
pub_key_b64 = result["public_key"]
|
||||
pub_key = base64.b64decode(pub_key_b64)
|
||||
|
||||
print(f"Public Key received (length: {len(pub_key)} bytes)")
|
||||
return pub_key
|
||||
|
||||
|
||||
def encrypt_password(public_key_pem, password):
|
||||
"""Step 2: Encrypt password with public key (RSA)."""
|
||||
print("\n" + "=" * 60)
|
||||
print("STEP 2: Encrypt Password")
|
||||
print("=" * 60)
|
||||
|
||||
if not HAS_CRYPTO:
|
||||
print("WARNING: cryptography not available, using base64 (NOT SECURE!)")
|
||||
return base64.b64encode(password.encode()).decode()
|
||||
|
||||
try:
|
||||
public_key = serialization.load_pem_public_key(public_key_pem, default_backend())
|
||||
|
||||
encrypted = public_key.encrypt(
|
||||
password.encode(),
|
||||
padding.PKCS1v15()
|
||||
)
|
||||
|
||||
encrypted_b64 = base64.b64encode(encrypted).decode()
|
||||
print(f"Password encrypted (RSA)")
|
||||
return encrypted_b64
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR encrypting: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def register_certificate(cert_path, encrypted_password, test_name="default"):
|
||||
"""Step 3: Register certificate via API."""
|
||||
print("\n" + "=" * 60)
|
||||
print("STEP 3: Register Certificate")
|
||||
print("=" * 60)
|
||||
print(f"Certificate path: {cert_path}")
|
||||
print(f"Password (encrypted): {encrypted_password[:40]}...")
|
||||
|
||||
data = {
|
||||
"cert_name": test_name.replace(" ", "_").replace("(", "").replace(")", ""),
|
||||
"cert_path": cert_path,
|
||||
"password_encrypted": encrypted_password
|
||||
}
|
||||
|
||||
result = call_api("/api/v1/auth/register", data, method="POST")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def test_certificate(cert_file, password, expected_result, test_name):
|
||||
"""Test a single certificate via real API calls."""
|
||||
print(f"\n{'#' * 60}")
|
||||
print(f"# TEST: {test_name}")
|
||||
print(f"# Expected: {expected_result}")
|
||||
print(f"{'#' * 60}")
|
||||
|
||||
# Step 1: Get public key
|
||||
pub_key = get_public_key()
|
||||
if not pub_key:
|
||||
print("[X] Cannot get public key - is API running?")
|
||||
return
|
||||
|
||||
# Step 2: Encrypt password
|
||||
enc_password = encrypt_password(pub_key, password)
|
||||
if not enc_password:
|
||||
print("[X] Cannot encrypt password")
|
||||
return
|
||||
|
||||
# Step 3: Register certificate
|
||||
cert_path = str(CERTS_DIR / cert_file)
|
||||
result = register_certificate(cert_path, enc_password, test_name)
|
||||
|
||||
print(f"\nAPI Response:")
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
# Validate result
|
||||
success = result.get("success", False)
|
||||
error = result.get("error", "")
|
||||
warnings = result.get("warnings", [])
|
||||
|
||||
if expected_result == "PASS" and success:
|
||||
print(f"\n[OK] TEST PASSED")
|
||||
elif expected_result == "FAIL" and not success:
|
||||
print(f"\n[OK] TEST PASSED (expected failure: {error})")
|
||||
elif expected_result == "PASS with WARNING" and success and warnings:
|
||||
print(f"\n[OK] TEST PASSED (with warnings: {warnings})")
|
||||
else:
|
||||
print(f"\n[FAIL] TEST FAILED (success={success}, error={error})")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main test runner."""
|
||||
print("=" * 60)
|
||||
print("VeriFactu API - Real User Simulation")
|
||||
print("=" * 60)
|
||||
print(f"\nAPI URL: {API_URL}")
|
||||
print(f"Certs Directory: {CERTS_DIR}")
|
||||
|
||||
# Check if API is running
|
||||
print("\nChecking if API is running...")
|
||||
result = call_api("/api/v1/health")
|
||||
if "error" in result:
|
||||
print("ERROR: API not running!")
|
||||
print("Start with: ./verifactu.exe")
|
||||
return
|
||||
|
||||
print(f"API is running!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Running Tests via Real API Calls")
|
||||
print("=" * 60)
|
||||
|
||||
test_cases = [
|
||||
("valid_365days.p12", CERT_PASSWORD, "PASS", "Valid certificate (365 days)"),
|
||||
("valid_60days.p12", CERT_PASSWORD, "PASS", "Valid certificate (60 days)"),
|
||||
("expired.p12", CERT_PASSWORD, "FAIL", "Expired certificate"),
|
||||
("expiring_soon.p12", CERT_PASSWORD, "PASS with WARNING", "Expiring soon (15 days)"),
|
||||
("not_yet_valid.p12", CERT_PASSWORD, "FAIL", "Not yet valid certificate"),
|
||||
("wrong_password.p12", CERT_PASSWORD, "FAIL", "Wrong password"),
|
||||
(REAL_CERT_PATH, CERT_PASSWORD, "PASS", "REAL certificate with REAL password"),
|
||||
]
|
||||
|
||||
for cert_file, password, expected, test_name in test_cases:
|
||||
test_certificate(cert_file, password, expected, test_name)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ALL TESTS COMPLETED")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import subprocess
|
||||
import os
|
||||
|
||||
p12 = "data/certs/personal.p12"
|
||||
pwd = "Mecedora12"
|
||||
|
||||
cmd = ["python", "convert_cert.py", p12, pwd]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
print(f"Convert: {result.returncode} {result.stdout}")
|
||||
|
||||
key = "data/certs/cert_key.pem"
|
||||
cert = "data/certs/cert_cert.pem"
|
||||
|
||||
if os.path.exists(key) and os.path.exists(cert):
|
||||
print(f"Key size: {os.path.getsize(key)}")
|
||||
print(f"Cert size: {os.path.getsize(cert)}")
|
||||
|
||||
cmd = ["go", "run", "main.go"]
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||
|
||||
import time
|
||||
time.sleep(3)
|
||||
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
try:
|
||||
req = urllib.request.Request("http://localhost:6789/api/v1/health")
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
print(f"Health: {resp.read()}")
|
||||
except Exception as e:
|
||||
print(f"Health error: {e}")
|
||||
|
||||
invoice = {
|
||||
"tipo": "alta",
|
||||
"factura": {
|
||||
"emisor_nif": "53950250R",
|
||||
"num_serie": "FV2026/FINAL",
|
||||
"fecha_expedicion": "17-04-2026",
|
||||
"tipo_factura": "F1",
|
||||
"descripcion": "Final test",
|
||||
"iva": [{"base": 100, "cuota": 21, "tipo": 21}],
|
||||
"importe_total": 121
|
||||
},
|
||||
"sistema": {
|
||||
"nombre": "Test",
|
||||
"nif_proveedor": "53950250R",
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
req = urllib.request.Request(
|
||||
"http://localhost:6789/api/v1/facturas",
|
||||
data=json.dumps(invoice).encode(),
|
||||
method="POST"
|
||||
)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
print(f"Invoice: {resp.read()}")
|
||||
except Exception as e:
|
||||
print(f"Invoice error: {e}")
|
||||
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
else:
|
||||
print("Files not found")
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import urllib.request
|
||||
import urllib.parse
|
||||
|
||||
URL = "https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"
|
||||
|
||||
soap_request = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<RegFactuSistemaFacturacion xmlns:sum="SuministroLR">
|
||||
<sum:Cabecera>
|
||||
<sum1:ObligadoEmision xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd">
|
||||
<sum1:NombreRazon>TEST</sum1:NombreRazon>
|
||||
<sum1:NIF>53950250R</sum1:NIF>
|
||||
</sum1:ObligadoEmision>
|
||||
</sum:Cabecera>
|
||||
<sum:RegistroFactura>
|
||||
<sum1:RegistroAlta xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd">
|
||||
<sum1:IDVersion>1.0</sum1:IDVersion>
|
||||
<sum1:IDFactura>
|
||||
<sum1:IDEmisorFactura>53950250R</sum1:IDEmisorFactura>
|
||||
<sum1:NumSerieFactura>FV2026/001</sum1:NumSerieFactura>
|
||||
<sum1:FechaExpedicionFactura>17-04-2026</sum1:FechaExpedicionFactura>
|
||||
</sum1:IDFactura>
|
||||
<sum1:NombreRazonEmisor>TEST EMPRESA</sum1:NombreRazonEmisor>
|
||||
<sum1:TipoFactura>F1</sum1:TipoFactura>
|
||||
<sum1:DescripcionOperacion>Factura de prueba</sum1:DescripcionOperacion>
|
||||
<sum1:Desglose>
|
||||
<sum1:DetalleDesglose>
|
||||
<sum1:ClaveRegimen>01</sum1:ClaveRegimen>
|
||||
<sum1:CalificacionOperacion>S1</sum1:CalificacionOperacion>
|
||||
<sum1:TipoImpositivo>01</sum1:TipoImpositivo>
|
||||
<sum1:BaseImponibleOimporteNoSujeto>100.00</sum1:BaseImponibleOimporteNoSujeto>
|
||||
<sum1:CuotaRepercutida>21.00</sum1:CuotaRepercutida>
|
||||
</sum1:DetalleDesglose>
|
||||
</sum1:Desglose>
|
||||
<sum1:CuotaTotal>21.00</sum1:CuotaTotal>
|
||||
<sum1:ImporteTotal>121.00</sum1:ImporteTotal>
|
||||
<sum1:Encadenamiento>
|
||||
<sum1:PrimerRegistro>S</sum1:PrimerRegistro>
|
||||
</sum1:Encadenamiento>
|
||||
<sum1:SistemaInformatico>
|
||||
<sum1:NombreRazon>TEST</sum1:NombreRazon>
|
||||
<sum1:NIF>53950250R</sum1:NIF>
|
||||
<sum1:NombreSistemaInformatico>TEST</sum1:NombreSistemaInformatico>
|
||||
<sum1:IdSistemaInformatico>1</sum1:IdSistemaInformatico>
|
||||
<sum1:Version>1.0</sum1:Version>
|
||||
<sum1:NumeroInstalacion>1</sum1:NumeroInstalacion>
|
||||
<sum1:TipoUsoPosibleSoloVerifactu>S</sum1:TipoUsoPosibleSoloVerifactu>
|
||||
</sum1:SistemaInformatico>
|
||||
<sum1:FechaHoraHusoGenRegistro>17-04-2026T12:00:00</sum1:FechaHoraHusoGenRegistro>
|
||||
<sum1:TipoHuella>SHA-256</sum1:TipoHuella>
|
||||
<sum1:Huella>TESTHASH</sum1:Huella>
|
||||
</sum1:RegistroAlta>
|
||||
</sum:RegistroFactura>
|
||||
</RegFactuSistemaFacturacion>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>"""
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
"SOAPAction": ""
|
||||
}
|
||||
|
||||
print("Enviando a AEAT test...")
|
||||
print(f"URL: {URL}")
|
||||
|
||||
req = urllib.request.Request(URL, data=soap_request.encode('utf-8'), headers=headers)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
print(f"\nStatus: {response.status}")
|
||||
content = response.read().decode('utf-8')
|
||||
print(f"\nResponse:\n{content[:1500]}")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"HTTP Error: {e.code}")
|
||||
print(f"Response: {e.read().decode('utf-8')[:1500]}")
|
||||
except urllib.error.URLError as e:
|
||||
print(f"URL Error: {e.reason}")
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
|
||||
URL = "https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"
|
||||
|
||||
soap_request = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<RegFactuSistemaFacturacion xmlns:sum="SuministroLR">
|
||||
<sum:Cabecera>
|
||||
<sum1:ObligadoEmision xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd">
|
||||
<sum1:NombreRazon>TEST EMPRESA SL</sum1:NombreRazon>
|
||||
<sum1:NIF>53950250R</sum1:NIF>
|
||||
</sum1:ObligadoEmision>
|
||||
</sum:Cabecera>
|
||||
<sum:RegistroFactura>
|
||||
<sum1:RegistroAlta xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd">
|
||||
<sum1:IDVersion>1.0</sum1:IDVersion>
|
||||
<sum1:IDFactura>
|
||||
<sum1:IDEmisorFactura>53950250R</sum1:IDEmisorFactura>
|
||||
<sum1:NumSerieFactura>FV2026/TEST001</sum1:NumSerieFactura>
|
||||
<sum1:FechaExpedicionFactura>17-04-2026</sum1:FechaExpedicionFactura>
|
||||
</sum1:IDFactura>
|
||||
<sum1:NombreRazonEmisor>TEST EMPRESA SL</sum1:NombreRazonEmisor>
|
||||
<sum1:TipoFactura>F1</sum1:TipoFactura>
|
||||
<sum1:DescripcionOperacion>Factura de prueba test</sum1:DescripcionOperacion>
|
||||
<sum1:Desglose>
|
||||
<sum1:DetalleDesglose>
|
||||
<sum1:ClaveRegimen>01</sum1:ClaveRegimen>
|
||||
<sum1:CalificacionOperacion>S1</sum1:CalificacionOperacion>
|
||||
<sum1:TipoImpositivo>01</sum1:TipoImpositivo>
|
||||
<sum1:BaseImponibleOimporteNoSujeto>100.00</sum1:BaseImponibleOimporteNoSujeto>
|
||||
<sum1:CuotaRepercutida>21.00</sum1:CuotaRepercutida>
|
||||
</sum1:DetalleDesglose>
|
||||
</sum1:Desglose>
|
||||
<sum1:CuotaTotal>21.00</sum1:CuotaTotal>
|
||||
<sum1:ImporteTotal>121.00</sum1:ImporteTotal>
|
||||
<sum1:Encadenamiento>
|
||||
<sum1:PrimerRegistro>S</sum1:PrimerRegistro>
|
||||
</sum1:Encadenamiento>
|
||||
<sum1:SistemaInformatico>
|
||||
<sum1:NombreRazon>TEST API</sum1:NombreRazon>
|
||||
<sum1:NIF>53950250R</sum1:NIF>
|
||||
<sum1:NombreSistemaInformatico>TEST-API</sum1:NombreSistemaInformatico>
|
||||
<sum1:IdSistemaInformatico>1</sum1:IdSistemaInformatico>
|
||||
<sum1:Version>1.0</sum1:Version>
|
||||
<sum1:NumeroInstalacion>1</sum1:NumeroInstalacion>
|
||||
<sum1:TipoUsoPosibleSoloVerifactu>S</sum1:TipoUsoPosibleSoloVerifactu>
|
||||
</sum1:SistemaInformatico>
|
||||
<sum1:FechaHoraHusoGenRegistro>17-04-2026T12:00:00</sum1:FechaHoraHusoGenRegistro>
|
||||
<sum1:TipoHuella>SHA-256</sum1:TipoHuella>
|
||||
<sum1:Huella>0A1B2C3D4E5F6</sum1:Huella>
|
||||
</sum1:RegistroAlta>
|
||||
</sum:RegistroFactura>
|
||||
</RegFactuSistemaFacturacion>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>"""
|
||||
|
||||
ctx = urllib.request.ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = urllib.request.ssl.CERT_NONE
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
"SOAPAction": ""
|
||||
}
|
||||
|
||||
print("Testing AEAT without certificate (just headers)...")
|
||||
|
||||
req = urllib.request.Request(URL, data=soap_request.encode('utf-8'), headers=headers)
|
||||
|
||||
try:
|
||||
opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=ctx))
|
||||
response = opener.open(req, timeout=30)
|
||||
print(f"Status: {response.status}")
|
||||
print(f"Response: {response.read().decode('utf-8')[:1500]}")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"HTTP Error: {e.code}")
|
||||
print(f"Body: {e.read().decode('utf-8')[:1500]}")
|
||||
except Exception as e:
|
||||
print(f"Error: {type(e).__name__}: {e}")
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import base64
|
||||
import json
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
API_URL = "http://localhost:6789"
|
||||
|
||||
print("=" * 60)
|
||||
print("Test: Enviar Factura")
|
||||
print("=" * 60)
|
||||
|
||||
invoice = {
|
||||
"tipo": "alta",
|
||||
"factura": {
|
||||
"emisor_nif": "53950250R",
|
||||
"num_serie": "FV2026/001",
|
||||
"fecha_expedicion": "17-04-2026",
|
||||
"tipo_factura": "F1",
|
||||
"descripcion": "Factura de prueba",
|
||||
"iva": [
|
||||
{"base": 100.00, "cuota": 21.00, "tipo": 21.0}
|
||||
],
|
||||
"importe_total": 121.00
|
||||
},
|
||||
"sistema": {
|
||||
"nombre": "VeriFactu API",
|
||||
"nif_proveedor": "53950250R",
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
print("\nEnviando factura...")
|
||||
|
||||
req = Request(
|
||||
f"{API_URL}/api/v1/facturas",
|
||||
data=json.dumps(invoice).encode(),
|
||||
method="POST"
|
||||
)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
|
||||
try:
|
||||
with urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
print(json.dumps(result, indent=2))
|
||||
except URLError as e:
|
||||
print(f"Error: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import subprocess
|
||||
import os
|
||||
|
||||
p12 = "C:\\Users\\jmest\\GolandProjects\\VerifactuMidAPI\\data\\certs\\personal.p12"
|
||||
pwd = "Mecedora12"
|
||||
out = "C:\\Users\\jmest\\GolandProjects\\VerifactuMidAPI\\data\\certs\\combined.pem"
|
||||
|
||||
cmd = f'openssl pkcs12 -in "{p12}" -passin pass:{pwd} -nodes -out "{out}"'
|
||||
print(f"Running: {cmd}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
print(f"Return code: {result.returncode}")
|
||||
if result.returncode != 0:
|
||||
print(f"Error: {result.stderr}")
|
||||
else:
|
||||
print(f"Success! Output: {out}")
|
||||
print(f"File size: {os.path.getsize(out)}")
|
||||
except Exception as e:
|
||||
print(f"Exception: {e}")
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import base64
|
||||
import json
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
API_URL = "http://localhost:6789"
|
||||
|
||||
# Get public key
|
||||
req = Request(f"{API_URL}/api/v1/auth/public-key", method="GET")
|
||||
with urlopen(req, timeout=10) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
pub_key_pem = base64.b64decode(result["public_key"])
|
||||
|
||||
# Encrypt password
|
||||
public_key = serialization.load_pem_public_key(pub_key_pem, default_backend())
|
||||
password = "Mecedora12"
|
||||
encrypted = public_key.encrypt(password.encode(), padding.PKCS1v15())
|
||||
encrypted_b64 = base64.b64encode(encrypted).decode()
|
||||
|
||||
# Register certificate
|
||||
data = {
|
||||
"cert_name": "personal",
|
||||
"cert_path": "C:/Users/jmest/GolandProjects/VerifactuMidAPI/data/certs/personal.p12",
|
||||
"password_encrypted": encrypted_b64
|
||||
}
|
||||
|
||||
req = Request(
|
||||
f"{API_URL}/api/v1/auth/register",
|
||||
data=json.dumps(data).encode(),
|
||||
method="POST"
|
||||
)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
with urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
print(json.dumps(result, indent=2))
|
||||
except URLError as e:
|
||||
print(f"Error: {e}")
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test infrastructure for VeriFactu API certificate validation.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError
|
||||
|
||||
API_URL = "http://localhost:6789"
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
HAS_CRYPTO = True
|
||||
except ImportError:
|
||||
HAS_CRYPTO = False
|
||||
|
||||
|
||||
class VeriFactuTester:
|
||||
def __init__(self):
|
||||
self.api_url = API_URL
|
||||
self.certs_dir = Path(__file__).parent / "certs"
|
||||
|
||||
def check_health(self):
|
||||
"""Check if API is running."""
|
||||
try:
|
||||
req = Request(f"{self.api_url}/api/v1/health", method="GET")
|
||||
with urlopen(req, timeout=5) as response:
|
||||
return json.loads(response.read().decode())
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_public_key(self):
|
||||
"""Get public key from API."""
|
||||
try:
|
||||
req = Request(f"{self.api_url}/api/v1/auth/public-key", method="GET")
|
||||
with urlopen(req, timeout=10) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
return base64.b64decode(result["public_key"])
|
||||
except Exception as e:
|
||||
print(f"ERROR getting public key: {e}")
|
||||
return None
|
||||
|
||||
def encrypt_password(self, public_key_pem, password):
|
||||
"""Encrypt password with public key."""
|
||||
if not HAS_CRYPTO:
|
||||
print("WARNING: cryptography not available")
|
||||
return base64.b64encode(password.encode()).decode()
|
||||
|
||||
try:
|
||||
public_key = serialization.load_pem_public_key(public_key_pem, default_backend())
|
||||
encrypted = public_key.encrypt(
|
||||
password.encode(),
|
||||
padding.PKCS1v15()
|
||||
)
|
||||
return base64.b64encode(encrypted).decode()
|
||||
except Exception as e:
|
||||
print(f"ERROR encrypting password: {e}")
|
||||
return None
|
||||
|
||||
def register_certificate(self, cert_path, encrypted_password, cert_name):
|
||||
"""Register certificate via API."""
|
||||
data = {
|
||||
"cert_name": cert_name,
|
||||
"cert_path": cert_path,
|
||||
"password_encrypted": encrypted_password
|
||||
}
|
||||
|
||||
try:
|
||||
req = Request(
|
||||
f"{self.api_url}/api/v1/auth/register",
|
||||
data=json.dumps(data).encode(),
|
||||
method="POST"
|
||||
)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
with urlopen(req, timeout=30) as response:
|
||||
return json.loads(response.read().decode())
|
||||
except URLError as e:
|
||||
return {"error": str(e), "success": False}
|
||||
except Exception as e:
|
||||
return {"error": str(e), "success": False}
|
||||
|
||||
def test_certificate(self, cert_file, password, expected_result, test_name):
|
||||
"""Test a single certificate."""
|
||||
print(f"\n--- Testing: {test_name} ---")
|
||||
|
||||
pub_key = self.get_public_key()
|
||||
if not pub_key:
|
||||
print("ERROR: Cannot get public key")
|
||||
return False
|
||||
|
||||
enc_password = self.encrypt_password(pub_key, password)
|
||||
if not enc_password:
|
||||
print("ERROR: Cannot encrypt password")
|
||||
return False
|
||||
|
||||
result = self.register_certificate(cert_file, enc_password, test_name)
|
||||
|
||||
print(f"API Response: {json.dumps(result, indent=2)}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("This module should be imported, not run directly.")
|
||||
print("Use: python test/run_tests.py")
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import base64
|
||||
import json
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
API_URL = "http://localhost:6789"
|
||||
|
||||
print("=" * 60)
|
||||
print("Test: Enviar Factura con Validation")
|
||||
print("=" * 60)
|
||||
|
||||
invoice = {
|
||||
"tipo": "alta",
|
||||
"factura": {
|
||||
"emisor_nif": "53950250R",
|
||||
"num_serie": "FV2026/001",
|
||||
"fecha_expedicion": "17-04-2026",
|
||||
"tipo_factura": "F1",
|
||||
"descripcion": "Factura de prueba",
|
||||
"destinatario": {
|
||||
"nombre": "Cliente Test SL",
|
||||
"nif": "B12345678"
|
||||
},
|
||||
"iva": [
|
||||
{"base": 100.00, "cuota": 21.00, "tipo": 21.0}
|
||||
],
|
||||
"importe_total": 121.00
|
||||
},
|
||||
"sistema": {
|
||||
"nombre": "VeriFactu API",
|
||||
"nif_proveedor": "53950250R",
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
print("\nEnviando factura...")
|
||||
|
||||
req = Request(
|
||||
f"{API_URL}/api/v1/facturas",
|
||||
data=json.dumps(invoice).encode(),
|
||||
method="POST"
|
||||
)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
|
||||
try:
|
||||
with urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
print(json.dumps(result, indent=2))
|
||||
except URLError as e:
|
||||
print(f"Error HTTP: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error: {type(e).__name__}: {e}")
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import sys
|
||||
import datetime
|
||||
import os
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
cert_path = sys.argv[1]
|
||||
password = sys.argv[2]
|
||||
|
||||
try:
|
||||
if not os.path.exists(cert_path):
|
||||
print("NOT_FOUND")
|
||||
sys.exit(1)
|
||||
|
||||
with open(cert_path, "rb") as f:
|
||||
p12_data = f.read()
|
||||
|
||||
private_key, cert, additional_certs = pkcs12.load_key_and_certificates(
|
||||
p12_data, password.encode(), default_backend()
|
||||
)
|
||||
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
not_after = cert.not_valid_after_utc.replace(tzinfo=datetime.timezone.utc)
|
||||
not_before = cert.not_valid_before_utc.replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
if now > not_after:
|
||||
print("EXPIRED")
|
||||
sys.exit(1)
|
||||
|
||||
if now < not_before:
|
||||
print("NOT_YET_VALID")
|
||||
sys.exit(1)
|
||||
|
||||
days_until = (not_after - now).days
|
||||
|
||||
print(f"OK:{days_until}")
|
||||
|
||||
except FileNotFoundError:
|
||||
print("NOT_FOUND")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("INVALID")
|
||||
sys.exit(1)
|
||||
Loading…
Reference in New Issue