aboutsummaryrefslogtreecommitdiff
path: root/rsa/pkcs1.py
diff options
context:
space:
mode:
Diffstat (limited to 'rsa/pkcs1.py')
-rw-r--r--rsa/pkcs1.py64
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.