Source code for dioptra.sdk.cryptography.verify

# This Software (Dioptra) is being made available as a public service by the
# National Institute of Standards and Technology (NIST), an Agency of the United
# States Department of Commerce. This software was developed in part by employees of
# NIST and in part by NIST contractors. Copyright in portions of this software that
# were developed by NIST contractors has been licensed or assigned to NIST. Pursuant
# to Title 17 United States Code Section 105, works of NIST employees are not
# subject to copyright protection in the United States. However, NIST may hold
# international copyright in software created by its employees and domestic
# copyright (or licensing rights) in portions of software that were assigned or
# licensed to NIST. To the extent that NIST holds copyright in this software, it is
# being made available under the Creative Commons Attribution 4.0 International
# license (CC BY 4.0). The disclaimers of the CC BY 4.0 license apply to all parts
# of the software developed or licensed by NIST.
#
# ACCESS THE FULL CC BY 4.0 LICENSE HERE:
# https://creativecommons.org/licenses/by/4.0/legalcode
#
# The load_public_key, load_signature, and verify_payload functions are adapted from the
# following source:
#
#     ErikusMaximus (https://stackoverflow.com/users/3508142/erikusmaximus), How to
#         verify a signed file in python, URL (version: 2019-07-02):
#         https://stackoverflow.com/q/51331461

from __future__ import annotations

import base64

import click
import structlog
from structlog.stdlib import BoundLogger

from dioptra.sdk.exceptions import CryptographyDependencyError
from dioptra.sdk.utilities.decorators import require_package

from .common import load_payload

LOGGER: BoundLogger = structlog.stdlib.get_logger()


try:
    from cryptography.exceptions import InvalidSignature
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
    from cryptography.hazmat.primitives.serialization import load_pem_public_key

except ImportError:  # pragma: nocover
    LOGGER.warn(
        "Unable to import one or more optional packages, functionality may be reduced",
        package="cryptography",
    )


[docs]@require_package("cryptography", exc_type=CryptographyDependencyError) def load_public_key(filepath: str) -> RSAPublicKey: """Load the public RSA key from a file""" with open(filepath, "rb") as f: public_key: RSAPublicKey = load_pem_public_key(f.read(), default_backend()) return public_key
[docs]def load_signature(filepath: str) -> bytes: """Load the signature""" with open(filepath, "rb") as f: signature: bytes = base64.b64decode(f.read()) return signature
[docs]@require_package("cryptography", exc_type=CryptographyDependencyError) def verify_payload(payload: bytes, signature: bytes, public_key: RSAPublicKey) -> bool: """Verify the payload signature""" try: public_key.verify( signature, payload, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(), ) except InvalidSignature: raise InvalidSignature( "Payload and/or signature files failed verification" ) from None return True
@click.command() @click.option( "--public-key-file", type=click.Path( exists=True, file_okay=True, dir_okay=False, resolve_path=True, readable=True ), required=True, help="File with public key to use for signing", ) @click.option( "--payload-file", type=click.Path( exists=True, file_okay=True, dir_okay=False, resolve_path=True, readable=True ), required=True, help="Payload to verify with signature file", ) @click.option( "--signature-file", type=click.Path( exists=True, file_okay=True, dir_okay=False, resolve_path=True, readable=True ), required=True, help="File with the payload signature", ) def verify(public_key_file: str, payload_file: str, signature_file: str) -> bool: public_key: RSAPublicKey = load_public_key(filepath=public_key_file) payload: bytes = load_payload(filepath=payload_file) signature: bytes = load_signature(filepath=signature_file) try: verification: bool = verify_payload( payload=payload, signature=signature, public_key=public_key ) click.echo("OK") except InvalidSignature: click.echo("ERROR - Payload and/or signature files failed verification") verification = False return verification if __name__ == "__main__": _ = verify()