add-quality-check-script #20
1 changed files with 77 additions and 31 deletions
|
@ -3,13 +3,12 @@ import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import typing
|
import typing
|
||||||
from datetime import datetime, timezone
|
|
||||||
from typing import NamedTuple
|
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.exceptions import UnsupportedAlgorithm
|
from cryptography.exceptions import UnsupportedAlgorithm
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa, ec
|
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||||
from sqlalchemy import create_engine, select, MetaData, Table
|
from datetime import datetime, timezone
|
||||||
|
from sqlalchemy import MetaData, Table, create_engine, select
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
class CheckResult(NamedTuple):
|
class CheckResult(NamedTuple):
|
||||||
|
@ -32,18 +31,27 @@ CODE_UNSUPPORTED_FORMAT = CheckResult("unsupported format", 1001)
|
||||||
CODE_EMPTY = CheckResult("empty", 1002)
|
CODE_EMPTY = CheckResult("empty", 1002)
|
||||||
CODE_DEPRECATED_SPKAC = CheckResult("deprecated SPKAC", 1003)
|
CODE_DEPRECATED_SPKAC = CheckResult("deprecated SPKAC", 1003)
|
||||||
CODE_INVALID_SIGNATURE = CheckResult("invalid signature", 1004)
|
CODE_INVALID_SIGNATURE = CheckResult("invalid signature", 1004)
|
||||||
CODE_UNSUPPORTED_SIGNATURE_ALGORITHM = CheckResult("unsupported signature algorithm", 1005)
|
CODE_UNSUPPORTED_SIGNATURE_ALGORITHM = CheckResult(
|
||||||
|
"unsupported signature algorithm", 1005
|
||||||
|
)
|
||||||
CODE_PUBLIC_KEY_TOO_WEAK = CheckResult("public key too weak", 1006)
|
CODE_PUBLIC_KEY_TOO_WEAK = CheckResult("public key too weak", 1006)
|
||||||
CODE_UNSUPPORTED_PUBLIC_KEY = CheckResult("unsupported public key", 1007)
|
CODE_UNSUPPORTED_PUBLIC_KEY = CheckResult("unsupported public key", 1007)
|
||||||
CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH = CheckResult("CSR and CRT public key mismatch", 1008)
|
CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH = CheckResult(
|
||||||
|
"CSR and CRT public key mismatch", 1008
|
||||||
|
)
|
||||||
CODE_CERTIFICATE_FOR_INVALID_CSR = CheckResult("certificate for invalid CSR", 1009)
|
CODE_CERTIFICATE_FOR_INVALID_CSR = CheckResult("certificate for invalid CSR", 1009)
|
||||||
CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE = CheckResult("not signed by expected CA", 1010)
|
CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE = CheckResult(
|
||||||
|
"not signed by expected CA", 1010
|
||||||
|
)
|
||||||
CODE_CERTIFICATE_IS_EXPIRED = CheckResult("certificate is expired", 1011)
|
CODE_CERTIFICATE_IS_EXPIRED = CheckResult("certificate is expired", 1011)
|
||||||
|
|
||||||
SUPPORTED_SIGNATURE_ALGORITHMS = [
|
SUPPORTED_SIGNATURE_ALGORITHMS = [
|
||||||
x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA256, x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA384,
|
x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA256,
|
||||||
x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA512, x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA256,
|
x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA384,
|
||||||
x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA384, x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA512
|
x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA512,
|
||||||
|
x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA256,
|
||||||
|
x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA384,
|
||||||
|
x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA512,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,21 +107,36 @@ class Counters:
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
f"good CSR and certificate: {self.good}\n"
|
(
|
||||||
f"good CSR, issue with certificate: {self.good_csr}\n"
|
f"good CSR and certificate: {self.good}\n"
|
||||||
f"good certificate, issue with CSR: {self.good_crt}\n"
|
f"good CSR, issue with certificate: {self.good_csr}\n"
|
||||||
f"failed CSR and certificate: {self.fail}\n"
|
f"good certificate, issue with CSR: {self.good_crt}\n"
|
||||||
f"missing certificate: {self.missing_crt}\n"
|
f"failed CSR and certificate: {self.fail}\n"
|
||||||
f"expired certificate: {self.expired_crt}\n\nCSR results:\n"
|
f"missing certificate: {self.missing_crt}\n"
|
||||||
) + "\n".join([f"{code}: {count:d}" for code, count in sorted(self.csr_codes.items())]) + (
|
f"expired certificate: {self.expired_crt}\n\nCSR results:\n"
|
||||||
"\n\nCertificate results:\n"
|
)
|
||||||
) + (
|
+ "\n".join(
|
||||||
"\n".join([f"{code}: {count:d}" for code, count in sorted(self.crt_codes.items())])
|
[f"{code}: {count:d}" for code, count in sorted(self.csr_codes.items())]
|
||||||
|
)
|
||||||
|
+ ("\n\nCertificate results:\n")
|
||||||
|
+ (
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
f"{code}: {count:d}"
|
||||||
|
for code, count in sorted(self.crt_codes.items())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Analyzer:
|
class Analyzer:
|
||||||
def __init__(self, logger: logging.Logger, dsn: str, ca_certificates: dict[int, x509.Certificate]):
|
def __init__(
|
||||||
|
self,
|
||||||
|
logger: logging.Logger,
|
||||||
|
dsn: str,
|
||||||
|
ca_certificates: dict[int, x509.Certificate],
|
||||||
|
):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.engine = create_engine(dsn)
|
self.engine = create_engine(dsn)
|
||||||
self.ca_certificates = ca_certificates
|
self.ca_certificates = ca_certificates
|
||||||
|
@ -138,10 +161,17 @@ class Analyzer:
|
||||||
crt_code = self.check_crt(row.crt_name, ca_cert, public_numbers)
|
crt_code = self.check_crt(row.crt_name, ca_cert, public_numbers)
|
||||||
self.c.count_crt(crt_code)
|
self.c.count_crt(crt_code)
|
||||||
|
|
||||||
if csr_code != CODE_OK and crt_code not in (CODE_OK, CODE_CERTIFICATE_IS_EXPIRED):
|
if csr_code != CODE_OK and crt_code not in (
|
||||||
|
CODE_OK,
|
||||||
|
CODE_CERTIFICATE_IS_EXPIRED,
|
||||||
|
):
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"%06d %s: %06d -> csr_code: %s, crt_code: %s",
|
"%06d %s: %06d -> csr_code: %s, crt_code: %s",
|
||||||
self.c.fail, table, row.id, csr_code, crt_code
|
self.c.fail,
|
||||||
|
table,
|
||||||
|
row.id,
|
||||||
|
csr_code,
|
||||||
|
crt_code,
|
||||||
)
|
)
|
||||||
self.c.count_fail()
|
self.c.count_fail()
|
||||||
return
|
return
|
||||||
|
@ -156,7 +186,10 @@ class Analyzer:
|
||||||
self.c.count_missing_crt()
|
self.c.count_missing_crt()
|
||||||
return
|
return
|
||||||
|
|
||||||
if csr_code == CODE_OK and crt_code not in (CODE_OK, CODE_CERTIFICATE_IS_EXPIRED):
|
if csr_code == CODE_OK and crt_code not in (
|
||||||
|
CODE_OK,
|
||||||
|
CODE_CERTIFICATE_IS_EXPIRED,
|
||||||
|
):
|
||||||
self.c.count_good_csr()
|
self.c.count_good_csr()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -207,7 +240,12 @@ class Analyzer:
|
||||||
|
|
||||||
return CODE_OK, public_key.public_numbers()
|
return CODE_OK, public_key.public_numbers()
|
||||||
|
|
||||||
def check_crt(self, crt_name: str, ca_certificate: x509.Certificate, public_numbers: typing.Any) -> CheckResult:
|
def check_crt(
|
||||||
|
self,
|
||||||
|
crt_name: str,
|
||||||
|
ca_certificate: x509.Certificate,
|
||||||
|
public_numbers: typing.Any,
|
||||||
|
) -> CheckResult:
|
||||||
if not crt_name:
|
if not crt_name:
|
||||||
return CODE_EMPTY
|
return CODE_EMPTY
|
||||||
|
|
||||||
|
@ -248,9 +286,13 @@ class Analyzer:
|
||||||
try:
|
try:
|
||||||
crt.verify_directly_issued_by(ca_certificate)
|
crt.verify_directly_issued_by(ca_certificate)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.debug("certificate verification failed: %s\n issuer of certificate: %s\n"
|
self.logger.debug(
|
||||||
" CA certificate: %s", e, crt.issuer,
|
"certificate verification failed: %s\n issuer of certificate: %s\n"
|
||||||
ca_certificate.subject)
|
" CA certificate: %s",
|
||||||
|
e,
|
||||||
|
crt.issuer,
|
||||||
|
ca_certificate.subject,
|
||||||
|
)
|
||||||
return CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE
|
return CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE
|
||||||
|
|
||||||
return CODE_OK
|
return CODE_OK
|
||||||
|
@ -266,10 +308,14 @@ def main():
|
||||||
db_port = os.getenv("DB_PORT", "3306")
|
db_port = os.getenv("DB_PORT", "3306")
|
||||||
db_name = os.getenv("DB_NAME", "cacert")
|
db_name = os.getenv("DB_NAME", "cacert")
|
||||||
root_ca_cert: str = os.getenv("ROOT_CA_CERTIFICATE", "../www/certs/root_X0F.crt")
|
root_ca_cert: str = os.getenv("ROOT_CA_CERTIFICATE", "../www/certs/root_X0F.crt")
|
||||||
sub_ca_cert = os.getenv("SUB_CA_CERTIFICATE", "../www/certs/CAcert_Class3Root_x14E228.crt")
|
sub_ca_cert = os.getenv(
|
||||||
|
"SUB_CA_CERTIFICATE", "../www/certs/CAcert_Class3Root_x14E228.crt"
|
||||||
|
)
|
||||||
debug = bool(os.getenv("DEBUG", "false"))
|
debug = bool(os.getenv("DEBUG", "false"))
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s"
|
||||||
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
|
|
Loading…
Reference in a new issue