1""" 
    2<Module Name> 
    3  rsa.py 
    4 
    5<Author> 
    6  Santiago Torres-Arias <santiago@nyu.edu> 
    7 
    8<Started> 
    9  Nov 15, 2017 
    10 
    11<Copyright> 
    12  See LICENSE for licensing information. 
    13 
    14<Purpose> 
    15  RSA-specific handling routines for signature verification and key parsing 
    16""" 
    17 
    18import binascii 
    19 
    20CRYPTO = True 
    21NO_CRYPTO_MSG = "RSA key support for GPG requires the cryptography library" 
    22try: 
    23    from cryptography.exceptions import InvalidSignature 
    24    from cryptography.hazmat import backends 
    25    from cryptography.hazmat.primitives.asymmetric import padding, rsa, utils 
    26except ImportError: 
    27    CRYPTO = False 
    28 
    29# ruff: noqa: E402 
    30from securesystemslib import exceptions 
    31from securesystemslib._gpg import util as gpg_util 
    32from securesystemslib._gpg.exceptions import PacketParsingError 
    33 
    34 
    35def create_pubkey(pubkey_info): 
    36    """ 
    37    <Purpose> 
    38      Create and return an RSAPublicKey object from the passed pubkey_info 
    39      using pyca/cryptography. 
    40 
    41    <Arguments> 
    42      pubkey_info: 
    43              An RSA pubkey dict. 
    44 
    45    <Exceptions> 
    46      securesystemslib.exceptions.UnsupportedLibraryError if 
    47        the cryptography module is unavailable 
    48 
    49    <Returns> 
    50      A cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey based on the 
    51      passed pubkey_info. 
    52 
    53    """ 
    54    if not CRYPTO:  # pragma: no cover 
    55        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) 
    56 
    57    e = int(pubkey_info["keyval"]["public"]["e"], 16) 
    58    n = int(pubkey_info["keyval"]["public"]["n"], 16) 
    59    pubkey = rsa.RSAPublicNumbers(e, n).public_key(backends.default_backend()) 
    60 
    61    return pubkey 
    62 
    63 
    64def get_pubkey_params(data): 
    65    """ 
    66    <Purpose> 
    67      Parse the public key parameters as multi-precision-integers. 
    68 
    69    <Arguments> 
    70      data: 
    71             the RFC4880-encoded public key parameters data buffer as described 
    72             in the fifth paragraph of section 5.5.2. 
    73 
    74    <Exceptions> 
    75      securesystemslib._gpg.exceptions.PacketParsingError: 
    76             if the public key parameters are malformed 
    77 
    78    <Side Effects> 
    79      None. 
    80 
    81    <Returns> 
    82      An RSA public key dict. 
    83    """ 
    84    ptr = 0 
    85 
    86    modulus_length = gpg_util.get_mpi_length(data[ptr : ptr + 2]) 
    87    ptr += 2 
    88    modulus = data[ptr : ptr + modulus_length] 
    89    if len(modulus) != modulus_length:  # pragma: no cover 
    90        raise PacketParsingError("This modulus MPI was truncated!") 
    91    ptr += modulus_length 
    92 
    93    exponent_e_length = gpg_util.get_mpi_length(data[ptr : ptr + 2]) 
    94    ptr += 2 
    95    exponent_e = data[ptr : ptr + exponent_e_length] 
    96    if len(exponent_e) != exponent_e_length:  # pragma: no cover 
    97        raise PacketParsingError("This e MPI has been truncated!") 
    98 
    99    return { 
    100        "e": binascii.hexlify(exponent_e).decode("ascii"), 
    101        "n": binascii.hexlify(modulus).decode("ascii"), 
    102    } 
    103 
    104 
    105def get_signature_params(data): 
    106    """ 
    107    <Purpose> 
    108      Parse the signature parameters as multi-precision-integers. 
    109 
    110    <Arguments> 
    111      data: 
    112             the RFC4880-encoded signature data buffer as described 
    113             in the third paragraph of section 5.2.2. 
    114 
    115    <Exceptions> 
    116      securesystemslib._gpg.exceptions.PacketParsingError: 
    117             if the public key parameters are malformed 
    118 
    119    <Side Effects> 
    120      None. 
    121 
    122    <Returns> 
    123      The decoded signature buffer 
    124    """ 
    125 
    126    ptr = 0 
    127    signature_length = gpg_util.get_mpi_length(data[ptr : ptr + 2]) 
    128    ptr += 2 
    129    signature = data[ptr : ptr + signature_length] 
    130    if len(signature) != signature_length:  # pragma: no cover 
    131        raise PacketParsingError("This signature was truncated!") 
    132 
    133    return signature 
    134 
    135 
    136def verify_signature(signature_object, pubkey_info, content, hash_algorithm_id): 
    137    """ 
    138    <Purpose> 
    139      Verify the passed signature against the passed content with the passed 
    140      RSA public key using pyca/cryptography. 
    141 
    142    <Arguments> 
    143      signature_object: 
    144              A signature dict. 
    145 
    146      pubkey_info: 
    147              The RSA public key dict. 
    148 
    149      content: 
    150              The signed bytes against which the signature is verified 
    151 
    152      hash_algorithm_id: 
    153              one of SHA1, SHA256, SHA512 (see securesystemslib._gpg.constants) 
    154              used to verify the signature 
    155              NOTE: Overrides any hash algorithm specification in "pubkey_info"'s 
    156              "hashes" or "method" fields. 
    157 
    158    <Exceptions> 
    159      securesystemslib.exceptions.UnsupportedLibraryError if: 
    160        the cryptography module is unavailable 
    161 
    162      ValueError: 
    163        if the passed hash_algorithm_id is not supported (see 
    164        securesystemslib._gpg.util.get_hashing_class) 
    165 
    166    <Returns> 
    167      True if signature verification passes and False otherwise 
    168 
    169    """ 
    170    if not CRYPTO:  # pragma: no cover 
    171        raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) 
    172 
    173    hasher = gpg_util.get_hashing_class(hash_algorithm_id) 
    174 
    175    pubkey_object = create_pubkey(pubkey_info) 
    176 
    177    # zero-pad the signature due to a discrepancy between the openssl backend 
    178    # and the gnupg interpretation of PKCSv1.5. Read more at: 
    179    # https://github.com/in-toto/in-toto/issues/171#issuecomment-440039256 
    180    # we are skipping this if on the tests because well, how would one test this 
    181    # deterministically. 
    182    pubkey_length = len(pubkey_info["keyval"]["public"]["n"]) 
    183    signature_length = len(signature_object["signature"]) 
    184    if pubkey_length != signature_length:  # pragma: no cover 
    185        zero_pad = "0" * (pubkey_length - signature_length) 
    186        signature_object["signature"] = "{}{}".format( 
    187            zero_pad, signature_object["signature"] 
    188        ) 
    189 
    190    digest = gpg_util.hash_object( 
    191        binascii.unhexlify(signature_object["other_headers"]), hasher(), content 
    192    ) 
    193 
    194    try: 
    195        pubkey_object.verify( 
    196            binascii.unhexlify(signature_object["signature"]), 
    197            digest, 
    198            padding.PKCS1v15(), 
    199            utils.Prehashed(hasher()), 
    200        ) 
    201        return True 
    202    except InvalidSignature: 
    203        return False