Enforce CSR proof of possession during certificate issuance
This commit is contained in:
@@ -672,6 +672,10 @@ async def issue_certificate(
|
|||||||
request_obj = await find_request(vault, payload.get("request_id"))
|
request_obj = await find_request(vault, payload.get("request_id"))
|
||||||
if payload.get("request_id") and request_obj is None:
|
if payload.get("request_id") and request_obj is None:
|
||||||
raise ValidationError("signing request not found")
|
raise ValidationError("signing request not found")
|
||||||
|
if request_obj is not None:
|
||||||
|
csr = req_core.parse_csr(bytes(request_obj.req_der))
|
||||||
|
if not req_core.verify_csr(csr):
|
||||||
|
raise ValidationError("signing request has an invalid signature")
|
||||||
|
|
||||||
signer = await find_certificate(vault, payload.get("signer_id"))
|
signer = await find_certificate(vault, payload.get("signer_id"))
|
||||||
if payload.get("signer_id") and signer is None:
|
if payload.get("signer_id") and signer is None:
|
||||||
|
|||||||
@@ -91,6 +91,65 @@ class RouteExtraTests(unittest.TestCase):
|
|||||||
response = await client.get(f"/api/vaults/{vault_id}/requests/{request['id']}")
|
response = await client.get(f"/api/vaults/{vault_id}/requests/{request['id']}")
|
||||||
assert response.json()["signed"] is True
|
assert response.json()["signed"] is True
|
||||||
|
|
||||||
|
@async_test
|
||||||
|
async def test_invalid_csr_signature_cannot_be_issued(self) -> None:
|
||||||
|
from cryptography import x509
|
||||||
|
|
||||||
|
from kca.core import keys as key_core
|
||||||
|
from kca.core import requests as req_core
|
||||||
|
from kca.models import Certificate
|
||||||
|
|
||||||
|
async with api_client() as client:
|
||||||
|
with oidc_user("alice"):
|
||||||
|
vault_id = await _vault(client)
|
||||||
|
ca_key = await _key(client, vault_id, "ca key")
|
||||||
|
ca = await _cert(client, vault_id, ca_key["id"], "Root CA", ca=True)
|
||||||
|
key = key_core.generate_key("rsa", 2048)
|
||||||
|
csr = req_core.build_csr(
|
||||||
|
subject=x509.Name(
|
||||||
|
[x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "invalid.example.com")]
|
||||||
|
),
|
||||||
|
private_key=key,
|
||||||
|
)
|
||||||
|
der = bytearray(req_core.csr_der(csr))
|
||||||
|
der[-1] ^= 1
|
||||||
|
invalid_der = bytes(der)
|
||||||
|
assert not req_core.verify_csr(req_core.parse_csr(invalid_der))
|
||||||
|
|
||||||
|
response = await client.post(
|
||||||
|
f"/api/vaults/{vault_id}/import",
|
||||||
|
json={
|
||||||
|
"filename": "invalid.csr",
|
||||||
|
"content_base64": base64.b64encode(invalid_der).decode(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
imported = response.json()["imported"]
|
||||||
|
assert len(imported) == 1
|
||||||
|
assert imported[0]["kind"] == "request"
|
||||||
|
request_id = imported[0]["id"]
|
||||||
|
|
||||||
|
response = await client.get(f"/api/vaults/{vault_id}/requests/{request_id}")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json()["signature_valid"] is False
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
response = await client.post(
|
||||||
|
f"/api/vaults/{vault_id}/certificates",
|
||||||
|
json={
|
||||||
|
"name": "invalid CSR cert",
|
||||||
|
"request_id": request_id,
|
||||||
|
"signer_id": ca["id"],
|
||||||
|
"not_before": now.isoformat(),
|
||||||
|
"not_after": (now + timedelta(days=30)).isoformat(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 400, response.text
|
||||||
|
assert response.json()["error"] == "signing request has an invalid signature"
|
||||||
|
assert await Certificate.filter(request_id=request_id).count() == 0
|
||||||
|
response = await client.get(f"/api/vaults/{vault_id}/requests/{request_id}")
|
||||||
|
assert response.json()["signed"] is False
|
||||||
|
|
||||||
@async_test
|
@async_test
|
||||||
async def test_token_authentication(self) -> None:
|
async def test_token_authentication(self) -> None:
|
||||||
async with api_client() as client:
|
async with api_client() as client:
|
||||||
|
|||||||
Reference in New Issue
Block a user