diff options
Diffstat (limited to 'rsa/pkcs1.py')
-rw-r--r-- | rsa/pkcs1.py | 64 |
1 files changed, 43 insertions, 21 deletions
diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 84f0e3b..57b0276 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu> # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,9 +28,10 @@ to your users. import hashlib import os +import sys +import typing -from rsa._compat import range -from rsa import common, transform, core +from . import common, transform, core, key # ASN.1 codes that describe the hash algorithm used. HASH_ASN1 = { @@ -54,6 +53,21 @@ HASH_METHODS = { } +if sys.version_info >= (3, 6): + # Python 3.6 introduced SHA3 support. + HASH_ASN1.update({ + 'SHA3-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20', + 'SHA3-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30', + 'SHA3-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40', + }) + + HASH_METHODS.update({ + 'SHA3-256': hashlib.sha3_256, + 'SHA3-384': hashlib.sha3_384, + 'SHA3-512': hashlib.sha3_512, + }) + + class CryptoError(Exception): """Base class for all exceptions in this module.""" @@ -66,7 +80,7 @@ class VerificationError(CryptoError): """Raised when verification fails.""" -def _pad_for_encryption(message, target_length): +def _pad_for_encryption(message: bytes, target_length: int) -> bytes: r"""Pads the message for encryption, returning the padded message. :return: 00 02 RANDOM_DATA 00 MESSAGE @@ -112,7 +126,7 @@ def _pad_for_encryption(message, target_length): message]) -def _pad_for_signing(message, target_length): +def _pad_for_signing(message: bytes, target_length: int) -> bytes: r"""Pads the message for signing, returning the padded message. The padding is always a repetition of FF bytes. @@ -146,7 +160,7 @@ def _pad_for_signing(message, target_length): message]) -def encrypt(message, pub_key): +def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes: """Encrypts the given message using PKCS#1 v1.5 :param message: the message to encrypt. Must be a byte string no longer than @@ -178,7 +192,7 @@ def encrypt(message, pub_key): return block -def decrypt(crypto, priv_key): +def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: r"""Decrypts the given message using PKCS#1 v1.5 The decryption is considered 'failed' when the resulting cleartext doesn't @@ -234,6 +248,12 @@ def decrypt(crypto, priv_key): decrypted = priv_key.blinded_decrypt(encrypted) cleartext = transform.int2bytes(decrypted, blocksize) + # Detect leading zeroes in the crypto. These are not reflected in the + # encrypted value (as leading zeroes do not influence the value of an + # integer). This fixes CVE-2020-13757. + if len(crypto) > blocksize: + raise DecryptionError('Decryption failed') + # If we can't find the cleartext marker, decryption failed. if cleartext[0:2] != b'\x00\x02': raise DecryptionError('Decryption failed') @@ -247,14 +267,13 @@ def decrypt(crypto, priv_key): return cleartext[sep_idx + 1:] -def sign_hash(hash_value, priv_key, hash_method): +def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes: """Signs a precomputed hash with the private key. Hashes the message, then signs the hash with the given key. This is known as a "detached signature", because the message itself isn't altered. - :param hash_value: A precomputed hash to sign (ignores message). Should be set to - None if needing to hash and sign message. + :param hash_value: A precomputed hash to sign (ignores message). :param priv_key: the :py:class:`rsa.PrivateKey` to sign with :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1', 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'. @@ -281,7 +300,7 @@ def sign_hash(hash_value, priv_key, hash_method): return block -def sign(message, priv_key, hash_method): +def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes: """Signs the message with the private key. Hashes the message, then signs the hash with the given key. This is known @@ -303,7 +322,7 @@ def sign(message, priv_key, hash_method): return sign_hash(msg_hash, priv_key, hash_method) -def verify(message, signature, pub_key): +def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str: """Verifies that the signature matches the message. The hash method is detected automatically from the signature. @@ -331,6 +350,9 @@ def verify(message, signature, pub_key): cleartext = HASH_ASN1[method_name] + message_hash expected = _pad_for_signing(cleartext, keylength) + if len(signature) != keylength: + raise VerificationError('Verification failed') + # Compare with the signed one if expected != clearsig: raise VerificationError('Verification failed') @@ -338,7 +360,7 @@ def verify(message, signature, pub_key): return method_name -def find_signature_hash(signature, pub_key): +def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str: """Returns the hash name detected from the signature. If you also want to verify the message, use :py:func:`rsa.verify()` instead. @@ -357,7 +379,7 @@ def find_signature_hash(signature, pub_key): return _find_method_hash(clearsig) -def yield_fixedblocks(infile, blocksize): +def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]: """Generator, yields each block of ``blocksize`` bytes in the input file. :param infile: file to read and separate in blocks. @@ -378,7 +400,7 @@ def yield_fixedblocks(infile, blocksize): break -def compute_hash(message, method_name): +def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes: """Returns the message digest. :param message: the signed message. Can be an 8-bit string or a file-like @@ -395,18 +417,18 @@ def compute_hash(message, method_name): method = HASH_METHODS[method_name] hasher = method() - if hasattr(message, 'read') and hasattr(message.read, '__call__'): + if isinstance(message, bytes): + hasher.update(message) + else: + assert hasattr(message, 'read') and hasattr(message.read, '__call__') # read as 1K blocks for block in yield_fixedblocks(message, 1024): hasher.update(block) - else: - # hash the message object itself. - hasher.update(message) return hasher.digest() -def _find_method_hash(clearsig): +def _find_method_hash(clearsig: bytes) -> str: """Finds the hash method. :param clearsig: full padded ASN1 and hash. |