Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate validation report as JSON #23

Merged
merged 8 commits into from
Apr 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ private: install cleanup
--sector private

validate-public:
spid-compliant-certificates validator
spid-compliant-certificates validator --out-file report.json

validate-private:
spid-compliant-certificates validator --sector private
spid-compliant-certificates validator --sector private --out-file report.json

docker-build:
docker build --tag $(REPO):$(GIT_COMMIT) .
Expand Down
50 changes: 47 additions & 3 deletions bin/spid-compliant-certificates
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ from spid_compliant_certificates import version
from spid_compliant_certificates.commons import logger
from spid_compliant_certificates.generator import generate
from spid_compliant_certificates.validator import validate
from spid_compliant_certificates.validator.report import ReportSerializer

LOG = logger.LOG

Expand Down Expand Up @@ -67,7 +68,10 @@ def logo():

''') # noqa

# main

def _indent(txt: str, count=1) -> str:
i = ' '
return f'{i * count}{txt}'


if __name__ == '__main__':
Expand Down Expand Up @@ -205,6 +209,21 @@ if __name__ == '__main__':
type=pathlib.Path
)

parser_v.add_argument(
'--out-form',
action='store',
choices=['txt', 'json', 'xml'],
default='json',
help='select the output file format'
)

parser_v.add_argument(
'--out-file',
action='store',
help='file where the validation report will be saved',
type=pathlib.Path
)

logo()
args = parser.parse_args()

Expand Down Expand Up @@ -237,9 +256,34 @@ if __name__ == '__main__':
LOG.error(f'Unable to find certificate file {args.crt_file}')
sys.exit(1)
try:
LOG.info(f'Validating certificate {args.crt_file} '
LOG.info(f'Validating certificate {args.crt_file.absolute()} '
+ f'against {args.sector} sector specifications')
validate(args.crt_file, args.sector)
r = validate(args.crt_file, args.sector)

msg = f'Certificate {args.crt_file.absolute()} '
if r.is_success():
msg += f'matches the {args.sector} sector specifications'
LOG.info(msg)
else:
msg += f'violates the {args.sector} sector specifications'
LOG.error(msg)

for t in r.tests:
log = LOG.info if t.is_success() else LOG.error
log(_indent(t.description))
for c in t.checks:
log = LOG.info if c.is_success() else LOG.error
log(_indent(f'{c.description} (now: {c.value})', 2))

if args.out_file is not None:
msg = f'Saving report as {args.out_form.upper()} '
msg += f'in {args.out_file.absolute()}'
LOG.info(msg)
rs = ReportSerializer()
with open(args.out_file, 'wb') as fp:
fp.write(rs.serialize(r, args.out_form).encode())
fp.close()

except Exception as e:
LOG.error(e)
sys.exit(1)
Expand Down
2 changes: 1 addition & 1 deletion spid_compliant_certificates/commons/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _c(f: str) -> str:
fmt = '[%(levelname)1.1s] %(message)s'
_sh.setFormatter(logging.Formatter(fmt))
else:
levelname = _c('%(levelname)-5.5s')
levelname = _c('%(levelname)1.1s')
fmt = '[' + levelname + '] %(message)s'
_sh.setFormatter(logging.Formatter(fmt))
_sh.addFilter(ColourFilter())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List, Tuple
from typing import Any, List, Tuple

from cryptography import x509

SUCCESS = True
FAILURE = not SUCCESS


def basic_constraints(extensions: x509.Extensions) -> List[Tuple[bool, str]]:
def basic_constraints(extensions: x509.Extensions) -> List[Tuple[bool, str, Any]]: # noqa
checks = []

# basicConstraints: CA:FALSE
Expand All @@ -36,15 +36,15 @@ def basic_constraints(extensions: x509.Extensions) -> List[Tuple[bool, str]]:
try:
ext = extensions.get_extension_for_class(ext_cls)

msg = f'{ext_name} can not be set as critical'
msg = f'{ext_name} must be not critical'
res = FAILURE if ext.critical else SUCCESS
checks.append((res, msg))
checks.append((res, msg, ext.critical))

msg = 'CA must be FALSE'
msg = 'CA extension property must be FALSE'
res = FAILURE if ext.value.ca else SUCCESS
checks.append((res, msg))
checks.append((res, msg, ext.value.ca))
except x509.ExtensionNotFound:
msg = f'{ext_name} must be present'
checks.append((FAILURE, msg))
checks.append((FAILURE, msg, None))

return checks
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List, Tuple
from typing import Any, List, Tuple

from cryptography import x509

SUCCESS = True
FAILURE = not SUCCESS


def certificate_policies(extensions: x509.Extensions, sector: str) -> List[Tuple[bool, str]]: # noqa
def certificate_policies(extensions: x509.Extensions, sector: str) -> List[Tuple[bool, str, Any]]: # noqa
checks = []

# certificatePolicies: agIDcert(agIDcert)
Expand All @@ -48,9 +48,9 @@ def certificate_policies(extensions: x509.Extensions, sector: str) -> List[Tuple
ext = extensions.get_extension_for_class(ext_cls)

# check if critical
msg = f'{ext_name} can not be set as critical'
msg = f'{ext_name} must be not critical'
res = FAILURE if ext.critical else SUCCESS
checks.append((res, msg))
checks.append((res, msg, ext.critical))

# check if expected policies are present
policies = ext.value
Expand All @@ -60,7 +60,7 @@ def certificate_policies(extensions: x509.Extensions, sector: str) -> List[Tuple
)
msg = f'policy {ep} must be present'
res = SUCCESS if is_present else FAILURE
checks.append((res, msg))
checks.append((res, msg, is_present))

# check the content of the policies
for p in policies:
Expand All @@ -72,10 +72,10 @@ def certificate_policies(extensions: x509.Extensions, sector: str) -> List[Tuple
etext = q.explicit_text

msg = f'policy {oid} must have '
msg += f'UserNotice.ExplicitText={exp_etext} (now: {etext})' # noqa
msg += f'UserNotice.ExplicitText={exp_etext}' # noqa

res = FAILURE if etext != exp_etext else SUCCESS
checks.append((res, msg))
checks.append((res, msg, etext))

if sector == 'public' and oid == '1.3.76.16.4.2.1':
for q in p.policy_qualifiers:
Expand All @@ -84,23 +84,23 @@ def certificate_policies(extensions: x509.Extensions, sector: str) -> List[Tuple
etext = q.explicit_text

msg = f'policy {oid} must have '
msg += f'UserNotice.ExplicitText={exp_etext} (now: {etext})' # noqa
msg += f'UserNotice.ExplicitText={exp_etext}' # noqa

res = FAILURE if etext != exp_etext else SUCCESS
checks.append((res, msg))
checks.append((res, msg, etext))
if sector == 'private' and oid == '1.3.76.16.4.3.1':
for q in p.policy_qualifiers:
if isinstance(q, x509.extensions.UserNotice):
exp_etext = 'cert_SP_Priv'
etext = q.explicit_text

msg = f'policy {oid} must have '
msg += f'UserNotice.ExplicitText={exp_etext} (now: {etext})' # noqa
msg += f'UserNotice.ExplicitText={exp_etext}' # noqa

res = FAILURE if etext != exp_etext else SUCCESS
checks.append((res, msg))
except x509.ExtensionNotFound:
checks.append((res, msg, etext))
except x509.ExtensionNotFound as e:
msg = f'{ext_name} must be present'
checks.append((FAILURE, msg))
checks.append((FAILURE, msg, str(e)))

return checks
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List, Tuple
from typing import Any, List, Tuple

from cryptography.hazmat.primitives import hashes

Expand All @@ -31,11 +31,11 @@
]


def digest_algorithm(alg: str) -> List[Tuple[bool, str]]:
def digest_algorithm(alg: str) -> List[Tuple[bool, str, Any]]:
checks = []

msg = f'The digest algorithm must be one of {ALLOWED_ALGS} (now: {alg})'
msg = f'The digest algorithm must be one of {ALLOWED_ALGS}'
res = FAILURE if alg not in ALLOWED_ALGS else SUCCESS
checks.append((res, msg))
checks.append((res, msg, alg))

return checks
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List, Tuple
from typing import Any, List, Tuple

from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa
Expand All @@ -33,7 +33,7 @@
]


def key_type_and_size(cert: x509.Certificate) -> List[Tuple[bool, str]]:
def key_type_and_size(cert: x509.Certificate) -> List[Tuple[bool, str, Any]]:
checks = []

# get the public key
Expand All @@ -44,18 +44,18 @@ def key_type_and_size(cert: x509.Certificate) -> List[Tuple[bool, str]]:
pk_type = 'RSA' if isinstance(pk, rsa.RSAPublicKey) else 'NOT ALLOWED'
msg = f'The keypair must be {exp_pk_type}'
res = FAILURE if pk_type != exp_pk_type else SUCCESS
checks.append((res, msg))
checks.append((res, msg, pk_type))

# check the key size
min_size = 2048
size = pk.key_size

msg = f'The key size must be greater than or equal to {min_size} (now: {size})' # noqa
msg = f'The key size must be greater than or equal to {min_size}'
res = FAILURE if size < min_size else SUCCESS
checks.append((res, msg))
checks.append((res, msg, size))

msg = f'The key size must be one of {ALLOWED_SIZES} (now: {size})'
msg = f'The key size must be one of {ALLOWED_SIZES}'
res = FAILURE if size not in ALLOWED_SIZES else SUCCESS
checks.append((res, msg))
checks.append((res, msg, size))

return checks
28 changes: 15 additions & 13 deletions spid_compliant_certificates/validator/checks/key_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
# SOFTWARE.


from typing import List, Tuple
from typing import Any, List, Tuple

from cryptography import x509

SUCCESS = True
FAILURE = not SUCCESS


def key_usage(extensions: x509.Extensions) -> List[Tuple[bool, str]]:
def key_usage(extensions: x509.Extensions) -> List[Tuple[bool, str, Any]]:
checks = []

# keyUsage: critical;nonRepudiation
Expand All @@ -37,23 +37,25 @@ def key_usage(extensions: x509.Extensions) -> List[Tuple[bool, str]]:
try:
ext = extensions.get_extension_for_class(ext_cls)

msg = f'{ext_name} must be set as critical'
res = FAILURE if not ext.critical else SUCCESS
checks.append((res, msg))
msg = f'{ext_name} must be critical'
res = SUCCESS if ext.critical else FAILURE
checks.append((res, msg, ext.critical))

for usage in ['content_commitment', 'digital_signature']:
msg = f'{usage} must be set'
res = FAILURE if not getattr(ext.value, usage) else SUCCESS
checks.append((res, msg))
msg = f'{usage} bit must be set'
val = getattr(ext.value, usage)
res = SUCCESS if val else FAILURE
checks.append((res, msg, val))

for usage in ['crl_sign', 'data_encipherment', 'key_agreement',
'key_cert_sign', 'key_encipherment']:
msg = f'{usage} must be unset'
res = FAILURE if getattr(ext.value, usage) else SUCCESS
checks.append((res, msg))
except x509.ExtensionNotFound:
msg = f'{usage} bit must be unset'
val = getattr(ext.value, usage)
res = SUCCESS if not val else FAILURE
checks.append((res, msg, val))
except x509.ExtensionNotFound as e:
msg = f'{ext_name} must be present'
res = FAILURE
checks.append((res, msg))
checks.append((res, msg, str(e)))

return checks
Loading