Skip to content

Commit

Permalink
Add REST API endpoint for PKIX certificates (#119)
Browse files Browse the repository at this point in the history
* Add REST API endpoint for PKIX certificates

* Create venv for test execution

* Fix linter groups test

* Bump REST API version for new PKIX cert endpoint
  • Loading branch information
CBonnell authored Oct 14, 2024
1 parent b7a21dd commit 45c3d69
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
- name: Test wheel
run: |
export WHEELFILE=`ls -1 dist/*.whl | head -n 1`
python -m venv pkilint-testbed
source pkilint-testbed/bin/activate
pip install "$WHEELFILE[dev]"
pytest
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.12.0
0.12.1
8 changes: 6 additions & 2 deletions pkilint/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

from fastapi import FastAPI, HTTPException

from pkilint.rest import cabf_serverauth, cabf_smime, etsi, ocsp, crl
from pkilint.rest import cabf_serverauth, cabf_smime, pkix, etsi, ocsp, crl
from pkilint.rest import model

_PKILINT_VERSION = version('pkilint')
_API_VERSION = 'v1.5'
_API_VERSION = 'v1.6'

app = FastAPI(
title='pkilint API',
Expand All @@ -17,6 +17,7 @@

_CERTIFICATE_LINTER_GROUPS = [
m.create_linter_group_instance() for m in (
pkix,
cabf_smime,
cabf_serverauth,
etsi,
Expand All @@ -26,6 +27,7 @@
_OCSP_PKIX_LINTER = ocsp.create_ocsp_response_linter()
_CRL_PKIX_LINTER = crl.create_crl_linter()


@app.get('/version')
def version() -> model.Version:
"""Retrieves the version of pkilint that this server is using"""
Expand Down Expand Up @@ -115,12 +117,14 @@ def ocsp_response_lint(doc: model.OcspResponseModel) -> model.LintResultList:

return _OCSP_PKIX_LINTER.lint(parsed_doc)


@app.get('/crl/pkix/crl')
def crl_linter_validations() -> List[model.Validation]:
"""Returns the set of validations performed by the CRL linter"""

return _CRL_PKIX_LINTER.validations


@app.post('/crl/pkix/crl')
def crl_lint(doc: model.CrlModel) -> model.LintResultList:
"""Lints the specified CRL"""
Expand Down
33 changes: 17 additions & 16 deletions pkilint/rest/crl.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from pkilint import pkix

from pkilint.pkix import crl, create_attribute_decoder, create_extension_decoder, extension, name
from pkilint.pkix import crl, extension, name
from pkilint.rest import model


def create_crl_linter(validity_additional_validators=None, doc_additional_validators=None):
if doc_additional_validators is None:
doc_additional_validators = []
Expand All @@ -11,19 +12,19 @@ def create_crl_linter(validity_additional_validators=None, doc_additional_valida

return model.Linter(
validator=crl.create_pkix_crl_validator_container(
[
pkix.create_attribute_decoder(name.ATTRIBUTE_TYPE_MAPPINGS),
pkix.create_extension_decoder(extension.EXTENSION_MAPPINGS),
],
[
crl.create_issuer_validator_container(
[]
),
crl.create_validity_validator_container(
validity_additional_validators
),
crl.create_extensions_validator_container(
[]
),
] + doc_additional_validators), name='crl_linter'
[
pkix.create_attribute_decoder(name.ATTRIBUTE_TYPE_MAPPINGS),
pkix.create_extension_decoder(extension.EXTENSION_MAPPINGS),
],
[
crl.create_issuer_validator_container(
[]
),
crl.create_validity_validator_container(
validity_additional_validators
),
crl.create_extensions_validator_container(
[]
),
] + doc_additional_validators), name='crl_linter'
)
35 changes: 35 additions & 0 deletions pkilint/rest/pkix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pkilint.pkix import certificate, name, extension
from pkilint.rest import model


class PkixCertificateLinterGroup(model.LinterGroup):
def __init__(self, linters):
super().__init__(name='pkix', linters=linters)

def determine_linter(self, doc):
return self.linters[0]


def create_linter_group_instance():
return PkixCertificateLinterGroup(
[
model.Linter(
validator=certificate.create_pkix_certificate_validator_container(
certificate.create_decoding_validators(name.ATTRIBUTE_TYPE_MAPPINGS, extension.EXTENSION_MAPPINGS),
[
certificate.create_issuer_validator_container(
[]
),
certificate.create_validity_validator_container(),
certificate.create_subject_validator_container(
[]
),
certificate.create_extensions_validator_container(
[]
),
]
),
name='certificate'
)
]
)
41 changes: 32 additions & 9 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def test_version(client):
Eqpda1II90v7ae6kNwIPK+140WOhkKilZ526OHvetaZ9XUc=
-----END CERTIFICATE-----'''


_OV_FINAL_CLEAN_PEM = '''-----BEGIN CERTIFICATE-----
MIIGxDCCBKygAwIBAgIMS9Kjc1CWWKwCO6fGMA0GCSqGSIb3DQEBCwUAMEUxCzAJ
BgNVBAYTAlVTMRMwEQYDVQQKEwpDZXJ0cyBSIFVzMSEwHwYDVQQDExhDZXJ0cyBS
Expand Down Expand Up @@ -108,7 +107,6 @@ def test_version(client):
XpOaUjkNSs4=
-----END CERTIFICATE-----'''


_BAD_CERT_POLICIES_DER_PEM = '''-----BEGIN CERTIFICATE-----
MIIFxjCCBK6gAwIBAgIQAROrI6zwQH6igXlKWdEvgjANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE
Expand Down Expand Up @@ -143,7 +141,6 @@ def test_version(client):
kJePcGspl/I0jGLIvpG34YRy9mLrgiWskyETVNFDPIzddBDAqWu2JkDK
-----END CERTIFICATE-----'''


_CERT_WITH_TRAILER_B64 = '''MIIGxDCCBKygAwIBAgIMS9Kjc1CWWKwCO6fGMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAlVT
MRMwEQYDVQQKEwpDZXJ0cyBSIFVzMSEwHwYDVQQDExhDZXJ0cyBSIFVzIElzc3VpbmcgQ0EgRzEw
HhcNMjIwODE4MDgwNjI3WhcNMjMwOTE5MDgwNjI2WjBBMQswCQYDVQQGEwJGUjEOMAwGA1UECBMF
Expand Down Expand Up @@ -176,7 +173,6 @@ def test_version(client):
cdn6cFbcd93aknIHd9Te8Z2v8saRPeLQCtFiCTe59IndMLJ8wwru0OHco8qL4Qf9VcuDMpNWUZGD
p6o9EaAJgzlOHGRsk5NZTCZkXpOaUjkNSs5B'''


_OCSP_RESPONSE_B64 = '''MIIDnwoBAKCCA5gwggOUBgkrBgEFBQcwAQEEggOFMIIDgTCBsKIWBBQK46D+ndQl
dpi163Lrygznvz318RgPMjAyNDA0MDIxMjM3NDdaMIGEMIGBMFkwDQYJYIZIAWUD
BAIBBQAEIDqZRndWgHOnB7/eUBhjReTNYTTbCF66odEEJfA7bwjqBCBHSmyjAfI9
Expand All @@ -198,7 +194,6 @@ def test_version(client):
AkFJCwtJpi7m00Qx9r/ugNWsnCFSiKUdxuvj7mg9lJtz0hexRJZKFODWJG5dUh//
Bc2w8vywgYYoduXu4QLcoP17CA=='''


_OCSP_RESPONSE_PEM = f'''-----BEGIN OCSP RESPONSE-----\n{_OCSP_RESPONSE_B64}\n-----END OCSP RESPONSE-----\n'''

_CRL_B64 = '''MIIBzTCBtgIBATANBgkqhkiG9w0BAQsFADAiMQswCQYDVQQGEwJYWDETMBEGA1UE
Expand Down Expand Up @@ -236,6 +231,7 @@ def test_version(client):
rzDRkyHn2a+5mqqc2J9asb6RFUs=
-----END X509 CRL-----'''


def _assert_validationerror_list_present(resp):
j = resp.json()

Expand All @@ -255,7 +251,7 @@ def test_groups(client):
j = resp.json()

names = {lg['name'] for lg in j}
assert names == {'cabf-serverauth', 'cabf-smime', 'etsi'}
assert names == {'cabf-serverauth', 'cabf-smime', 'etsi', 'pkix'}


def test_group_no_exist(client):
Expand Down Expand Up @@ -513,9 +509,9 @@ def test_crl_pkix_validations_list(client, validity_additional_validators=None,
j = resp.json()

v = crl.create_pkix_crl_validator_container([
pkix.create_attribute_decoder(name.ATTRIBUTE_TYPE_MAPPINGS),
pkix.create_extension_decoder(extension.EXTENSION_MAPPINGS),
],
pkix.create_attribute_decoder(name.ATTRIBUTE_TYPE_MAPPINGS),
pkix.create_extension_decoder(extension.EXTENSION_MAPPINGS),
],
[
crl.create_issuer_validator_container(
[]
Expand All @@ -532,6 +528,7 @@ def test_crl_pkix_validations_list(client, validity_additional_validators=None,
assert actual['code'] == expected.code
assert actual['severity'] == str(expected.severity)


def test_crl_pkix_lint(client):
resp = client.post('/crl/pkix/crl', json={'b64': _CRL_B64})
assert resp.status_code == HTTPStatus.OK
Expand All @@ -540,6 +537,7 @@ def test_crl_pkix_lint(client):

assert len(j['results']) == 0


def test_crl_pkix_lint_pem(client):
resp = client.post('/crl/pkix/crl', json={'pem': _CRL_PEM})
assert resp.status_code == HTTPStatus.OK
Expand All @@ -548,14 +546,39 @@ def test_crl_pkix_lint_pem(client):

assert len(j['results']) == 0


def test_crl_pkix_lint_b64_in_pem_field(client):
resp = client.post('/crl/pkix/crl', json={'pem': _CRL_B64})
assert resp.status_code == HTTPStatus.UNPROCESSABLE_ENTITY


def test_crl_pkix_lint_pem_with_error(client):
resp = client.post('/crl/pkix/crl', json={'pem': _CRL_PEM_EXPECT_ERROR})
assert resp.status_code == HTTPStatus.OK

j = resp.json()

assert len(j['results']) == 3


def test_pkix_certificate_group(client):
resp = client.get('/certificate/pkix')
assert resp.status_code == HTTPStatus.OK

j = resp.json()

assert j['linters'][0]['name'] == 'certificate'


def test_pkix_certificate_lint(client):
resp = client.post('/certificate/pkix', json={'pem': _OV_FINAL_CLEAN_PEM})

assert resp.status_code == HTTPStatus.OK

j = resp.json()

assert j['linter']['name'] == 'certificate'

r = j['results'][0]

assert r['finding_descriptions'][0]['code'] == 'pkix.certificate_skid_end_entity_missing'

0 comments on commit 45c3d69

Please sign in to comment.