Skip to content

Commit 8d6c310

Browse files
committed
Protect classical PKI mutation endpoints
1 parent 6e7f100 commit 8d6c310

1 file changed

Lines changed: 16 additions & 6 deletions

File tree

src/hybrid_pki/api/routes_classical.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from pathlib import Path
22

3-
from fastapi import APIRouter, HTTPException
3+
from fastapi import APIRouter, Depends, HTTPException
44
from pydantic import BaseModel, Field
55

6+
from hybrid_pki.api.security import require_mutation_access
67
from hybrid_pki.classical.ca import (
78
create_intermediate_ca_certificate,
89
create_root_ca_certificate,
@@ -117,7 +118,7 @@ def ensure_file_exists(path: Path, description: str) -> None:
117118
)
118119

119120

120-
@router.post("/ca/root/init")
121+
@router.post("/ca/root/init", dependencies=[Depends(require_mutation_access)])
121122
def init_root_ca(request: RootCARequest):
122123
"""
123124
Generate a classical Root CA.
@@ -128,6 +129,12 @@ def init_root_ca(request: RootCARequest):
128129
- Root CA certificate PEM
129130
- Root CA certificate DER
130131
"""
132+
if ROOT_KEY_PATH.exists() or ROOT_CERT_PATH.exists():
133+
raise HTTPException(
134+
status_code=409,
135+
detail="Root CA already exists; remove it explicitly before reinitializing.",
136+
)
137+
131138
ROOT_DIR.mkdir(parents=True, exist_ok=True)
132139

133140
private_key = generate_private_key(request.algorithm)
@@ -174,7 +181,7 @@ def init_root_ca(request: RootCARequest):
174181
}
175182

176183

177-
@router.post("/ca/intermediate/init")
184+
@router.post("/ca/intermediate/init", dependencies=[Depends(require_mutation_access)])
178185
def init_intermediate_ca(request: IntermediateCARequest):
179186
"""
180187
Generate an Intermediate CA signed by the Root CA.
@@ -235,7 +242,7 @@ def init_intermediate_ca(request: IntermediateCARequest):
235242
}
236243

237244

238-
@router.post("/certificates/server/issue")
245+
@router.post("/certificates/server/issue", dependencies=[Depends(require_mutation_access)])
239246
def issue_classical_server_certificate(request: ServerCertificateRequest):
240247
"""
241248
Issue a classical server certificate signed by the Intermediate CA.
@@ -323,7 +330,10 @@ def verify_classical_server_certificate(request: VerifyCertificateRequest):
323330
"""
324331
Verify a classical server certificate against the Intermediate CA and Root CA.
325332
"""
326-
certificate_path = Path(request.certificate_path)
333+
certificate_path = Path(request.certificate_path).resolve()
334+
issued_root = ISSUED_DIR.resolve()
335+
if issued_root not in certificate_path.parents:
336+
raise HTTPException(status_code=400, detail="Certificate path must be inside certs/issued")
327337

328338
ensure_file_exists(certificate_path, "Server certificate")
329339
ensure_file_exists(INTERMEDIATE_CERT_PATH, "Intermediate CA certificate")
@@ -367,7 +377,7 @@ def verify_classical_server_certificate(request: VerifyCertificateRequest):
367377
}
368378

369379

370-
@router.post("/certificates/server/revoke")
380+
@router.post("/certificates/server/revoke", dependencies=[Depends(require_mutation_access)])
371381
def revoke_classical_server_certificate(request: RevokeCertificateRequest):
372382
"""
373383
Revoke a classical server certificate.

0 commit comments

Comments
 (0)