From 5d6603258881710fdd7aac866f6ec7445c864d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 15:16:18 +0100 Subject: [PATCH 001/142] Bumped version to 4.0-alpha --- rsa/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 0b287b8..bf48834 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -30,7 +30,7 @@ __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = "2016-03-17" -__version__ = '3.4' +__version__ = '4.0-alpha' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 3b41d25..a4d1d41 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ if __name__ == '__main__': setup(name='rsa', - version='3.4', + version='4.0-alpha', description='Pure-Python RSA implementation', author='Sybren A. Stuvel', author_email='sybren@stuvel.eu', From 83a81107342ba414064703e5919978782d6bee01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 15:04:04 +0100 Subject: [PATCH 002/142] Removed deprecated functionality. The following modules have been removed: - rsa._version133 - rsa._version200 - rsa.bigfile - rsa.varblock The encrypt/decrypt-bigfile CLI commands have also been removed. --- rsa/_version133.py | 441 ----------------------------------- rsa/_version200.py | 513 ----------------------------------------- rsa/bigfile.py | 135 ----------- rsa/cli.py | 99 +------- rsa/pkcs1.py | 26 ++- rsa/varblock.py | 179 -------------- setup.py | 2 - tests/test_bigfile.py | 73 ------ tests/test_varblock.py | 88 ------- 9 files changed, 24 insertions(+), 1532 deletions(-) delete mode 100644 rsa/_version133.py delete mode 100644 rsa/_version200.py delete mode 100644 rsa/bigfile.py delete mode 100644 rsa/varblock.py delete mode 100644 tests/test_bigfile.py delete mode 100644 tests/test_varblock.py diff --git a/rsa/_version133.py b/rsa/_version133.py deleted file mode 100644 index ff03b45..0000000 --- a/rsa/_version133.py +++ /dev/null @@ -1,441 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Deprecated version of the RSA module - -.. deprecated:: 2.0 - - This submodule is deprecated and will be completely removed as of version 4.0. - -Module for calculating large primes, and RSA encryption, decryption, -signing and verification. Includes generating public and private keys. - -WARNING: this code implements the mathematics of RSA. It is not suitable for -real-world secure cryptography purposes. It has not been reviewed by a security -expert. It does not include padding of data. There are many ways in which the -output of this module, when used without any modification, can be sucessfully -attacked. -""" - -__author__ = "Sybren Stuvel, Marloes de Boer and Ivo Tamboer" -__date__ = "2010-02-05" -__version__ = '1.3.3' - -# NOTE: Python's modulo can return negative numbers. We compensate for -# this behaviour using the abs() function - -try: - import cPickle as pickle -except ImportError: - import pickle -from pickle import dumps, loads -import base64 -import math -import os -import random -import sys -import types -import zlib - -from rsa._compat import byte - -# Display a warning that this insecure version is imported. -import warnings -warnings.warn('Insecure version of the RSA module is imported as %s, be careful' - % __name__) -warnings.warn('This submodule is deprecated and will be completely removed as of version 4.0.', - DeprecationWarning) - - -def gcd(p, q): - """Returns the greatest common divisor of p and q - - - >>> gcd(42, 6) - 6 - """ - if p 0: - string = "%s%s" % (byte(number & 0xFF), string) - number /= 256 - - return string - -def fast_exponentiation(a, p, n): - """Calculates r = a^p mod n - """ - result = a % n - remainders = [] - while p != 1: - remainders.append(p & 1) - p = p >> 1 - while remainders: - rem = remainders.pop() - result = ((a ** rem) * result ** 2) % n - return result - -def read_random_int(nbits): - """Reads a random integer of approximately nbits bits rounded up - to whole bytes""" - - nbytes = ceil(nbits/8.) - randomdata = os.urandom(nbytes) - return bytes2int(randomdata) - -def ceil(x): - """ceil(x) -> int(math.ceil(x))""" - - return int(math.ceil(x)) - -def randint(minvalue, maxvalue): - """Returns a random integer x with minvalue <= x <= maxvalue""" - - # Safety - get a lot of random data even if the range is fairly - # small - min_nbits = 32 - - # The range of the random numbers we need to generate - range = maxvalue - minvalue - - # Which is this number of bytes - rangebytes = ceil(math.log(range, 2) / 8.) - - # Convert to bits, but make sure it's always at least min_nbits*2 - rangebits = max(rangebytes * 8, min_nbits * 2) - - # Take a random number of bits between min_nbits and rangebits - nbits = random.randint(min_nbits, rangebits) - - return (read_random_int(nbits) % range) + minvalue - -def fermat_little_theorem(p): - """Returns 1 if p may be prime, and something else if p definitely - is not prime""" - - a = randint(1, p-1) - return fast_exponentiation(a, p-1, p) - -def jacobi(a, b): - """Calculates the value of the Jacobi symbol (a/b) - """ - - if a % b == 0: - return 0 - result = 1 - while a > 1: - if a & 1: - if ((a-1)*(b-1) >> 2) & 1: - result = -result - b, a = a, b % a - else: - if ((b ** 2 - 1) >> 3) & 1: - result = -result - a = a >> 1 - return result - -def jacobi_witness(x, n): - """Returns False if n is an Euler pseudo-prime with base x, and - True otherwise. - """ - - j = jacobi(x, n) % n - f = fast_exponentiation(x, (n-1)/2, n) - - if j == f: return False - return True - -def randomized_primality_testing(n, k): - """Calculates whether n is composite (which is always correct) or - prime (which is incorrect with error probability 2**-k) - - Returns False if the number if composite, and True if it's - probably prime. - """ - - q = 0.5 # Property of the jacobi_witness function - - # t = int(math.ceil(k / math.log(1/q, 2))) - t = ceil(k / math.log(1/q, 2)) - for i in range(t+1): - x = randint(1, n-1) - if jacobi_witness(x, n): return False - - return True - -def is_prime(number): - """Returns True if the number is prime, and False otherwise. - """ - - """ - if not fermat_little_theorem(number) == 1: - # Not prime, according to Fermat's little theorem - return False - """ - - if randomized_primality_testing(number, 5): - # Prime, according to Jacobi - return True - - # Not prime - return False - - -def getprime(nbits): - """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In - other words: nbits is rounded up to whole bytes. - """ - - nbytes = int(math.ceil(nbits/8.)) - - while True: - integer = read_random_int(nbits) - - # Make sure it's odd - integer |= 1 - - # Test for primeness - if is_prime(integer): break - - # Retry if not prime - - return integer - -def are_relatively_prime(a, b): - """Returns True if a and b are relatively prime, and False if they - are not. - """ - - d = gcd(a, b) - return (d == 1) - -def find_p_q(nbits): - """Returns a tuple of two different primes of nbits bits""" - - p = getprime(nbits) - while True: - q = getprime(nbits) - if not q == p: break - - return (p, q) - -def extended_euclid_gcd(a, b): - """Returns a tuple (d, i, j) such that d = gcd(a, b) = ia + jb - """ - - if b == 0: - return (a, 1, 0) - - q = abs(a % b) - r = long(a / b) - (d, k, l) = extended_euclid_gcd(b, q) - - return (d, l, k - l*r) - -# Main function: calculate encryption and decryption keys -def calculate_keys(p, q, nbits): - """Calculates an encryption and a decryption key for p and q, and - returns them as a tuple (e, d)""" - - n = p * q - phi_n = (p-1) * (q-1) - - while True: - # Make sure e has enough bits so we ensure "wrapping" through - # modulo n - e = getprime(max(8, nbits/2)) - if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break - - (d, i, j) = extended_euclid_gcd(e, phi_n) - - if not d == 1: - raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n)) - - if not (e * i) % phi_n == 1: - raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n)) - - return (e, i) - - -def gen_keys(nbits): - """Generate RSA keys of nbits bits. Returns (p, q, e, d). - - Note: this can take a long time, depending on the key size. - """ - - while True: - (p, q) = find_p_q(nbits) - (e, d) = calculate_keys(p, q, nbits) - - # For some reason, d is sometimes negative. We don't know how - # to fix it (yet), so we keep trying until everything is shiny - if d > 0: break - - return (p, q, e, d) - -def gen_pubpriv_keys(nbits): - """Generates public and private keys, and returns them as (pub, - priv). - - The public key consists of a dict {e: ..., , n: ....). The private - key consists of a dict {d: ...., p: ...., q: ....). - """ - - (p, q, e, d) = gen_keys(nbits) - - return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} ) - -def encrypt_int(message, ekey, n): - """Encrypts a message using encryption key 'ekey', working modulo - n""" - - if type(message) is types.IntType: - return encrypt_int(long(message), ekey, n) - - if not type(message) is types.LongType: - raise TypeError("You must pass a long or an int") - - if message > 0 and \ - math.floor(math.log(message, 2)) > math.floor(math.log(n, 2)): - raise OverflowError("The message is too long") - - return fast_exponentiation(message, ekey, n) - -def decrypt_int(cyphertext, dkey, n): - """Decrypts a cypher text using the decryption key 'dkey', working - modulo n""" - - return encrypt_int(cyphertext, dkey, n) - -def sign_int(message, dkey, n): - """Signs 'message' using key 'dkey', working modulo n""" - - return decrypt_int(message, dkey, n) - -def verify_int(signed, ekey, n): - """verifies 'signed' using key 'ekey', working modulo n""" - - return encrypt_int(signed, ekey, n) - -def picklechops(chops): - """Pickles and base64encodes it's argument chops""" - - value = zlib.compress(dumps(chops)) - encoded = base64.encodestring(value) - return encoded.strip() - -def unpicklechops(string): - """base64decodes and unpickes it's argument string into chops""" - - return loads(zlib.decompress(base64.decodestring(string))) - -def chopstring(message, key, n, funcref): - """Splits 'message' into chops that are at most as long as n, - converts these into integers, and calls funcref(integer, key, n) - for each chop. - - Used by 'encrypt' and 'sign'. - """ - - msglen = len(message) - mbits = msglen * 8 - nbits = int(math.floor(math.log(n, 2))) - nbytes = nbits / 8 - blocks = msglen / nbytes - - if msglen % nbytes > 0: - blocks += 1 - - cypher = [] - - for bindex in range(blocks): - offset = bindex * nbytes - block = message[offset:offset+nbytes] - value = bytes2int(block) - cypher.append(funcref(value, key, n)) - - return picklechops(cypher) - -def gluechops(chops, key, n, funcref): - """Glues chops back together into a string. calls - funcref(integer, key, n) for each chop. - - Used by 'decrypt' and 'verify'. - """ - message = "" - - chops = unpicklechops(chops) - - for cpart in chops: - mpart = funcref(cpart, key, n) - message += int2bytes(mpart) - - return message - -def encrypt(message, key): - """Encrypts a string 'message' with the public key 'key'""" - - return chopstring(message, key['e'], key['n'], encrypt_int) - -def sign(message, key): - """Signs a string 'message' with the private key 'key'""" - - return chopstring(message, key['d'], key['p']*key['q'], decrypt_int) - -def decrypt(cypher, key): - """Decrypts a cypher with the private key 'key'""" - - return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int) - -def verify(cypher, key): - """Verifies a cypher with the public key 'key'""" - - return gluechops(cypher, key['e'], key['n'], encrypt_int) - -# Do doctest if we're not imported -if __name__ == "__main__": - import doctest - doctest.testmod() - -__all__ = ["gen_pubpriv_keys", "encrypt", "decrypt", "sign", "verify"] - diff --git a/rsa/_version200.py b/rsa/_version200.py deleted file mode 100644 index 1a16949..0000000 --- a/rsa/_version200.py +++ /dev/null @@ -1,513 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Deprecated version of the RSA module - -.. deprecated:: 3.0 - - This submodule is deprecated and will be completely removed as of version 4.0. - -""" - -__author__ = "Sybren Stuvel, Marloes de Boer, Ivo Tamboer, and Barry Mead" -__date__ = "2010-02-08" -__version__ = '2.0' - -import math -import os -import random -import sys -import types -from rsa._compat import byte - -# Display a warning that this insecure version is imported. -import warnings -warnings.warn('Insecure version of the RSA module is imported as %s' % __name__) -warnings.warn('This submodule is deprecated and will be completely removed as of version 4.0.', - DeprecationWarning) - - -def bit_size(number): - """Returns the number of bits required to hold a specific long number""" - - return int(math.ceil(math.log(number,2))) - -def gcd(p, q): - """Returns the greatest common divisor of p and q - >>> gcd(48, 180) - 12 - """ - # Iterateive Version is faster and uses much less stack space - while q != 0: - if p < q: (p,q) = (q,p) - (p,q) = (q, p % q) - return p - - -def bytes2int(bytes): - r"""Converts a list of bytes or a string to an integer - """ - - if not (type(bytes) is types.ListType or type(bytes) is types.StringType): - raise TypeError("You must pass a string or a list") - - # Convert byte stream to integer - integer = 0 - for byte in bytes: - integer *= 256 - if type(byte) is types.StringType: byte = ord(byte) - integer += byte - - return integer - -def int2bytes(number): - """ - Converts a number to a string of bytes - """ - - if not (type(number) is types.LongType or type(number) is types.IntType): - raise TypeError("You must pass a long or an int") - - string = "" - - while number > 0: - string = "%s%s" % (byte(number & 0xFF), string) - number /= 256 - - return string - -def to64(number): - """Converts a number in the range of 0 to 63 into base 64 digit - character in the range of '0'-'9', 'A'-'Z', 'a'-'z','-','_'. - """ - - if not (type(number) is types.LongType or type(number) is types.IntType): - raise TypeError("You must pass a long or an int") - - if 0 <= number <= 9: #00-09 translates to '0' - '9' - return byte(number + 48) - - if 10 <= number <= 35: - return byte(number + 55) #10-35 translates to 'A' - 'Z' - - if 36 <= number <= 61: - return byte(number + 61) #36-61 translates to 'a' - 'z' - - if number == 62: # 62 translates to '-' (minus) - return byte(45) - - if number == 63: # 63 translates to '_' (underscore) - return byte(95) - - raise ValueError('Invalid Base64 value: %i' % number) - - -def from64(number): - """Converts an ordinal character value in the range of - 0-9,A-Z,a-z,-,_ to a number in the range of 0-63. - """ - - if not (type(number) is types.LongType or type(number) is types.IntType): - raise TypeError("You must pass a long or an int") - - if 48 <= number <= 57: #ord('0') - ord('9') translates to 0-9 - return(number - 48) - - if 65 <= number <= 90: #ord('A') - ord('Z') translates to 10-35 - return(number - 55) - - if 97 <= number <= 122: #ord('a') - ord('z') translates to 36-61 - return(number - 61) - - if number == 45: #ord('-') translates to 62 - return(62) - - if number == 95: #ord('_') translates to 63 - return(63) - - raise ValueError('Invalid Base64 value: %i' % number) - - -def int2str64(number): - """Converts a number to a string of base64 encoded characters in - the range of '0'-'9','A'-'Z,'a'-'z','-','_'. - """ - - if not (type(number) is types.LongType or type(number) is types.IntType): - raise TypeError("You must pass a long or an int") - - string = "" - - while number > 0: - string = "%s%s" % (to64(number & 0x3F), string) - number /= 64 - - return string - - -def str642int(string): - """Converts a base64 encoded string into an integer. - The chars of this string in in the range '0'-'9','A'-'Z','a'-'z','-','_' - """ - - if not (type(string) is types.ListType or type(string) is types.StringType): - raise TypeError("You must pass a string or a list") - - integer = 0 - for byte in string: - integer *= 64 - if type(byte) is types.StringType: byte = ord(byte) - integer += from64(byte) - - return integer - -def read_random_int(nbits): - """Reads a random integer of approximately nbits bits rounded up - to whole bytes""" - - nbytes = int(math.ceil(nbits/8.)) - randomdata = os.urandom(nbytes) - return bytes2int(randomdata) - -def randint(minvalue, maxvalue): - """Returns a random integer x with minvalue <= x <= maxvalue""" - - # Safety - get a lot of random data even if the range is fairly - # small - min_nbits = 32 - - # The range of the random numbers we need to generate - range = (maxvalue - minvalue) + 1 - - # Which is this number of bytes - rangebytes = ((bit_size(range) + 7) / 8) - - # Convert to bits, but make sure it's always at least min_nbits*2 - rangebits = max(rangebytes * 8, min_nbits * 2) - - # Take a random number of bits between min_nbits and rangebits - nbits = random.randint(min_nbits, rangebits) - - return (read_random_int(nbits) % range) + minvalue - -def jacobi(a, b): - """Calculates the value of the Jacobi symbol (a/b) - where both a and b are positive integers, and b is odd - """ - - if a == 0: return 0 - result = 1 - while a > 1: - if a & 1: - if ((a-1)*(b-1) >> 2) & 1: - result = -result - a, b = b % a, a - else: - if (((b * b) - 1) >> 3) & 1: - result = -result - a >>= 1 - if a == 0: return 0 - return result - -def jacobi_witness(x, n): - """Returns False if n is an Euler pseudo-prime with base x, and - True otherwise. - """ - - j = jacobi(x, n) % n - f = pow(x, (n-1)/2, n) - - if j == f: return False - return True - -def randomized_primality_testing(n, k): - """Calculates whether n is composite (which is always correct) or - prime (which is incorrect with error probability 2**-k) - - Returns False if the number is composite, and True if it's - probably prime. - """ - - # 50% of Jacobi-witnesses can report compositness of non-prime numbers - - for i in range(k): - x = randint(1, n-1) - if jacobi_witness(x, n): return False - - return True - -def is_prime(number): - """Returns True if the number is prime, and False otherwise. - """ - - if randomized_primality_testing(number, 6): - # Prime, according to Jacobi - return True - - # Not prime - return False - - -def getprime(nbits): - """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In - other words: nbits is rounded up to whole bytes. - """ - - while True: - integer = read_random_int(nbits) - - # Make sure it's odd - integer |= 1 - - # Test for primeness - if is_prime(integer): break - - # Retry if not prime - - return integer - -def are_relatively_prime(a, b): - """Returns True if a and b are relatively prime, and False if they - are not. - - >>> are_relatively_prime(2, 3) - 1 - >>> are_relatively_prime(2, 4) - 0 - """ - - d = gcd(a, b) - return (d == 1) - -def find_p_q(nbits): - """Returns a tuple of two different primes of nbits bits""" - pbits = nbits + (nbits/16) #Make sure that p and q aren't too close - qbits = nbits - (nbits/16) #or the factoring programs can factor n - p = getprime(pbits) - while True: - q = getprime(qbits) - #Make sure p and q are different. - if not q == p: break - return (p, q) - -def extended_gcd(a, b): - """Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb - """ - # r = gcd(a,b) i = multiplicitive inverse of a mod b - # or j = multiplicitive inverse of b mod a - # Neg return values for i or j are made positive mod b or a respectively - # Iterateive Version is faster and uses much less stack space - x = 0 - y = 1 - lx = 1 - ly = 0 - oa = a #Remember original a/b to remove - ob = b #negative values from return results - while b != 0: - q = long(a/b) - (a, b) = (b, a % b) - (x, lx) = ((lx - (q * x)),x) - (y, ly) = ((ly - (q * y)),y) - if (lx < 0): lx += ob #If neg wrap modulo orignal b - if (ly < 0): ly += oa #If neg wrap modulo orignal a - return (a, lx, ly) #Return only positive values - -# Main function: calculate encryption and decryption keys -def calculate_keys(p, q, nbits): - """Calculates an encryption and a decryption key for p and q, and - returns them as a tuple (e, d)""" - - n = p * q - phi_n = (p-1) * (q-1) - - while True: - # Make sure e has enough bits so we ensure "wrapping" through - # modulo n - e = max(65537,getprime(nbits/4)) - if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break - - (d, i, j) = extended_gcd(e, phi_n) - - if not d == 1: - raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n)) - if (i < 0): - raise Exception("New extended_gcd shouldn't return negative values") - if not (e * i) % phi_n == 1: - raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n)) - - return (e, i) - - -def gen_keys(nbits): - """Generate RSA keys of nbits bits. Returns (p, q, e, d). - - Note: this can take a long time, depending on the key size. - """ - - (p, q) = find_p_q(nbits) - (e, d) = calculate_keys(p, q, nbits) - - return (p, q, e, d) - -def newkeys(nbits): - """Generates public and private keys, and returns them as (pub, - priv). - - The public key consists of a dict {e: ..., , n: ....). The private - key consists of a dict {d: ...., p: ...., q: ....). - """ - nbits = max(9,nbits) # Don't let nbits go below 9 bits - (p, q, e, d) = gen_keys(nbits) - - return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} ) - -def encrypt_int(message, ekey, n): - """Encrypts a message using encryption key 'ekey', working modulo n""" - - if type(message) is types.IntType: - message = long(message) - - if not type(message) is types.LongType: - raise TypeError("You must pass a long or int") - - if message < 0 or message > n: - raise OverflowError("The message is too long") - - #Note: Bit exponents start at zero (bit counts start at 1) this is correct - safebit = bit_size(n) - 2 #compute safe bit (MSB - 1) - message += (1 << safebit) #add safebit to ensure folding - - return pow(message, ekey, n) - -def decrypt_int(cyphertext, dkey, n): - """Decrypts a cypher text using the decryption key 'dkey', working - modulo n""" - - message = pow(cyphertext, dkey, n) - - safebit = bit_size(n) - 2 #compute safe bit (MSB - 1) - message -= (1 << safebit) #remove safebit before decode - - return message - -def encode64chops(chops): - """base64encodes chops and combines them into a ',' delimited string""" - - chips = [] #chips are character chops - - for value in chops: - chips.append(int2str64(value)) - - #delimit chops with comma - encoded = ','.join(chips) - - return encoded - -def decode64chops(string): - """base64decodes and makes a ',' delimited string into chops""" - - chips = string.split(',') #split chops at commas - - chops = [] - - for string in chips: #make char chops (chips) into chops - chops.append(str642int(string)) - - return chops - -def chopstring(message, key, n, funcref): - """Chops the 'message' into integers that fit into n, - leaving room for a safebit to be added to ensure that all - messages fold during exponentiation. The MSB of the number n - is not independant modulo n (setting it could cause overflow), so - use the next lower bit for the safebit. Therefore reserve 2-bits - in the number n for non-data bits. Calls specified encryption - function for each chop. - - Used by 'encrypt' and 'sign'. - """ - - msglen = len(message) - mbits = msglen * 8 - #Set aside 2-bits so setting of safebit won't overflow modulo n. - nbits = bit_size(n) - 2 # leave room for safebit - nbytes = nbits / 8 - blocks = msglen / nbytes - - if msglen % nbytes > 0: - blocks += 1 - - cypher = [] - - for bindex in range(blocks): - offset = bindex * nbytes - block = message[offset:offset+nbytes] - value = bytes2int(block) - cypher.append(funcref(value, key, n)) - - return encode64chops(cypher) #Encode encrypted ints to base64 strings - -def gluechops(string, key, n, funcref): - """Glues chops back together into a string. calls - funcref(integer, key, n) for each chop. - - Used by 'decrypt' and 'verify'. - """ - message = "" - - chops = decode64chops(string) #Decode base64 strings into integer chops - - for cpart in chops: - mpart = funcref(cpart, key, n) #Decrypt each chop - message += int2bytes(mpart) #Combine decrypted strings into a msg - - return message - -def encrypt(message, key): - """Encrypts a string 'message' with the public key 'key'""" - if 'n' not in key: - raise Exception("You must use the public key with encrypt") - - return chopstring(message, key['e'], key['n'], encrypt_int) - -def sign(message, key): - """Signs a string 'message' with the private key 'key'""" - if 'p' not in key: - raise Exception("You must use the private key with sign") - - return chopstring(message, key['d'], key['p']*key['q'], encrypt_int) - -def decrypt(cypher, key): - """Decrypts a string 'cypher' with the private key 'key'""" - if 'p' not in key: - raise Exception("You must use the private key with decrypt") - - return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int) - -def verify(cypher, key): - """Verifies a string 'cypher' with the public key 'key'""" - if 'n' not in key: - raise Exception("You must use the public key with verify") - - return gluechops(cypher, key['e'], key['n'], decrypt_int) - -# Do doctest if we're not imported -if __name__ == "__main__": - import doctest - doctest.testmod() - -__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify"] - diff --git a/rsa/bigfile.py b/rsa/bigfile.py deleted file mode 100644 index 3a09716..0000000 --- a/rsa/bigfile.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Large file support - -.. deprecated:: 3.4 - - The VARBLOCK format is NOT recommended for general use, has been deprecated since - Python-RSA 3.4, and will be removed in a future release. It's vulnerable to a - number of attacks: - - 1. decrypt/encrypt_bigfile() does not implement `Authenticated encryption`_ nor - uses MACs to verify messages before decrypting public key encrypted messages. - - 2. decrypt/encrypt_bigfile() does not use hybrid encryption (it uses plain RSA) - and has no method for chaining, so block reordering is possible. - - See `issue #19 on Github`_ for more information. - -.. _Authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption -.. _issue #19 on Github: https://github.com/sybrenstuvel/python-rsa/issues/13 - - -This module contains functions to: - - - break a file into smaller blocks, and encrypt them, and store the - encrypted blocks in another file. - - - take such an encrypted files, decrypt its blocks, and reconstruct the - original file. - -The encrypted file format is as follows, where || denotes byte concatenation: - - FILE := VERSION || BLOCK || BLOCK ... - - BLOCK := LENGTH || DATA - - LENGTH := varint-encoded length of the subsequent data. Varint comes from - Google Protobuf, and encodes an integer into a variable number of bytes. - Each byte uses the 7 lowest bits to encode the value. The highest bit set - to 1 indicates the next byte is also part of the varint. The last byte will - have this bit set to 0. - -This file format is called the VARBLOCK format, in line with the varint format -used to denote the block sizes. - -""" - -import warnings - -from rsa import key, common, pkcs1, varblock -from rsa._compat import byte - - -def encrypt_bigfile(infile, outfile, pub_key): - """Encrypts a file, writing it to 'outfile' in VARBLOCK format. - - .. deprecated:: 3.4 - This function was deprecated in Python-RSA version 3.4 due to security issues - in the VARBLOCK format. See the documentation_ for more information. - - .. _documentation: https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files - - :param infile: file-like object to read the cleartext from - :param outfile: file-like object to write the crypto in VARBLOCK format to - :param pub_key: :py:class:`rsa.PublicKey` to encrypt with - - """ - - warnings.warn("The 'rsa.bigfile.encrypt_bigfile' function was deprecated in Python-RSA version " - "3.4 due to security issues in the VARBLOCK format. See " - "https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files " - "for more information.", - DeprecationWarning, stacklevel=2) - - if not isinstance(pub_key, key.PublicKey): - raise TypeError('Public key required, but got %r' % pub_key) - - key_bytes = common.bit_size(pub_key.n) // 8 - blocksize = key_bytes - 11 # keep space for PKCS#1 padding - - # Write the version number to the VARBLOCK file - outfile.write(byte(varblock.VARBLOCK_VERSION)) - - # Encrypt and write each block - for block in varblock.yield_fixedblocks(infile, blocksize): - crypto = pkcs1.encrypt(block, pub_key) - - varblock.write_varint(outfile, len(crypto)) - outfile.write(crypto) - - -def decrypt_bigfile(infile, outfile, priv_key): - """Decrypts an encrypted VARBLOCK file, writing it to 'outfile' - - .. deprecated:: 3.4 - This function was deprecated in Python-RSA version 3.4 due to security issues - in the VARBLOCK format. See the documentation_ for more information. - - .. _documentation: https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files - - :param infile: file-like object to read the crypto in VARBLOCK format from - :param outfile: file-like object to write the cleartext to - :param priv_key: :py:class:`rsa.PrivateKey` to decrypt with - - """ - - warnings.warn("The 'rsa.bigfile.decrypt_bigfile' function was deprecated in Python-RSA version " - "3.4 due to security issues in the VARBLOCK format. See " - "https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files " - "for more information.", - DeprecationWarning, stacklevel=2) - - if not isinstance(priv_key, key.PrivateKey): - raise TypeError('Private key required, but got %r' % priv_key) - - for block in varblock.yield_varblocks(infile): - cleartext = pkcs1.decrypt(block, priv_key) - outfile.write(cleartext) - - -__all__ = ['encrypt_bigfile', 'decrypt_bigfile'] diff --git a/rsa/cli.py b/rsa/cli.py index 3a21878..9415b97 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -26,7 +26,6 @@ from optparse import OptionParser import rsa -import rsa.bigfile import rsa.pkcs1 HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) @@ -198,8 +197,7 @@ class EncryptOperation(CryptoOperation): keyname = 'public' description = ('Encrypts a file. The file must be shorter than the key ' - 'length in order to be encrypted. For larger files, use the ' - 'pyrsa-encrypt-bigfile command.') + 'length in order to be encrypted.') operation = 'encrypt' operation_past = 'encrypted' operation_progressive = 'encrypting' @@ -215,8 +213,7 @@ class DecryptOperation(CryptoOperation): keyname = 'private' description = ('Decrypts a file. The original file must be shorter than ' - 'the key length in order to have been encrypted. For larger ' - 'files, use the pyrsa-decrypt-bigfile command.') + 'the key length in order to have been encrypted.') operation = 'decrypt' operation_past = 'decrypted' operation_progressive = 'decrypting' @@ -285,99 +282,7 @@ def perform_operation(self, indata, pub_key, cli_args): print('Verification OK', file=sys.stderr) -class BigfileOperation(CryptoOperation): - """CryptoOperation that doesn't read the entire file into memory.""" - - def __init__(self): - CryptoOperation.__init__(self) - - self.file_objects = [] - - def __del__(self): - """Closes any open file handles.""" - - for fobj in self.file_objects: - fobj.close() - - def __call__(self): - """Runs the program.""" - - (cli, cli_args) = self.parse_cli() - - key = self.read_key(cli_args[0], cli.keyform) - - # Get the file handles - infile = self.get_infile(cli.input) - outfile = self.get_outfile(cli.output) - - # Call the operation - print(self.operation_progressive.title(), file=sys.stderr) - self.perform_operation(infile, outfile, key, cli_args) - - def get_infile(self, inname): - """Returns the input file object""" - - if inname: - print('Reading input from %s' % inname, file=sys.stderr) - fobj = open(inname, 'rb') - self.file_objects.append(fobj) - else: - print('Reading input from stdin', file=sys.stderr) - fobj = sys.stdin - - return fobj - - def get_outfile(self, outname): - """Returns the output file object""" - - if outname: - print('Will write output to %s' % outname, file=sys.stderr) - fobj = open(outname, 'wb') - self.file_objects.append(fobj) - else: - print('Will write output to stdout', file=sys.stderr) - fobj = sys.stdout - - return fobj - - -class EncryptBigfileOperation(BigfileOperation): - """Encrypts a file to VARBLOCK format.""" - - keyname = 'public' - description = ('Encrypts a file to an encrypted VARBLOCK file. The file ' - 'can be larger than the key length, but the output file is only ' - 'compatible with Python-RSA.') - operation = 'encrypt' - operation_past = 'encrypted' - operation_progressive = 'encrypting' - - def perform_operation(self, infile, outfile, pub_key, cli_args=None): - """Encrypts files to VARBLOCK.""" - - return rsa.bigfile.encrypt_bigfile(infile, outfile, pub_key) - - -class DecryptBigfileOperation(BigfileOperation): - """Decrypts a file in VARBLOCK format.""" - - keyname = 'private' - description = ('Decrypts an encrypted VARBLOCK file that was encrypted ' - 'with pyrsa-encrypt-bigfile') - operation = 'decrypt' - operation_past = 'decrypted' - operation_progressive = 'decrypting' - key_class = rsa.PrivateKey - - def perform_operation(self, infile, outfile, priv_key, cli_args=None): - """Decrypts a VARBLOCK file.""" - - return rsa.bigfile.decrypt_bigfile(infile, outfile, priv_key) - - encrypt = EncryptOperation() decrypt = DecryptOperation() sign = SignOperation() verify = VerifyOperation() -encrypt_bigfile = EncryptBigfileOperation() -decrypt_bigfile = DecryptBigfileOperation() diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 28f0dc5..fdbf093 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -317,6 +317,27 @@ def verify(message, signature, pub_key): return True +def yield_fixedblocks(infile, blocksize): + """Generator, yields each block of ``blocksize`` bytes in the input file. + + :param infile: file to read and separate in blocks. + :param blocksize: block size in bytes. + :returns: a generator that yields the contents of each block + """ + + while True: + block = infile.read(blocksize) + + read_bytes = len(block) + if read_bytes == 0: + break + + yield block + + if read_bytes < blocksize: + break + + def _hash(message, method_name): """Returns the message digest. @@ -335,11 +356,8 @@ def _hash(message, method_name): hasher = method() if hasattr(message, 'read') and hasattr(message.read, '__call__'): - # Late import to prevent DeprecationWarnings. - from . import varblock - # read as 1K blocks - for block in varblock.yield_fixedblocks(message, 1024): + for block in yield_fixedblocks(message, 1024): hasher.update(block) else: # hash the message object itself. diff --git a/rsa/varblock.py b/rsa/varblock.py deleted file mode 100644 index 1c8d839..0000000 --- a/rsa/varblock.py +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""VARBLOCK file support - -.. deprecated:: 3.4 - - The VARBLOCK format is NOT recommended for general use, has been deprecated since - Python-RSA 3.4, and will be removed in a future release. It's vulnerable to a - number of attacks: - - 1. decrypt/encrypt_bigfile() does not implement `Authenticated encryption`_ nor - uses MACs to verify messages before decrypting public key encrypted messages. - - 2. decrypt/encrypt_bigfile() does not use hybrid encryption (it uses plain RSA) - and has no method for chaining, so block reordering is possible. - - See `issue #19 on Github`_ for more information. - -.. _Authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption -.. _issue #19 on Github: https://github.com/sybrenstuvel/python-rsa/issues/13 - - -The VARBLOCK file format is as follows, where || denotes byte concatenation: - - FILE := VERSION || BLOCK || BLOCK ... - - BLOCK := LENGTH || DATA - - LENGTH := varint-encoded length of the subsequent data. Varint comes from - Google Protobuf, and encodes an integer into a variable number of bytes. - Each byte uses the 7 lowest bits to encode the value. The highest bit set - to 1 indicates the next byte is also part of the varint. The last byte will - have this bit set to 0. - -This file format is called the VARBLOCK format, in line with the varint format -used to denote the block sizes. - -""" - -import warnings - -from rsa._compat import byte, b - -ZERO_BYTE = b('\x00') -VARBLOCK_VERSION = 1 - -warnings.warn("The 'rsa.varblock' module was deprecated in Python-RSA version " - "3.4 due to security issues in the VARBLOCK format. See " - "https://github.com/sybrenstuvel/python-rsa/issues/13 for more information.", - DeprecationWarning) - - -def read_varint(infile): - """Reads a varint from the file. - - When the first byte to be read indicates EOF, (0, 0) is returned. When an - EOF occurs when at least one byte has been read, an EOFError exception is - raised. - - :param infile: the file-like object to read from. It should have a read() - method. - :returns: (varint, length), the read varint and the number of read bytes. - """ - - varint = 0 - read_bytes = 0 - - while True: - char = infile.read(1) - if len(char) == 0: - if read_bytes == 0: - return 0, 0 - raise EOFError('EOF while reading varint, value is %i so far' % - varint) - - byte = ord(char) - varint += (byte & 0x7F) << (7 * read_bytes) - - read_bytes += 1 - - if not byte & 0x80: - return varint, read_bytes - - -def write_varint(outfile, value): - """Writes a varint to a file. - - :param outfile: the file-like object to write to. It should have a write() - method. - :returns: the number of written bytes. - """ - - # there is a big difference between 'write the value 0' (this case) and - # 'there is nothing left to write' (the false-case of the while loop) - - if value == 0: - outfile.write(ZERO_BYTE) - return 1 - - written_bytes = 0 - while value > 0: - to_write = value & 0x7f - value >>= 7 - - if value > 0: - to_write |= 0x80 - - outfile.write(byte(to_write)) - written_bytes += 1 - - return written_bytes - - -def yield_varblocks(infile): - """Generator, yields each block in the input file. - - :param infile: file to read, is expected to have the VARBLOCK format as - described in the module's docstring. - @yields the contents of each block. - """ - - # Check the version number - first_char = infile.read(1) - if len(first_char) == 0: - raise EOFError('Unable to read VARBLOCK version number') - - version = ord(first_char) - if version != VARBLOCK_VERSION: - raise ValueError('VARBLOCK version %i not supported' % version) - - while True: - (block_size, read_bytes) = read_varint(infile) - - # EOF at block boundary, that's fine. - if read_bytes == 0 and block_size == 0: - break - - block = infile.read(block_size) - - read_size = len(block) - if read_size != block_size: - raise EOFError('Block size is %i, but could read only %i bytes' % - (block_size, read_size)) - - yield block - - -def yield_fixedblocks(infile, blocksize): - """Generator, yields each block of ``blocksize`` bytes in the input file. - - :param infile: file to read and separate in blocks. - :returns: a generator that yields the contents of each block - """ - - while True: - block = infile.read(blocksize) - - read_bytes = len(block) - if read_bytes == 0: - break - - yield block - - if read_bytes < blocksize: - break diff --git a/setup.py b/setup.py index a4d1d41..ee2968f 100755 --- a/setup.py +++ b/setup.py @@ -54,8 +54,6 @@ 'pyrsa-decrypt = rsa.cli:decrypt', 'pyrsa-sign = rsa.cli:sign', 'pyrsa-verify = rsa.cli:verify', - 'pyrsa-encrypt-bigfile = rsa.cli:encrypt_bigfile', - 'pyrsa-decrypt-bigfile = rsa.cli:decrypt_bigfile', ]}, ) diff --git a/tests/test_bigfile.py b/tests/test_bigfile.py deleted file mode 100644 index 70278dc..0000000 --- a/tests/test_bigfile.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests block operations.""" - -from rsa._compat import b - -try: - from StringIO import StringIO as BytesIO -except ImportError: - from io import BytesIO -import unittest - -import rsa -from rsa import bigfile, varblock, pkcs1 - - -class BigfileTest(unittest.TestCase): - def test_encrypt_decrypt_bigfile(self): - # Expected block size + 11 bytes padding - pub_key, priv_key = rsa.newkeys((6 + 11) * 8) - - # Encrypt the file - message = b('123456Sybren') - infile = BytesIO(message) - outfile = BytesIO() - - bigfile.encrypt_bigfile(infile, outfile, pub_key) - - # Test - crypto = outfile.getvalue() - - cryptfile = BytesIO(crypto) - clearfile = BytesIO() - - bigfile.decrypt_bigfile(cryptfile, clearfile, priv_key) - self.assertEquals(clearfile.getvalue(), message) - - # We have 2x6 bytes in the message, so that should result in two - # bigfile. - cryptfile.seek(0) - varblocks = list(varblock.yield_varblocks(cryptfile)) - self.assertEqual(2, len(varblocks)) - - def test_sign_verify_bigfile(self): - # Large enough to store MD5-sum and ASN.1 code for MD5 - pub_key, priv_key = rsa.newkeys((34 + 11) * 8) - - # Sign the file - msgfile = BytesIO(b('123456Sybren')) - signature = pkcs1.sign(msgfile, priv_key, 'MD5') - - # Check the signature - msgfile.seek(0) - self.assertTrue(pkcs1.verify(msgfile, signature, pub_key)) - - # Alter the message, re-check - msgfile = BytesIO(b('123456sybren')) - self.assertRaises(pkcs1.VerificationError, - pkcs1.verify, msgfile, signature, pub_key) diff --git a/tests/test_varblock.py b/tests/test_varblock.py deleted file mode 100644 index d1c3730..0000000 --- a/tests/test_varblock.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests varblock operations.""" - -try: - from StringIO import StringIO as BytesIO -except ImportError: - from io import BytesIO -import unittest - -from rsa._compat import b -from rsa import varblock - - -class VarintTest(unittest.TestCase): - def test_read_varint(self): - encoded = b('\xac\x02crummy') - infile = BytesIO(encoded) - - (decoded, read) = varblock.read_varint(infile) - - # Test the returned values - self.assertEqual(300, decoded) - self.assertEqual(2, read) - - # The rest of the file should be untouched - self.assertEqual(b('crummy'), infile.read()) - - def test_read_zero(self): - encoded = b('\x00crummy') - infile = BytesIO(encoded) - - (decoded, read) = varblock.read_varint(infile) - - # Test the returned values - self.assertEqual(0, decoded) - self.assertEqual(1, read) - - # The rest of the file should be untouched - self.assertEqual(b('crummy'), infile.read()) - - def test_write_varint(self): - expected = b('\xac\x02') - outfile = BytesIO() - - written = varblock.write_varint(outfile, 300) - - # Test the returned values - self.assertEqual(expected, outfile.getvalue()) - self.assertEqual(2, written) - - def test_write_zero(self): - outfile = BytesIO() - written = varblock.write_varint(outfile, 0) - - # Test the returned values - self.assertEqual(b('\x00'), outfile.getvalue()) - self.assertEqual(1, written) - - -class VarblockTest(unittest.TestCase): - def test_yield_varblock(self): - infile = BytesIO(b('\x01\x0512345\x06Sybren')) - - varblocks = list(varblock.yield_varblocks(infile)) - self.assertEqual([b('12345'), b('Sybren')], varblocks) - - -class FixedblockTest(unittest.TestCase): - def test_yield_fixedblock(self): - infile = BytesIO(b('123456Sybren')) - - fixedblocks = list(varblock.yield_fixedblocks(infile, 6)) - self.assertEqual([b('123456'), b('Sybren')], fixedblocks) From fedcaa1f94c637e5fe007f8799525d4bb13e66f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 14:34:22 +0100 Subject: [PATCH 003/142] Ensuring that PEM output is always in bytes. This may break some applications. However, it does make the RSA library easier to use on different Python versions. --- rsa/cli.py | 4 ++++ rsa/pem.py | 12 ++++++------ tests/test_pem.py | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/rsa/cli.py b/rsa/cli.py index 9415b97..fc01b12 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -83,6 +83,8 @@ def keygen(): outfile.write(data) else: print('Writing private key to stdout', file=sys.stderr) + if sys.version_info[0] >= 3: + data = data.decode('ascii') # on Py3 we must write text, not bytes sys.stdout.write(data) @@ -189,6 +191,8 @@ def write_outfile(self, outdata, outname): outfile.write(outdata) else: print('Writing output to stdout', file=sys.stderr) + if sys.version_info[0] >= 3: + data = outdata.decode('ascii') # on Py3 we must write text, not bytes sys.stdout.write(outdata) diff --git a/rsa/pem.py b/rsa/pem.py index 0f68cb2..d5427a0 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -22,14 +22,14 @@ def _markers(pem_marker): """ - Returns the start and end PEM markers + Returns the start and end PEM markers, as bytes. """ - if is_bytes(pem_marker): - pem_marker = pem_marker.decode('utf-8') + if not is_bytes(pem_marker): + pem_marker = pem_marker.encode('ascii') - return (b('-----BEGIN %s-----' % pem_marker), - b('-----END %s-----' % pem_marker)) + return (b('-----BEGIN ') + pem_marker + b('-----'), + b('-----END ') + pem_marker + b('-----')) def load_pem(contents, pem_marker): @@ -106,7 +106,7 @@ def save_pem(contents, pem_marker): when your file has '-----BEGIN RSA PRIVATE KEY-----' and '-----END RSA PRIVATE KEY-----' markers. - :return: the base64-encoded content between the start and end markers. + :return: the base64-encoded content between the start and end markers, as bytes. """ diff --git a/tests/test_pem.py b/tests/test_pem.py index 952ec79..61a66fc 100644 --- a/tests/test_pem.py +++ b/tests/test_pem.py @@ -17,7 +17,7 @@ import unittest -from rsa._compat import b +from rsa._compat import b, is_bytes from rsa.pem import _markers import rsa.key @@ -72,3 +72,17 @@ def test_bytes_private(self): key = rsa.key.PrivateKey.load_pkcs1(private_key_pem.encode('ascii')) self.assertEqual(prime1, key.p) self.assertEqual(prime2, key.q) + + +class TestByteOutput(unittest.TestCase): + """Tests that PEM and DER are returned as bytes.""" + + def test_bytes_public(self): + key = rsa.key.PublicKey.load_pkcs1_openssl_pem(public_key_pem) + self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) + self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + + def test_bytes_private(self): + key = rsa.key.PrivateKey.load_pkcs1(private_key_pem) + self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) + self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) From 3d5c098dbcd1f7732b7b559f793f4b0944e90884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 13:27:41 +0100 Subject: [PATCH 004/142] Attempt at unit-testing CLI commands --- tests/test_cli.py | 149 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/test_cli.py diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..0051aa1 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,149 @@ +""" +Unit tests for CLI entry points. +""" + +import unittest +import sys +import functools +from contextlib import contextmanager + +import os +from io import StringIO, BytesIO + +import rsa +import rsa.cli + +if sys.version_info[0] < 3: + IOClass = BytesIO +else: + IOClass = StringIO + + +@contextmanager +def captured_output(): + """Captures output to stdout and stderr""" + + new_out, new_err = IOClass(), IOClass() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = new_out, new_err + yield sys.stdout, sys.stderr + finally: + sys.stdout, sys.stderr = old_out, old_err + + +@contextmanager +def cli_args(*new_argv): + """Updates sys.argv[1:] for a single test.""" + + old_args = sys.argv[:] + sys.argv[1:] = [str(arg) for arg in new_argv] + + try: + yield + finally: + sys.argv[1:] = old_args + + +def cleanup_files(*filenames): + """Makes sure the files don't exist when the test runs, and deletes them afterward.""" + + def remove(): + for fname in filenames: + if os.path.exists(fname): + os.unlink(fname) + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + remove() + try: + return func(*args, **kwargs) + finally: + remove() + + return wrapper + + return decorator + + +class AbstractCliTest(unittest.TestCase): + def assertExits(self, status_code, func, *args, **kwargs): + try: + func(*args, **kwargs) + except SystemExit as ex: + if status_code == ex.code: + return + self.fail('SystemExit() raised by %r, but exited with code %i, expected %i' % ( + func, ex.code, status_code)) + else: + self.fail('SystemExit() not raised by %r' % func) + + +class KeygenTest(AbstractCliTest): + def test_keygen_no_args(self): + with cli_args(): + self.assertExits(1, rsa.cli.keygen) + + def test_keygen_priv_stdout(self): + with captured_output() as (out, err): + with cli_args(128): + rsa.cli.keygen() + + lines = out.getvalue().splitlines() + self.assertEqual('-----BEGIN RSA PRIVATE KEY-----', lines[0]) + self.assertEqual('-----END RSA PRIVATE KEY-----', lines[-1]) + + # The key size should be shown on stderr + self.assertTrue('128-bit key' in err.getvalue()) + + @cleanup_files('test_cli_privkey_out.pem') + def test_keygen_priv_out_pem(self): + with captured_output() as (out, err): + with cli_args('--out=test_cli_privkey_out.pem', '--form=PEM', 128): + rsa.cli.keygen() + + # The key size should be shown on stderr + self.assertTrue('128-bit key' in err.getvalue()) + + # The output file should be shown on stderr + self.assertTrue('test_cli_privkey_out.pem' in err.getvalue()) + + # If we can load the file as PEM, it's good enough. + with open('test_cli_privkey_out.pem', 'rb') as pemfile: + rsa.PrivateKey.load_pkcs1(pemfile.read()) + + @cleanup_files('test_cli_privkey_out.der') + def test_keygen_priv_out_der(self): + with captured_output() as (out, err): + with cli_args('--out=test_cli_privkey_out.der', '--form=DER', 128): + rsa.cli.keygen() + + # The key size should be shown on stderr + self.assertTrue('128-bit key' in err.getvalue()) + + # The output file should be shown on stderr + self.assertTrue('test_cli_privkey_out.der' in err.getvalue()) + + # If we can load the file as der, it's good enough. + with open('test_cli_privkey_out.der', 'rb') as derfile: + rsa.PrivateKey.load_pkcs1(derfile.read(), format='DER') + + @cleanup_files('test_cli_privkey_out.pem', 'test_cli_pubkey_out.pem') + def test_keygen_pub_out_pem(self): + with captured_output() as (out, err): + with cli_args('--out=test_cli_privkey_out.pem', + '--pubout=test_cli_pubkey_out.pem', + '--form=PEM', 256): + rsa.cli.keygen() + + # The key size should be shown on stderr + self.assertTrue('256-bit key' in err.getvalue()) + + # The output files should be shown on stderr + self.assertTrue('test_cli_privkey_out.pem' in err.getvalue()) + self.assertTrue('test_cli_pubkey_out.pem' in err.getvalue()) + + # If we can load the file as PEM, it's good enough. + with open('test_cli_pubkey_out.pem', 'rb') as pemfile: + rsa.PublicKey.load_pkcs1(pemfile.read()) From f0627bed3e8815e8138fbe72b740483f69d6ac7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 15:52:23 +0100 Subject: [PATCH 005/142] More CLI tests & clearer bytes stuff Ensuring that bytes are written correctly on all supported Python versions, including when writing to stdout. --- rsa/cli.py | 14 ++-- rsa/key.py | 65 +++++++++++++-- tests/__init__.py | 13 +++ tests/test_cli.py | 151 ++++++++++++++++++++++++++++++++--- tests/test_compat.py | 6 +- tests/test_load_save_keys.py | 6 +- tests/test_pem.py | 14 ++++ 7 files changed, 245 insertions(+), 24 deletions(-) diff --git a/rsa/cli.py b/rsa/cli.py index fc01b12..7419780 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -84,8 +84,10 @@ def keygen(): else: print('Writing private key to stdout', file=sys.stderr) if sys.version_info[0] >= 3: - data = data.decode('ascii') # on Py3 we must write text, not bytes - sys.stdout.write(data) + # on Py3 we must use the buffer interface to write bytes. + sys.stdout.buffer.write(data) + else: + sys.stdout.write(data) class CryptoOperation(object): @@ -114,7 +116,7 @@ def __init__(self): self.output_help = self.output_help % self.__class__.__dict__ @abc.abstractmethod - def perform_operation(self, indata, key, cli_args=None): + def perform_operation(self, indata, key, cli_args): """Performs the program's operation. Implement in a subclass. @@ -192,8 +194,10 @@ def write_outfile(self, outdata, outname): else: print('Writing output to stdout', file=sys.stderr) if sys.version_info[0] >= 3: - data = outdata.decode('ascii') # on Py3 we must write text, not bytes - sys.stdout.write(outdata) + # on Py3 we must use the buffer interface to write bytes. + sys.stdout.buffer.write(outdata) + else: + sys.stdout.write(outdata) class EncryptOperation(CryptoOperation): diff --git a/rsa/key.py b/rsa/key.py index 64600a2..3e3fc42 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -34,8 +34,8 @@ """ import logging -from rsa._compat import b +from rsa._compat import b import rsa.prime import rsa.pem import rsa.common @@ -55,15 +55,56 @@ def __init__(self, n, e): self.n = n self.e = e + @classmethod + def _load_pkcs1_pem(cls, keyfile): + """Loads a key in PKCS#1 PEM format, implement in a subclass. + + :param keyfile: contents of a PEM-encoded file that contains + the public key. + :type keyfile: bytes + + :return: the loaded key + :rtype: AbstractKey + """ + + @classmethod + def _load_pkcs1_der(cls, keyfile): + """Loads a key in PKCS#1 PEM format, implement in a subclass. + + :param keyfile: contents of a DER-encoded file that contains + the public key. + :type keyfile: bytes + + :return: the loaded key + :rtype: AbstractKey + """ + + def _save_pkcs1_pem(self): + """Saves the key in PKCS#1 PEM format, implement in a subclass. + + :returns: the PEM-encoded key. + :rtype: bytes + """ + + def _save_pkcs1_der(self): + """Saves the key in PKCS#1 DER format, implement in a subclass. + + :returns: the DER-encoded key. + :rtype: bytes + """ + @classmethod def load_pkcs1(cls, keyfile, format='PEM'): """Loads a key in PKCS#1 DER or PEM format. :param keyfile: contents of a DER- or PEM-encoded file that contains - the public key. + the key. + :type keyfile: bytes :param format: the format of the file to load; 'PEM' or 'DER' + :type format: str - :return: a PublicKey object + :return: the loaded key + :rtype: AbstractKey """ methods = { @@ -87,10 +128,12 @@ def _assert_format_exists(file_format, methods): formats)) def save_pkcs1(self, format='PEM'): - """Saves the public key in PKCS#1 DER or PEM format. + """Saves the key in PKCS#1 DER or PEM format. :param format: the format to save; 'PEM' or 'DER' - :returns: the DER- or PEM-encoded public key. + :type format: str + :returns: the DER- or PEM-encoded key. + :rtype: bytes """ methods = { @@ -215,7 +258,8 @@ def _load_pkcs1_der(cls, keyfile): def _save_pkcs1_der(self): """Saves the public key in PKCS#1 DER format. - @returns: the DER-encoded public key. + :returns: the DER-encoded public key. + :rtype: bytes """ from pyasn1.codec.der import encoder @@ -247,6 +291,7 @@ def _save_pkcs1_pem(self): """Saves a PKCS#1 PEM-encoded public key file. :return: contents of a PEM-encoded file that contains the public key. + :rtype: bytes """ der = self._save_pkcs1_der() @@ -264,6 +309,7 @@ def load_pkcs1_openssl_pem(cls, keyfile): :param keyfile: contents of a PEM-encoded file that contains the public key, from OpenSSL. + :type keyfile: bytes :return: a PublicKey object """ @@ -277,6 +323,7 @@ def load_pkcs1_openssl_der(cls, keyfile): :param keyfile: contents of a DER-encoded file that contains the public key, from OpenSSL. :return: a PublicKey object + :rtype: bytes """ @@ -420,6 +467,7 @@ def _load_pkcs1_der(cls, keyfile): :param keyfile: contents of a DER-encoded file that contains the private key. + :type keyfile: bytes :return: a PrivateKey object First let's construct a DER encoded key: @@ -462,7 +510,8 @@ def _load_pkcs1_der(cls, keyfile): def _save_pkcs1_der(self): """Saves the private key in PKCS#1 DER format. - @returns: the DER-encoded private key. + :returns: the DER-encoded private key. + :rtype: bytes """ from pyasn1.type import univ, namedtype @@ -504,6 +553,7 @@ def _load_pkcs1_pem(cls, keyfile): :param keyfile: contents of a PEM-encoded file that contains the private key. + :type keyfile: bytes :return: a PrivateKey object """ @@ -514,6 +564,7 @@ def _save_pkcs1_pem(self): """Saves a PKCS#1 PEM-encoded private key file. :return: contents of a PEM-encoded file that contains the private key. + :rtype: bytes """ der = self._save_pkcs1_der() diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..85bafc7 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,13 @@ +import unittest +import sys + + +if sys.hexversion < 0x2070000: + # Monkey-patch unittest.TestCase to add assertIsInstance on Python 2.6 + + def assertIsInstance(self, obj, cls, msg=None): + """Same as self.assertTrue(isinstance(obj, cls)), with a nicer default message.""" + if not isinstance(obj, cls): + self.fail('%r is not an instance of %r but is a %r' % (obj, cls, type(obj))) + + unittest.TestCase.assertIsInstance = assertIsInstance diff --git a/tests/test_cli.py b/tests/test_cli.py index 0051aa1..511d25d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,8 @@ Unit tests for CLI entry points. """ +from __future__ import print_function + import unittest import sys import functools @@ -12,22 +14,37 @@ import rsa import rsa.cli +from rsa._compat import b if sys.version_info[0] < 3: - IOClass = BytesIO + def make_buffer(): + return BytesIO() + + + def get_bytes_out(out): + # Python 2.x writes 'str' to stdout: + return out.getvalue() else: - IOClass = StringIO + def make_buffer(): + buf = StringIO() + buf.buffer = BytesIO() + return buf + + + def get_bytes_out(out): + # Python 3.x writes 'bytes' to stdout.buffer: + return out.buffer.getvalue() @contextmanager def captured_output(): """Captures output to stdout and stderr""" - new_out, new_err = IOClass(), IOClass() + new_out, new_err = make_buffer(), make_buffer() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err - yield sys.stdout, sys.stderr + yield new_out, new_err finally: sys.stdout, sys.stderr = old_out, old_err @@ -45,13 +62,19 @@ def cli_args(*new_argv): sys.argv[1:] = old_args +def remove_if_exists(fname): + """Removes a file if it exists.""" + + if os.path.exists(fname): + os.unlink(fname) + + def cleanup_files(*filenames): """Makes sure the files don't exist when the test runs, and deletes them afterward.""" def remove(): for fname in filenames: - if os.path.exists(fname): - os.unlink(fname) + remove_if_exists(fname) def decorator(func): @functools.wraps(func) @@ -68,13 +91,33 @@ def wrapper(*args, **kwargs): class AbstractCliTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Ensure there is a key to use + cls.pub_key, cls.priv_key = rsa.newkeys(512) + cls.pub_fname = '%s.pub' % cls.__name__ + cls.priv_fname = '%s.key' % cls.__name__ + + with open(cls.pub_fname, 'wb') as outfile: + outfile.write(cls.pub_key.save_pkcs1()) + + with open(cls.priv_fname, 'wb') as outfile: + outfile.write(cls.priv_key.save_pkcs1()) + + @classmethod + def tearDownClass(cls): + if hasattr(cls, 'pub_fname'): + remove_if_exists(cls.pub_fname) + if hasattr(cls, 'priv_fname'): + remove_if_exists(cls.priv_fname) + def assertExits(self, status_code, func, *args, **kwargs): try: func(*args, **kwargs) except SystemExit as ex: if status_code == ex.code: return - self.fail('SystemExit() raised by %r, but exited with code %i, expected %i' % ( + self.fail('SystemExit() raised by %r, but exited with code %r, expected %r' % ( func, ex.code, status_code)) else: self.fail('SystemExit() not raised by %r' % func) @@ -90,9 +133,9 @@ def test_keygen_priv_stdout(self): with cli_args(128): rsa.cli.keygen() - lines = out.getvalue().splitlines() - self.assertEqual('-----BEGIN RSA PRIVATE KEY-----', lines[0]) - self.assertEqual('-----END RSA PRIVATE KEY-----', lines[-1]) + lines = get_bytes_out(out).splitlines() + self.assertEqual(b('-----BEGIN RSA PRIVATE KEY-----'), lines[0]) + self.assertEqual(b('-----END RSA PRIVATE KEY-----'), lines[-1]) # The key size should be shown on stderr self.assertTrue('128-bit key' in err.getvalue()) @@ -147,3 +190,91 @@ def test_keygen_pub_out_pem(self): # If we can load the file as PEM, it's good enough. with open('test_cli_pubkey_out.pem', 'rb') as pemfile: rsa.PublicKey.load_pkcs1(pemfile.read()) + + +class EncryptDecryptTest(AbstractCliTest): + def test_empty_decrypt(self): + with cli_args(): + self.assertExits(1, rsa.cli.decrypt) + + def test_empty_encrypt(self): + with cli_args(): + self.assertExits(1, rsa.cli.encrypt) + + @cleanup_files('encrypted.txt', 'cleartext.txt') + def test_encrypt_decrypt(self): + with open('cleartext.txt', 'wb') as outfile: + outfile.write(b'Hello cleartext RSA users!') + + with cli_args('-i', 'cleartext.txt', '--out=encrypted.txt', self.pub_fname): + with captured_output(): + rsa.cli.encrypt() + + with cli_args('-i', 'encrypted.txt', self.priv_fname): + with captured_output() as (out, err): + rsa.cli.decrypt() + + # We should have the original cleartext on stdout now. + output = get_bytes_out(out) + self.assertEqual(b('Hello cleartext RSA users!'), output) + + @cleanup_files('encrypted.txt', 'cleartext.txt') + def test_encrypt_decrypt_unhappy(self): + with open('cleartext.txt', 'wb') as outfile: + outfile.write(b'Hello cleartext RSA users!') + + with cli_args('-i', 'cleartext.txt', '--out=encrypted.txt', self.pub_fname): + with captured_output(): + rsa.cli.encrypt() + + # Change a few bytes in the encrypted stream. + with open('encrypted.txt', 'r+b') as encfile: + encfile.seek(40) + encfile.write(b'hahaha') + + with cli_args('-i', 'encrypted.txt', self.priv_fname): + with captured_output() as (out, err): + self.assertRaises(rsa.DecryptionError, rsa.cli.decrypt) + + +class SignVerifyTest(AbstractCliTest): + def test_empty_verify(self): + with cli_args(): + self.assertExits(1, rsa.cli.verify) + + def test_empty_sign(self): + with cli_args(): + self.assertExits(1, rsa.cli.sign) + + @cleanup_files('signature.txt', 'cleartext.txt') + def test_sign_verify(self): + with open('cleartext.txt', 'wb') as outfile: + outfile.write(b'Hello RSA users!') + + with cli_args('-i', 'cleartext.txt', '--out=signature.txt', self.priv_fname, 'SHA-256'): + with captured_output(): + rsa.cli.sign() + + with cli_args('-i', 'cleartext.txt', self.pub_fname, 'signature.txt'): + with captured_output() as (out, err): + rsa.cli.verify() + + self.assertFalse(b'Verification OK' in get_bytes_out(out)) + + @cleanup_files('signature.txt', 'cleartext.txt') + def test_sign_verify_unhappy(self): + with open('cleartext.txt', 'wb') as outfile: + outfile.write(b'Hello RSA users!') + + with cli_args('-i', 'cleartext.txt', '--out=signature.txt', self.priv_fname, 'SHA-256'): + with captured_output(): + rsa.cli.sign() + + # Change a few bytes in the cleartext file. + with open('cleartext.txt', 'r+b') as encfile: + encfile.seek(6) + encfile.write(b'DSA') + + with cli_args('-i', 'cleartext.txt', self.pub_fname, 'signature.txt'): + with captured_output() as (out, err): + self.assertExits('Verification failed.', rsa.cli.verify) diff --git a/tests/test_compat.py b/tests/test_compat.py index 8cbf101..74d6f53 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -16,8 +16,9 @@ import unittest import struct +import sys -from rsa._compat import is_bytes, byte +from rsa._compat import is_bytes, byte, b class TestByte(unittest.TestCase): @@ -30,3 +31,6 @@ def test_byte(self): def test_raises_StructError_on_overflow(self): self.assertRaises(struct.error, byte, 256) self.assertRaises(struct.error, byte, -1) + + def test_byte_literal(self): + self.assertIsInstance(b('abc'), bytes) diff --git a/tests/test_load_save_keys.py b/tests/test_load_save_keys.py index 6f374cf..0caa067 100644 --- a/tests/test_load_save_keys.py +++ b/tests/test_load_save_keys.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -'''Unittest for saving and loading keys.''' +"""Unittest for saving and loading keys.""" import base64 import unittest @@ -89,6 +89,7 @@ def test_save_private_key(self): key = rsa.key.PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) der = key.save_pkcs1('DER') + self.assertIsInstance(der, bytes) self.assertEqual(PRIVATE_DER, der) def test_load_public_key(self): @@ -105,6 +106,7 @@ def test_save_public_key(self): key = rsa.key.PublicKey(3727264081, 65537) der = key.save_pkcs1('DER') + self.assertIsInstance(der, bytes) self.assertEqual(PUBLIC_DER, der) @@ -125,6 +127,7 @@ def test_save_private_key(self): key = rsa.key.PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) pem = key.save_pkcs1('PEM') + self.assertIsInstance(pem, bytes) self.assertEqual(CLEAN_PRIVATE_PEM, pem) def test_load_public_key(self): @@ -141,6 +144,7 @@ def test_save_public_key(self): key = rsa.key.PublicKey(3727264081, 65537) pem = key.save_pkcs1('PEM') + self.assertIsInstance(pem, bytes) self.assertEqual(CLEAN_PUBLIC_PEM, pem) def test_load_from_disk(self): diff --git a/tests/test_pem.py b/tests/test_pem.py index 61a66fc..3e03ab0 100644 --- a/tests/test_pem.py +++ b/tests/test_pem.py @@ -86,3 +86,17 @@ def test_bytes_private(self): key = rsa.key.PrivateKey.load_pkcs1(private_key_pem) self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + + +class TestByteInput(unittest.TestCase): + """Tests that PEM and DER can be loaded from bytes.""" + + def test_bytes_public(self): + key = rsa.key.PublicKey.load_pkcs1_openssl_pem(public_key_pem.encode('ascii')) + self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) + self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + + def test_bytes_private(self): + key = rsa.key.PrivateKey.load_pkcs1(private_key_pem.encode('ascii')) + self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) + self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) From bb27408398923b746c81282fb738b70b1015f912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 15:56:40 +0100 Subject: [PATCH 006/142] Updated changelog --- CHANGELOG.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2fa4e44..270fc11 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,17 @@ Python-RSA changelog ======================================== +Version 4.0 - in development +---------------------------------------- + +- Removed deprecated modules: + - rsa.varblock + - rsa.bigfile + - rsa._version133 + - rsa._version200 +- Removed CLI commands that use the VARBLOCK/bigfile format. +- Ensured that PublicKey.save_pkcs1() and PrivateKey.save_pkcs1() always return bytes. + Version 3.4 - released 2006-03-17 ---------------------------------------- From ff7f0c7ae75e8aa56a1ad338185004077a854e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 16:12:55 +0100 Subject: [PATCH 007/142] Added unit test for rsa.util.private_to_public() --- tests/test_cli.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 511d25d..aad734a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -14,6 +14,7 @@ import rsa import rsa.cli +import rsa.util from rsa._compat import b if sys.version_info[0] < 3: @@ -278,3 +279,21 @@ def test_sign_verify_unhappy(self): with cli_args('-i', 'cleartext.txt', self.pub_fname, 'signature.txt'): with captured_output() as (out, err): self.assertExits('Verification failed.', rsa.cli.verify) + + +class PrivatePublicTest(AbstractCliTest): + """Test CLI command to convert a private to a public key.""" + + @cleanup_files('test_private_to_public.pem') + def test_private_to_public(self): + + with cli_args('-i', self.priv_fname, '-o', 'test_private_to_public.pem'): + with captured_output(): + rsa.util.private_to_public() + + # Check that the key is indeed valid. + with open('test_private_to_public.pem', 'rb') as pemfile: + key = rsa.PublicKey.load_pkcs1(pemfile.read()) + + self.assertEqual(self.priv_key.n, key.n) + self.assertEqual(self.priv_key.e, key.e) From a8eb6f06d41d608a4d3bca1f387e44cad95f1de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 16:13:11 +0100 Subject: [PATCH 008/142] Removed a doctest, it's covered by a unit test anyway. --- rsa/prime.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rsa/prime.py b/rsa/prime.py index 6f23f9d..a5726e6 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -99,8 +99,6 @@ def is_prime(number): False >>> is_prime(41) True - >>> [x for x in range(901, 1000) if is_prime(x)] - [907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997] """ # Check for small numbers. From 347b82f65c3f9f15934b1a3e3be07368b71e4420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 16:20:01 +0100 Subject: [PATCH 009/142] Dropped support for Python 2.6, its life ended in 2013. --- CHANGELOG.txt | 2 +- setup.py | 2 -- speed.sh | 1 - tox.ini | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 270fc11..0bcdfef 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -11,7 +11,7 @@ Version 4.0 - in development - rsa._version200 - Removed CLI commands that use the VARBLOCK/bigfile format. - Ensured that PublicKey.save_pkcs1() and PrivateKey.save_pkcs1() always return bytes. - +- Dropped support for Python 2.6 Version 3.4 - released 2006-03-17 ---------------------------------------- diff --git a/setup.py b/setup.py index ee2968f..dc2ce39 100755 --- a/setup.py +++ b/setup.py @@ -35,8 +35,6 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', diff --git a/speed.sh b/speed.sh index 72cc9ad..f9b4493 100755 --- a/speed.sh +++ b/speed.sh @@ -28,7 +28,6 @@ check_command() { python_versions=" pypy - python2.6 python2.7 python3.3 python3.4 diff --git a/tox.ini b/tox.ini index 2aa7823..6e4c6a6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py26,py27,py33,py34,py35,pypy +envlist = py27,py33,py34,py35,pypy [pytest] addopts = -v --cov rsa --cov-report term-missing From c28cb895120310d8fa00bb93feb19048dee03440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 16:20:12 +0100 Subject: [PATCH 010/142] Using set instead of list, it's faster. --- rsa/prime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsa/prime.py b/rsa/prime.py index a5726e6..94d1ef8 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -103,7 +103,7 @@ def is_prime(number): # Check for small numbers. if number < 10: - return number in [2, 3, 5, 7] + return number in {2, 3, 5, 7} # Check for even numbers. if not (number & 1): From 8b7975c534b2a2339fd04f784032e8685b97dc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 16:28:28 +0100 Subject: [PATCH 011/142] Removed Python 2.6 from Travis configuration --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5304305..a365e3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ python: # Environment changes have to be manually synced with 'tox.ini'. # See: https://github.com/travis-ci/travis-ci/issues/3024 env: - - TOXENV=py26 - TOXENV=py27 - TOXENV=py33 - TOXENV=py34 From b57360048cb97bbf37e10404899f77972981491f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 17 Mar 2016 16:58:23 +0100 Subject: [PATCH 012/142] Added note about dropping Python 2.6 (and possibly 3.3) support. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ba1013b..4710363 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,5 @@ Those modules are marked as deprecated in version 3.4. Furthermore, in 4.0 the I/O functions will be streamlined to always work with bytes on all supported versions of Python. + +Version 4.0 will drop support for Python 2.6, and possibly for Python 3.3. From fdc2777af505052bf7300a6502e846d0573146fe Mon Sep 17 00:00:00 2001 From: Fabio Alessandro Locati Date: Fri, 18 Mar 2016 10:23:01 +0000 Subject: [PATCH 013/142] Fix name of file README in manifest --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 8cf0021..3865d3f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README +include README.md include LICENSE include *.py recursive-include rsa *.py From 2c6cd151dec9d98c173fc4c5c3a8e6f9110e7a77 Mon Sep 17 00:00:00 2001 From: adamantike Date: Mon, 21 Mar 2016 08:37:21 -0300 Subject: [PATCH 014/142] Include PEM test files in Manifest --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3865d3f..f41c924 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,4 @@ include README.md include LICENSE include *.py recursive-include rsa *.py -recursive-include tests *.py +recursive-include tests *.py *.pem From 56e9873a9194309c0dcdb1bfa2e7d1bfd5dcff89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 15:34:35 +0200 Subject: [PATCH 015/142] Removed monkey-patch of unittest This was required for Python 2.6, which we no longer support. --- tests/__init__.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 85bafc7..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,13 +0,0 @@ -import unittest -import sys - - -if sys.hexversion < 0x2070000: - # Monkey-patch unittest.TestCase to add assertIsInstance on Python 2.6 - - def assertIsInstance(self, obj, cls, msg=None): - """Same as self.assertTrue(isinstance(obj, cls)), with a nicer default message.""" - if not isinstance(obj, cls): - self.fail('%r is not an instance of %r but is a %r' % (obj, cls, type(obj))) - - unittest.TestCase.assertIsInstance = assertIsInstance From 7d79b6196f9f47d619188e373d7e054fd6599072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 15:38:03 +0200 Subject: [PATCH 016/142] Prevent running doctests in 'build' directory --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6e4c6a6..be38566 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ deps=pyasn1 >=0.1.3 pytest-cov [testenv:py35] -commands=py.test --doctest-modules [] +commands=py.test --doctest-modules rsa tests [pep8] max-line-length = 100 From c042a0e7fe12d5b4cbbc22d80fd4ae31f2781bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 18:13:30 +0200 Subject: [PATCH 017/142] PEP8 formatting --- rsa/key.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rsa/key.py b/rsa/key.py index 3e3fc42..40a7c83 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -519,15 +519,15 @@ def _save_pkcs1_der(self): class AsnPrivKey(univ.Sequence): componentType = namedtype.NamedTypes( - namedtype.NamedType('version', univ.Integer()), - namedtype.NamedType('modulus', univ.Integer()), - namedtype.NamedType('publicExponent', univ.Integer()), - namedtype.NamedType('privateExponent', univ.Integer()), - namedtype.NamedType('prime1', univ.Integer()), - namedtype.NamedType('prime2', univ.Integer()), - namedtype.NamedType('exponent1', univ.Integer()), - namedtype.NamedType('exponent2', univ.Integer()), - namedtype.NamedType('coefficient', univ.Integer()), + namedtype.NamedType('version', univ.Integer()), + namedtype.NamedType('modulus', univ.Integer()), + namedtype.NamedType('publicExponent', univ.Integer()), + namedtype.NamedType('privateExponent', univ.Integer()), + namedtype.NamedType('prime1', univ.Integer()), + namedtype.NamedType('prime2', univ.Integer()), + namedtype.NamedType('exponent1', univ.Integer()), + namedtype.NamedType('exponent2', univ.Integer()), + namedtype.NamedType('coefficient', univ.Integer()), ) # Create the ASN object From 46b72a462dbd7955f7e8f13cef1b1a3cb0af2c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 18:15:25 +0200 Subject: [PATCH 018/142] Added unittest for rsa.common.inverse This unittest tests both execution branches of the function, reducing randomness of code coverage. --- rsa/common.py | 2 +- tests/test_common.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rsa/common.py b/rsa/common.py index e074334..b573252 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -131,7 +131,7 @@ def extended_gcd(a, b): def inverse(x, n): - """Returns x^-1 (mod n) + """Returns the inverse of x % n under multiplication, a.k.a x^-1 (mod n) >>> inverse(7, 4) 3 diff --git a/tests/test_common.py b/tests/test_common.py index 453dcc8..ef32f61 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -18,7 +18,7 @@ import unittest import struct from rsa._compat import byte, b -from rsa.common import byte_size, bit_size, _bit_size +from rsa.common import byte_size, bit_size, _bit_size, inverse class TestByte(unittest.TestCase): @@ -75,3 +75,13 @@ def test_values(self): self.assertEqual(_bit_size(1 << 1024), 1025) self.assertEqual(_bit_size((1 << 1024) + 1), 1025) self.assertEqual(_bit_size((1 << 1024) - 1), 1024) + + +class TestInverse(unittest.TestCase): + def test_normal(self): + self.assertEqual(3, inverse(7, 4)) + self.assertEqual(9, inverse(5, 11)) + + def test_not_relprime(self): + self.assertRaises(ValueError, inverse, 4, 8) + self.assertRaises(ValueError, inverse, 25, 5) From fec61ece4c1593e79bc52c84cf69a715cd6a9dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 18:18:50 +0200 Subject: [PATCH 019/142] Added unittest for rsa.key.gen_keys This unittest tests both execution branches of the function (keys relatively prime or not), reducing randomness of code coverage. --- tests/test_key.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_key.py b/tests/test_key.py index 0e62f55..24f6501 100644 --- a/tests/test_key.py +++ b/tests/test_key.py @@ -40,3 +40,20 @@ def test_default_exponent(self): self.assertEqual(0x10001, priv.e) self.assertEqual(0x10001, pub.e) + + def test_custom_getprime_func(self): + # List of primes to test with, in order [p, q, p, q, ....] + primes = [64123, 50957, 39317, 33107] + + def getprime(_): + return primes.pop(0) + + # This exponent will cause two other primes to be generated. + exponent = 136407 + + (p, q, e, d) = rsa.key.gen_keys(64, + accurate=False, + getprime_func=getprime, + exponent=exponent) + self.assertEqual(39317, p) + self.assertEqual(33107, q) From a5487826a1f28952a560c50f14e284c4292d94ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 18:20:17 +0200 Subject: [PATCH 020/142] Introduced NotRelativePrimeError exception. This makes catching exceptions slightly stronger, as it is now possible to check for this specific exception. Furthermore, information about the not-prime numbers is included in the exception object. --- rsa/common.py | 11 ++++++++++- rsa/key.py | 8 +++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/rsa/common.py b/rsa/common.py index b573252..34142cc 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -17,6 +17,15 @@ """Common functionality shared by several modules.""" +class NotRelativePrimeError(ValueError): + def __init__(self, a, b, d, msg=None): + super(NotRelativePrimeError, self).__init__( + msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d)) + self.a = a + self.b = b + self.d = d + + def bit_size(num): """ Number of bits needed to represent a integer excluding any prefix @@ -142,7 +151,7 @@ def inverse(x, n): (divider, inv, _) = extended_gcd(x, n) if divider != 1: - raise ValueError("x (%d) and n (%d) are not relatively prime" % (x, n)) + raise NotRelativePrimeError(x, n, divider) return inv diff --git a/rsa/key.py b/rsa/key.py index 40a7c83..a5afced 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -666,9 +666,11 @@ def calculate_keys_custom_exponent(p, q, exponent): try: d = rsa.common.inverse(exponent, phi_n) - except ValueError: - raise ValueError("e (%d) and phi_n (%d) are not relatively prime" % - (exponent, phi_n)) + except rsa.common.NotRelativePrimeError as ex: + raise rsa.common.NotRelativePrimeError( + exponent, phi_n, ex.d, + msg="e (%d) and phi_n (%d) are not relatively prime (divider=%i)" % + (exponent, phi_n, ex.d)) if (exponent * d) % phi_n != 1: raise ValueError("e (%d) and d (%d) are not mult. inv. modulo " From 9a9e08ce6559f888c345935bac7f85f747fc5f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 19:05:23 +0200 Subject: [PATCH 021/142] More tests with hard-coded 'random' values. This reduces noise in the code coverage measurements. --- tests/test_key.py | 4 +++- tests/test_prime.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/test_key.py b/tests/test_key.py index 24f6501..833a6aa 100644 --- a/tests/test_key.py +++ b/tests/test_key.py @@ -43,7 +43,9 @@ def test_default_exponent(self): def test_custom_getprime_func(self): # List of primes to test with, in order [p, q, p, q, ....] - primes = [64123, 50957, 39317, 33107] + # By starting with two of the same primes, we test that this is + # properly rejected. + primes = [64123, 64123, 64123, 50957, 39317, 33107] def getprime(_): return primes.pop(0) diff --git a/tests/test_prime.py b/tests/test_prime.py index a47c3f2..173c991 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -19,6 +19,7 @@ import unittest import rsa.prime +import rsa.randnum class PrimeTest(unittest.TestCase): @@ -42,3 +43,34 @@ def test_is_prime(self): # Test around the 50th millionth known prime. self.assertTrue(rsa.prime.is_prime(982451653)) self.assertFalse(rsa.prime.is_prime(982451653 * 961748941)) + + def test_miller_rabin_primality_testing(self): + """Uses monkeypatching to ensure certain random numbers. + + This allows us to predict/control the code path. + """ + + randints = [] + + def fake_randint(maxvalue): + return randints.pop(0) + + orig_randint = rsa.randnum.randint + rsa.randnum.randint = fake_randint + try: + # 'n is composite' + randints.append(2630484831) # causes the 'n is composite' case with n=3784949785 + self.assertEqual(False, rsa.prime.miller_rabin_primality_testing(2787998641, 7)) + self.assertEqual([], randints) + + # 'Exit inner loop and continue with next witness' + randints.extend([ + 2119139097, # causes 'Exit inner loop and continue with next witness' + # the next witnesses for the above case: + 3051067715, 3603501762, 3230895846, 3687808132, 3760099986, 4026931494, 3022471881, + ]) + self.assertEqual(True, rsa.prime.miller_rabin_primality_testing(2211417913, + len(randints))) + self.assertEqual([], randints) + finally: + rsa.randnum.randint = orig_randint From 5e08c91a122bac2e59e176928aa1b24f9aa7b0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 19:45:47 +0200 Subject: [PATCH 022/142] No longer require string operations to find bit lengths. Now that we no longer support Python 2.6, we can use int.bit_length() instead. Co-authored by @adamantike. --- rsa/common.py | 41 ++++------------------------------------- tests/test_common.py | 23 ++++++++++++++++------- 2 files changed, 20 insertions(+), 44 deletions(-) diff --git a/rsa/common.py b/rsa/common.py index 34142cc..4f8c0d9 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -31,9 +31,6 @@ def bit_size(num): Number of bits needed to represent a integer excluding any prefix 0 bits. - As per definition from https://wiki.python.org/moin/BitManipulation and - to match the behavior of the Python 3 API. - Usage:: >>> bit_size(1023) @@ -50,41 +47,11 @@ def bit_size(num): :returns: Returns the number of bits in the integer. """ - if num == 0: - return 0 - if num < 0: - num = -num - - # Make sure this is an int and not a float. - num & 1 - - hex_num = "%x" % num - return ((len(hex_num) - 1) * 4) + { - '0': 0, '1': 1, '2': 2, '3': 2, - '4': 3, '5': 3, '6': 3, '7': 3, - '8': 4, '9': 4, 'a': 4, 'b': 4, - 'c': 4, 'd': 4, 'e': 4, 'f': 4, - }[hex_num[0]] - - -def _bit_size(number): - """ - Returns the number of bits required to hold a specific long number. - """ - if number < 0: - raise ValueError('Only nonnegative numbers possible: %s' % number) - - if number == 0: - return 0 - - # This works, even with very large numbers. When using math.log(number, 2), - # you'll get rounding errors and it'll fail. - bits = 0 - while number: - bits += 1 - number >>= 1 - return bits + try: + return num.bit_length() + except AttributeError: + raise TypeError('bit_size(num) only supports integers, not %r' % type(num)) def byte_size(number): diff --git a/tests/test_common.py b/tests/test_common.py index ef32f61..e26e004 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -18,7 +18,7 @@ import unittest import struct from rsa._compat import byte, b -from rsa.common import byte_size, bit_size, _bit_size, inverse +from rsa.common import byte_size, bit_size, inverse class TestByte(unittest.TestCase): @@ -69,12 +69,21 @@ def test_values(self): self.assertEqual(bit_size((1 << 1024) + 1), 1025) self.assertEqual(bit_size((1 << 1024) - 1), 1024) - self.assertEqual(_bit_size(1023), 10) - self.assertEqual(_bit_size(1024), 11) - self.assertEqual(_bit_size(1025), 11) - self.assertEqual(_bit_size(1 << 1024), 1025) - self.assertEqual(_bit_size((1 << 1024) + 1), 1025) - self.assertEqual(_bit_size((1 << 1024) - 1), 1024) + def test_negative_values(self): + self.assertEqual(bit_size(-1023), 10) + self.assertEqual(bit_size(-1024), 11) + self.assertEqual(bit_size(-1025), 11) + self.assertEqual(bit_size(-1 << 1024), 1025) + self.assertEqual(bit_size(-((1 << 1024) + 1)), 1025) + self.assertEqual(bit_size(-((1 << 1024) - 1)), 1024) + + def test_bad_type(self): + self.assertRaises(TypeError, bit_size, []) + self.assertRaises(TypeError, bit_size, ()) + self.assertRaises(TypeError, bit_size, dict()) + self.assertRaises(TypeError, bit_size, "") + self.assertRaises(TypeError, bit_size, None) + self.assertRaises(TypeError, bit_size, 0.0) class TestInverse(unittest.TestCase): From 96eaa5e82b75b8cfc36a67ad72a768f03c285647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 29 Mar 2016 20:39:08 +0200 Subject: [PATCH 023/142] Implemented __hash__ function for key objects. Overriding __eq__ blocks inheritance of __hash__ in Python 3. Fixes issue #55 --- rsa/key.py | 6 ++++++ tests/test_key.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/rsa/key.py b/rsa/key.py index a5afced..73494b9 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -228,6 +228,9 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) + def __hash__(self): + return hash((self.n, self.e)) + @classmethod def _load_pkcs1_der(cls, keyfile): """Loads a key in PKCS#1 DER format. @@ -430,6 +433,9 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) + def __hash__(self): + return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef)) + def blinded_decrypt(self, encrypted): """Decrypts the message using blinding to prevent side-channel attacks. diff --git a/tests/test_key.py b/tests/test_key.py index 833a6aa..afefa3a 100644 --- a/tests/test_key.py +++ b/tests/test_key.py @@ -59,3 +59,14 @@ def getprime(_): exponent=exponent) self.assertEqual(39317, p) self.assertEqual(33107, q) + + +class HashTest(unittest.TestCase): + """Test hashing of keys""" + + def test_hash_possible(self): + priv, pub = rsa.key.newkeys(16) + + # This raises a TypeError when hashing isn't possible. + hash(priv) + hash(pub) From 38a7255a5e935c3b2663613392db9d5290fc2340 Mon Sep 17 00:00:00 2001 From: adamantike Date: Mon, 28 Mar 2016 13:12:12 -0300 Subject: [PATCH 024/142] Set Miller-Rabin rounds based on bitsize --- CHANGELOG.txt | 1 + rsa/prime.py | 40 ++++++++++++++++++++++++++++++++-------- tests/test_prime.py | 14 ++++++++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 315bb4a..6488615 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -12,6 +12,7 @@ Version 4.0 - in development - Removed CLI commands that use the VARBLOCK/bigfile format. - Ensured that PublicKey.save_pkcs1() and PrivateKey.save_pkcs1() always return bytes. - Dropped support for Python 2.6 +- Miller-Rabin iterations determined by bitsize of key. Version 3.4.2 - released 2016-03-29 diff --git a/rsa/prime.py b/rsa/prime.py index 94d1ef8..29fa498 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -20,6 +20,7 @@ Roberto Tamassia, 2002. """ +import rsa.common import rsa.randnum __all__ = ['getprime', 'are_relatively_prime'] @@ -37,6 +38,32 @@ def gcd(p, q): return p +def get_primality_testing_rounds(number): + """Returns minimum number of rounds for Miller-Rabing primality testing, + based on number bitsize. + + According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of + rounds of M-R testing, using an error probability of 2 ** (-100), for + different p, q bitsizes are: + * p, q bitsize: 512; rounds: 7 + * p, q bitsize: 1024; rounds: 4 + * p, q bitsize: 1536; rounds: 3 + See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + """ + + # Calculate number bitsize. + bitsize = rsa.common.bit_size(number) + # Set number of rounds. + if bitsize >= 1536: + return 3 + if bitsize >= 1024: + return 4 + if bitsize >= 512: + return 7 + # For smaller bitsizes, set arbitrary number of rounds. + return 10 + + def miller_rabin_primality_testing(n, k): """Calculates whether n is composite (which is always correct) or prime (which theoretically is incorrect with error probability 4**-k), by @@ -109,14 +136,11 @@ def is_prime(number): if not (number & 1): return False - # According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of - # rounds of M-R testing, using an error probability of 2 ** (-100), for - # different p, q bitsizes are: - # * p, q bitsize: 512; rounds: 7 - # * p, q bitsize: 1024; rounds: 4 - # * p, q bitsize: 1536; rounds: 3 - # See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf - return miller_rabin_primality_testing(number, 7) + # Calculate minimum number of rounds. + k = get_primality_testing_rounds(number) + + # Run primality testing with (minimum + 1) rounds. + return miller_rabin_primality_testing(number, k + 1) def getprime(nbits): diff --git a/tests/test_prime.py b/tests/test_prime.py index 173c991..44d8d71 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -74,3 +74,17 @@ def fake_randint(maxvalue): self.assertEqual([], randints) finally: rsa.randnum.randint = orig_randint + + def test_get_primality_testing_rounds(self): + """Test round calculation for primality testing.""" + + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 63), 10) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 127), 10) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 255), 10) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 511), 7) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 767), 7) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 1023), 4) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 1279), 4) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 1535), 3) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 2047), 3) + self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 4095), 3) From f520014826fbbfffea1ace30fd46af9c6097c547 Mon Sep 17 00:00:00 2001 From: adamantike Date: Mon, 28 Mar 2016 15:29:59 -0300 Subject: [PATCH 025/142] Trivial fix on primality testing --- rsa/prime.py | 2 +- tests/test_prime.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rsa/prime.py b/rsa/prime.py index 29fa498..ab3eb97 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -96,7 +96,7 @@ def miller_rabin_primality_testing(n, k): # Test k witnesses. for _ in range(k): # Generate random integer a, where 2 <= a <= (n - 2) - a = rsa.randnum.randint(n - 4) + 2 + a = rsa.randnum.randint(n - 3) + 1 x = pow(a, d, n) if x == 1 or x == n - 1: diff --git a/tests/test_prime.py b/tests/test_prime.py index 44d8d71..57620a1 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -59,15 +59,15 @@ def fake_randint(maxvalue): rsa.randnum.randint = fake_randint try: # 'n is composite' - randints.append(2630484831) # causes the 'n is composite' case with n=3784949785 + randints.append(2630484832) # causes the 'n is composite' case with n=3784949785 self.assertEqual(False, rsa.prime.miller_rabin_primality_testing(2787998641, 7)) self.assertEqual([], randints) # 'Exit inner loop and continue with next witness' randints.extend([ - 2119139097, # causes 'Exit inner loop and continue with next witness' + 2119139098, # causes 'Exit inner loop and continue with next witness' # the next witnesses for the above case: - 3051067715, 3603501762, 3230895846, 3687808132, 3760099986, 4026931494, 3022471881, + 3051067716, 3603501763, 3230895847, 3687808133, 3760099987, 4026931495, 3022471882, ]) self.assertEqual(True, rsa.prime.miller_rabin_primality_testing(2211417913, len(randints))) From 5acf9cdde2a5b93c9603619eedd3ed184e53d444 Mon Sep 17 00:00:00 2001 From: adamantike Date: Fri, 15 Apr 2016 13:56:25 -0300 Subject: [PATCH 026/142] Remove old version excluded files from CodeClimate --- .codeclimate.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 92ec005..907c00b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -15,4 +15,3 @@ ratings: - "**.py" exclude_paths: - tests/**/* -- rsa/_version*.py From 08e609f59219ca4bbea6cf781d007ca1a4fceb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sat, 23 Apr 2016 10:02:23 +0200 Subject: [PATCH 027/142] Drop psyco usage --- CHANGELOG.txt | 1 + rsa/transform.py | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6488615..f3e0955 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -12,6 +12,7 @@ Version 4.0 - in development - Removed CLI commands that use the VARBLOCK/bigfile format. - Ensured that PublicKey.save_pkcs1() and PrivateKey.save_pkcs1() always return bytes. - Dropped support for Python 2.6 +- Dropped support for Psyco - Miller-Rabin iterations determined by bitsize of key. diff --git a/rsa/transform.py b/rsa/transform.py index 16061a9..f325b17 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -21,16 +21,6 @@ from __future__ import absolute_import -try: - # We'll use psyco if available on 32-bit architectures to speed up code. - # Using psyco (if available) cuts down the execution time on Python 2.5 - # at least by half. - import psyco - - psyco.full() -except ImportError: - pass - import binascii from struct import pack from rsa import common From a72efaa6a3709fa8f2079914c68bcb1506fe3064 Mon Sep 17 00:00:00 2001 From: adamantike Date: Sat, 23 Apr 2016 10:35:26 +0200 Subject: [PATCH 028/142] Unit test for Mersenne primes --- tests/test_prime.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_prime.py b/tests/test_prime.py index 57620a1..0ecdaa6 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -75,6 +75,25 @@ def fake_randint(maxvalue): finally: rsa.randnum.randint = orig_randint + def test_mersenne_primes(self): + """Tests first known Mersenne primes. + + Mersenne primes are prime numbers that can be written in the form + `Mn = 2**n - 1` for some integer `n`. For the list of known Mersenne + primes, see: + https://en.wikipedia.org/wiki/Mersenne_prime#List_of_known_Mersenne_primes + """ + + # List of known Mersenne exponents. + known_mersenne_exponents = [ + 2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, + 2203, 2281, 4423, + ] + + # Test Mersenne primes. + for exp in known_mersenne_exponents: + self.assertTrue(rsa.prime.is_prime(2**exp - 1)) + def test_get_primality_testing_rounds(self): """Test round calculation for primality testing.""" From 98d9eb170dd0c5400000210ec7c0d2020dcd676e Mon Sep 17 00:00:00 2001 From: adamantike Date: Sun, 17 Apr 2016 00:24:47 -0300 Subject: [PATCH 029/142] Remove sys.maxint fallback usage --- rsa/_compat.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index 93393d9..9d1d99d 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -21,11 +21,7 @@ import sys from struct import pack -try: - MAX_INT = sys.maxsize -except AttributeError: - MAX_INT = sys.maxint - +MAX_INT = sys.maxsize MAX_INT64 = (1 << 63) - 1 MAX_INT32 = (1 << 31) - 1 MAX_INT16 = (1 << 15) - 1 From 97b809a3025066bcaf9437eb99eb1c5f12214fcf Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 23 Apr 2016 05:44:26 -0300 Subject: [PATCH 030/142] Trivial link fix in changelog (#66) --- CHANGELOG.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f3e0955..9c165fa 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -48,7 +48,7 @@ Version 3.4 - released 2016-03-17 [1] https://travis-ci.org/sybrenstuvel/python-rsa [2] https://coveralls.io/github/sybrenstuvel/python-rsa [3] https://codeclimate.com/github/sybrenstuvel/python-rsa -[4] http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pd +[4] http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf Version 3.3 - released 2016-01-13 @@ -136,7 +136,7 @@ Version 3.0 - released 2011-08-05 the size of both ``p`` and ``q``. This is the common interpretation of RSA keysize. To get the old behaviour, double the keysize when generating a new key. - + - Added a lot of doctests - Added random-padded encryption and decryption using PKCS#1 version 1.5 @@ -154,4 +154,3 @@ Version 2.0 ---------------------------------------- - Security improvements by Barry Mead. - From fb062b303a84f716e6180a365f7f507c662152f7 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 23 Apr 2016 05:45:13 -0300 Subject: [PATCH 031/142] Remove bit_size test comparison from speed script (#67) --- speed.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/speed.sh b/speed.sh index f9b4493..73d5935 100755 --- a/speed.sh +++ b/speed.sh @@ -42,12 +42,3 @@ for version in $python_versions; do "$version" -mtimeit -s'from rsa.transform import _int2bytes; n = 1<<4096' '_int2bytes(n)' fi done - -echo "bit_size speed test" -for version in $python_versions; do - if check_command "$version"; then - echo "$version" - "$version" -mtimeit -s'from rsa.common import bit_size; n = 1<<4096' 'bit_size(n)' - "$version" -mtimeit -s'from rsa.common import _bit_size; n = 1<<4096' '_bit_size(n)' - fi -done From cf124d3fbf4aaa98da36d65ec92daf0d7bc8c094 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 23 Apr 2016 05:46:16 -0300 Subject: [PATCH 032/142] Fix edge case for byte() call at pkcs1 test (#64) Nice catch :) --- tests/test_pkcs1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 39555f6..5702aae 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -48,7 +48,8 @@ def test_decoding_failure(self): a = encrypted[5] if is_bytes(a): a = ord(a) - encrypted = encrypted[:5] + byte(a + 1) + encrypted[6:] + altered_a = (a + 1) % 256 + encrypted = encrypted[:5] + byte(altered_a) + encrypted[6:] self.assertRaises(pkcs1.DecryptionError, pkcs1.decrypt, encrypted, self.priv) From 7e5854dc6f4c748ffcbff0066dbfef0a8bdbfbf6 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 8 May 2016 08:18:29 -0300 Subject: [PATCH 033/142] Use 'assertEqual' instead of deprecated alias (#73) --- tests/test_prime.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_prime.py b/tests/test_prime.py index 0ecdaa6..75b80b3 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -97,13 +97,13 @@ def test_mersenne_primes(self): def test_get_primality_testing_rounds(self): """Test round calculation for primality testing.""" - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 63), 10) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 127), 10) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 255), 10) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 511), 7) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 767), 7) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 1023), 4) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 1279), 4) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 1535), 3) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 2047), 3) - self.assertEquals(rsa.prime.get_primality_testing_rounds(1 << 4095), 3) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 63), 10) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 127), 10) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 255), 10) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 511), 7) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 767), 7) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 1023), 4) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 1279), 4) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 1535), 3) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 2047), 3) + self.assertEqual(rsa.prime.get_primality_testing_rounds(1 << 4095), 3) From 1369d748e9b557ed769c9456dc43ab3c0530663f Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 8 May 2016 08:19:07 -0300 Subject: [PATCH 034/142] xrange compatibility optimization for Python 2 (#69) --- rsa/_compat.py | 8 ++++++++ rsa/key.py | 2 +- rsa/parallel.py | 1 + rsa/pem.py | 3 ++- rsa/pkcs1.py | 2 +- rsa/prime.py | 1 + rsa/transform.py | 1 + tests/test_compat.py | 2 +- tests/test_prime.py | 1 + 9 files changed, 17 insertions(+), 4 deletions(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index 9d1d99d..deef1fc 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -52,6 +52,14 @@ def byte_literal(s): def byte_literal(s): return s +# Range generator. +try: + # < Python3 + range = xrange +except NameError: + # Python3 + range = range + # ``long`` is no more. Do type detection using this instead. try: integer_types = (int, long) diff --git a/rsa/key.py b/rsa/key.py index 73494b9..2b59751 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -35,7 +35,7 @@ import logging -from rsa._compat import b +from rsa._compat import b, range import rsa.prime import rsa.pem import rsa.common diff --git a/rsa/parallel.py b/rsa/parallel.py index edc924f..edaf6bc 100644 --- a/rsa/parallel.py +++ b/rsa/parallel.py @@ -28,6 +28,7 @@ import multiprocessing as mp +from rsa._compat import range import rsa.prime import rsa.randnum diff --git a/rsa/pem.py b/rsa/pem.py index d5427a0..db0304a 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -17,7 +17,8 @@ """Functions that load and write PEM-encoded files.""" import base64 -from rsa._compat import b, is_bytes + +from rsa._compat import b, is_bytes, range def _markers(pem_marker): diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index fdbf093..cfea847 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -31,7 +31,7 @@ import hashlib import os -from rsa._compat import b +from rsa._compat import b, range from rsa import common, transform, core # ASN.1 codes that describe the hash algorithm used. diff --git a/rsa/prime.py b/rsa/prime.py index ab3eb97..d8c1f04 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -20,6 +20,7 @@ Roberto Tamassia, 2002. """ +from rsa._compat import range import rsa.common import rsa.randnum diff --git a/rsa/transform.py b/rsa/transform.py index f325b17..ae3224b 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -23,6 +23,7 @@ import binascii from struct import pack + from rsa import common from rsa._compat import is_integer, b, byte, get_word_alignment, ZERO_BYTE, EMPTY_BYTE diff --git a/tests/test_compat.py b/tests/test_compat.py index 74d6f53..2a58df5 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -18,7 +18,7 @@ import struct import sys -from rsa._compat import is_bytes, byte, b +from rsa._compat import b, byte, is_bytes, range class TestByte(unittest.TestCase): diff --git a/tests/test_prime.py b/tests/test_prime.py index 75b80b3..f3bda9b 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -18,6 +18,7 @@ import unittest +from rsa._compat import range import rsa.prime import rsa.randnum From 505a25a1ada6f0d2b161ac7d12e626c7965f16f5 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 8 May 2016 08:19:17 -0300 Subject: [PATCH 035/142] Update security warning (#70) --- rsa/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 942e899..e69bbf3 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -18,9 +18,8 @@ Module for calculating large primes, and RSA encryption, decryption, signing and verification. Includes generating public and private keys. -WARNING: this implementation does not use random padding, compression of the -cleartext input to prevent repetitions, or other common security improvements. -Use with care. +WARNING: this implementation does not use compression of the cleartext input to +prevent repetitions, or other common security improvements. Use with care. """ From 9f57740ec47f828b2be0cf0a104638c4abee9c3d Mon Sep 17 00:00:00 2001 From: adamantike Date: Sun, 8 May 2016 15:36:57 -0300 Subject: [PATCH 036/142] Drop byte_literal in favour of b'' --- rsa/_compat.py | 21 --------------------- rsa/key.py | 6 +++--- rsa/pem.py | 16 ++++++++-------- rsa/pkcs1.py | 36 ++++++++++++++++++------------------ rsa/transform.py | 22 +++++++++++----------- tests/test_cli.py | 7 +++---- tests/test_common.py | 6 +++--- tests/test_compat.py | 4 ++-- tests/test_load_save_keys.py | 30 ++++++++++++++---------------- tests/test_pem.py | 6 +++--- tests/test_pkcs1.py | 10 +++++----- tests/test_transform.py | 25 ++++++++++++------------- 12 files changed, 82 insertions(+), 107 deletions(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index deef1fc..6824bdb 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -37,21 +37,6 @@ # Else we just assume 64-bit processor keeping up with modern times. MACHINE_WORD_SIZE = 64 -try: - # < Python3 - unicode_type = unicode -except NameError: - # Python3. - unicode_type = str - -# Fake byte literals. -if str is unicode_type: - def byte_literal(s): - return s.encode('latin1') -else: - def byte_literal(s): - return s - # Range generator. try: # < Python3 @@ -66,12 +51,6 @@ def byte_literal(s): except NameError: integer_types = (int,) -b = byte_literal - -# To avoid calling b() multiple times in tight loops. -ZERO_BYTE = b('\x00') -EMPTY_BYTE = b('') - def is_bytes(obj): """ diff --git a/rsa/key.py b/rsa/key.py index 2b59751..eb30bbe 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -35,7 +35,7 @@ import logging -from rsa._compat import b, range +from rsa._compat import range import rsa.prime import rsa.pem import rsa.common @@ -563,7 +563,7 @@ def _load_pkcs1_pem(cls, keyfile): :return: a PrivateKey object """ - der = rsa.pem.load_pem(keyfile, b('RSA PRIVATE KEY')) + der = rsa.pem.load_pem(keyfile, b'RSA PRIVATE KEY') return cls._load_pkcs1_der(der) def _save_pkcs1_pem(self): @@ -574,7 +574,7 @@ def _save_pkcs1_pem(self): """ der = self._save_pkcs1_der() - return rsa.pem.save_pem(der, b('RSA PRIVATE KEY')) + return rsa.pem.save_pem(der, b'RSA PRIVATE KEY') def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True): diff --git a/rsa/pem.py b/rsa/pem.py index db0304a..2ddfae8 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -18,7 +18,7 @@ import base64 -from rsa._compat import b, is_bytes, range +from rsa._compat import is_bytes, range def _markers(pem_marker): @@ -29,8 +29,8 @@ def _markers(pem_marker): if not is_bytes(pem_marker): pem_marker = pem_marker.encode('ascii') - return (b('-----BEGIN ') + pem_marker + b('-----'), - b('-----END ') + pem_marker + b('-----')) + return (b'-----BEGIN ' + pem_marker + b'-----', + b'-----END ' + pem_marker + b'-----') def load_pem(contents, pem_marker): @@ -82,7 +82,7 @@ def load_pem(contents, pem_marker): break # Load fields - if b(':') in line: + if b':' in line: continue pem_lines.append(line) @@ -95,7 +95,7 @@ def load_pem(contents, pem_marker): raise ValueError('No PEM end marker "%s" found' % pem_end) # Base64-decode the contents - pem = b('').join(pem_lines) + pem = b''.join(pem_lines) return base64.standard_b64decode(pem) @@ -113,7 +113,7 @@ def save_pem(contents, pem_marker): (pem_start, pem_end) = _markers(pem_marker) - b64 = base64.standard_b64encode(contents).replace(b('\n'), b('')) + b64 = base64.standard_b64encode(contents).replace(b'\n', b'') pem_lines = [pem_start] for block_start in range(0, len(b64), 64): @@ -121,6 +121,6 @@ def save_pem(contents, pem_marker): pem_lines.append(block) pem_lines.append(pem_end) - pem_lines.append(b('')) + pem_lines.append(b'') - return b('\n').join(pem_lines) + return b'\n'.join(pem_lines) diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index cfea847..bb08f4d 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -31,16 +31,16 @@ import hashlib import os -from rsa._compat import b, range +from rsa._compat import range from rsa import common, transform, core # ASN.1 codes that describe the hash algorithm used. HASH_ASN1 = { - 'MD5': b('\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10'), - 'SHA-1': b('\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'), - 'SHA-256': b('\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'), - 'SHA-384': b('\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30'), - 'SHA-512': b('\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40'), + 'MD5': b'\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10', + 'SHA-1': b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14', + 'SHA-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20', + 'SHA-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30', + 'SHA-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40', } HASH_METHODS = { @@ -87,7 +87,7 @@ def _pad_for_encryption(message, target_length): ' space for %i' % (msglength, max_msglength)) # Get random padding - padding = b('') + padding = b'' padding_length = target_length - msglength - 3 # We remove 0-bytes, so we'll end up with less padding than we've asked for, @@ -99,15 +99,15 @@ def _pad_for_encryption(message, target_length): # after removing the 0-bytes. This increases the chance of getting # enough bytes, especially when needed_bytes is small new_padding = os.urandom(needed_bytes + 5) - new_padding = new_padding.replace(b('\x00'), b('')) + new_padding = new_padding.replace(b'\x00', b'') padding = padding + new_padding[:needed_bytes] assert len(padding) == padding_length - return b('').join([b('\x00\x02'), - padding, - b('\x00'), - message]) + return b''.join([b'\x00\x02', + padding, + b'\x00', + message]) def _pad_for_signing(message, target_length): @@ -138,10 +138,10 @@ def _pad_for_signing(message, target_length): padding_length = target_length - msglength - 3 - return b('').join([b('\x00\x01'), - padding_length * b('\xff'), - b('\x00'), - message]) + return b''.join([b'\x00\x01', + padding_length * b'\xff', + b'\x00', + message]) def encrypt(message, pub_key): @@ -233,12 +233,12 @@ def decrypt(crypto, priv_key): cleartext = transform.int2bytes(decrypted, blocksize) # If we can't find the cleartext marker, decryption failed. - if cleartext[0:2] != b('\x00\x02'): + if cleartext[0:2] != b'\x00\x02': raise DecryptionError('Decryption failed') # Find the 00 separator between the padding and the message try: - sep_idx = cleartext.index(b('\x00'), 2) + sep_idx = cleartext.index(b'\x00', 2) except ValueError: raise DecryptionError('Decryption failed') diff --git a/rsa/transform.py b/rsa/transform.py index ae3224b..acf044d 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -25,7 +25,7 @@ from struct import pack from rsa import common -from rsa._compat import is_integer, b, byte, get_word_alignment, ZERO_BYTE, EMPTY_BYTE +from rsa._compat import byte, is_integer, get_word_alignment def bytes2int(raw_bytes): @@ -83,7 +83,7 @@ def _int2bytes(number, block_size=None): # Do some bounds checking if number == 0: needed_bytes = 1 - raw_bytes = [ZERO_BYTE] + raw_bytes = [b'\x00'] else: needed_bytes = common.byte_size(number) raw_bytes = [] @@ -101,14 +101,14 @@ def _int2bytes(number, block_size=None): # Pad with zeroes to fill the block if block_size and block_size > 0: - padding = (block_size - needed_bytes) * ZERO_BYTE + padding = (block_size - needed_bytes) * b'\x00' else: - padding = EMPTY_BYTE + padding = b'' - return padding + EMPTY_BYTE.join(raw_bytes) + return padding + b''.join(raw_bytes) -def bytes_leading(raw_bytes, needle=ZERO_BYTE): +def bytes_leading(raw_bytes, needle=b'\x00'): """ Finds the number of prefixed byte occurrences in the haystack. @@ -117,7 +117,7 @@ def bytes_leading(raw_bytes, needle=ZERO_BYTE): :param raw_bytes: Raw bytes. :param needle: - The byte to count. Default \000. + The byte to count. Default \x00. :returns: The number of leading needle bytes. """ @@ -177,7 +177,7 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): # Ensure these are integers. number & 1 - raw_bytes = b('') + raw_bytes = b'' # Pack the integer one machine word at a time into bytes. num = number @@ -189,7 +189,7 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): # Obtain the index of the first non-zero byte. zero_leading = bytes_leading(raw_bytes) if number == 0: - raw_bytes = ZERO_BYTE + raw_bytes = b'\x00' # De-padding. raw_bytes = raw_bytes[zero_leading:] @@ -200,12 +200,12 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): "Need %d bytes for number, but fill size is %d" % (length, fill_size) ) - raw_bytes = raw_bytes.rjust(fill_size, ZERO_BYTE) + raw_bytes = raw_bytes.rjust(fill_size, b'\x00') elif chunk_size and chunk_size > 0: remainder = length % chunk_size if remainder: padding_size = chunk_size - remainder - raw_bytes = raw_bytes.rjust(length + padding_size, ZERO_BYTE) + raw_bytes = raw_bytes.rjust(length + padding_size, b'\x00') return raw_bytes diff --git a/tests/test_cli.py b/tests/test_cli.py index aad734a..4ae8da3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -15,7 +15,6 @@ import rsa import rsa.cli import rsa.util -from rsa._compat import b if sys.version_info[0] < 3: def make_buffer(): @@ -135,8 +134,8 @@ def test_keygen_priv_stdout(self): rsa.cli.keygen() lines = get_bytes_out(out).splitlines() - self.assertEqual(b('-----BEGIN RSA PRIVATE KEY-----'), lines[0]) - self.assertEqual(b('-----END RSA PRIVATE KEY-----'), lines[-1]) + self.assertEqual(b'-----BEGIN RSA PRIVATE KEY-----', lines[0]) + self.assertEqual(b'-----END RSA PRIVATE KEY-----', lines[-1]) # The key size should be shown on stderr self.assertTrue('128-bit key' in err.getvalue()) @@ -217,7 +216,7 @@ def test_encrypt_decrypt(self): # We should have the original cleartext on stdout now. output = get_bytes_out(out) - self.assertEqual(b('Hello cleartext RSA users!'), output) + self.assertEqual(b'Hello cleartext RSA users!', output) @cleanup_files('encrypted.txt', 'cleartext.txt') def test_encrypt_decrypt_unhappy(self): diff --git a/tests/test_common.py b/tests/test_common.py index e26e004..af13695 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -17,14 +17,14 @@ import unittest import struct -from rsa._compat import byte, b +from rsa._compat import byte from rsa.common import byte_size, bit_size, inverse class TestByte(unittest.TestCase): def test_values(self): - self.assertEqual(byte(0), b('\x00')) - self.assertEqual(byte(255), b('\xff')) + self.assertEqual(byte(0), b'\x00') + self.assertEqual(byte(255), b'\xff') def test_struct_error_when_out_of_bounds(self): self.assertRaises(struct.error, byte, 256) diff --git a/tests/test_compat.py b/tests/test_compat.py index 2a58df5..0013155 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -18,7 +18,7 @@ import struct import sys -from rsa._compat import b, byte, is_bytes, range +from rsa._compat import byte, is_bytes, range class TestByte(unittest.TestCase): @@ -33,4 +33,4 @@ def test_raises_StructError_on_overflow(self): self.assertRaises(struct.error, byte, -1) def test_byte_literal(self): - self.assertIsInstance(b('abc'), bytes) + self.assertIsInstance(b'abc', bytes) diff --git a/tests/test_load_save_keys.py b/tests/test_load_save_keys.py index 0caa067..ef6584b 100644 --- a/tests/test_load_save_keys.py +++ b/tests/test_load_save_keys.py @@ -21,55 +21,53 @@ import os.path import pickle -from rsa._compat import b - import rsa.key -B64PRIV_DER = b('MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt') +B64PRIV_DER = b'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt' PRIVATE_DER = base64.standard_b64decode(B64PRIV_DER) -B64PUB_DER = b('MAwCBQDeKYlRAgMBAAE=') +B64PUB_DER = b'MAwCBQDeKYlRAgMBAAE=' PUBLIC_DER = base64.standard_b64decode(B64PUB_DER) -PRIVATE_PEM = b(''' +PRIVATE_PEM = b'''\ -----BEGIN CONFUSING STUFF----- Cruft before the key -----BEGIN RSA PRIVATE KEY----- Comment: something blah -%s +''' + B64PRIV_DER + b''' -----END RSA PRIVATE KEY----- Stuff after the key -----END CONFUSING STUFF----- -''' % B64PRIV_DER.decode("utf-8")) +''' -CLEAN_PRIVATE_PEM = b('''\ +CLEAN_PRIVATE_PEM = b'''\ -----BEGIN RSA PRIVATE KEY----- -%s +''' + B64PRIV_DER + b''' -----END RSA PRIVATE KEY----- -''' % B64PRIV_DER.decode("utf-8")) +''' -PUBLIC_PEM = b(''' +PUBLIC_PEM = b'''\ -----BEGIN CONFUSING STUFF----- Cruft before the key -----BEGIN RSA PUBLIC KEY----- Comment: something blah -%s +''' + B64PUB_DER + b''' -----END RSA PUBLIC KEY----- Stuff after the key -----END CONFUSING STUFF----- -''' % B64PUB_DER.decode("utf-8")) +''' -CLEAN_PUBLIC_PEM = b('''\ +CLEAN_PUBLIC_PEM = b'''\ -----BEGIN RSA PUBLIC KEY----- -%s +''' + B64PUB_DER + b''' -----END RSA PUBLIC KEY----- -''' % B64PUB_DER.decode("utf-8")) +''' class DerTest(unittest.TestCase): diff --git a/tests/test_pem.py b/tests/test_pem.py index 3e03ab0..5fb9600 100644 --- a/tests/test_pem.py +++ b/tests/test_pem.py @@ -17,7 +17,7 @@ import unittest -from rsa._compat import b, is_bytes +from rsa._compat import is_bytes from rsa.pem import _markers import rsa.key @@ -49,8 +49,8 @@ class TestMarkers(unittest.TestCase): def test_values(self): self.assertEqual(_markers('RSA PRIVATE KEY'), - (b('-----BEGIN RSA PRIVATE KEY-----'), - b('-----END RSA PRIVATE KEY-----'))) + (b'-----BEGIN RSA PRIVATE KEY-----', + b'-----END RSA PRIVATE KEY-----')) class TestBytesAndStrings(unittest.TestCase): diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 5702aae..bd1fd81 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -21,7 +21,7 @@ import rsa from rsa import pkcs1 -from rsa._compat import byte, b, is_bytes +from rsa._compat import byte, is_bytes class BinaryTest(unittest.TestCase): @@ -73,7 +73,7 @@ def setUp(self): def test_sign_verify(self): """Test happy flow of sign and verify""" - message = b('je moeder') + message = b'je moeder' print("\tMessage: %r" % message) signature = pkcs1.sign(message, self.priv, 'SHA-256') @@ -84,16 +84,16 @@ def test_sign_verify(self): def test_alter_message(self): """Altering the message should let the verification fail.""" - signature = pkcs1.sign(b('je moeder'), self.priv, 'SHA-256') + signature = pkcs1.sign(b'je moeder', self.priv, 'SHA-256') self.assertRaises(pkcs1.VerificationError, pkcs1.verify, - b('mijn moeder'), signature, self.pub) + b'mijn moeder', signature, self.pub) def test_sign_different_key(self): """Signing with another key should let the verification fail.""" (otherpub, _) = rsa.newkeys(512) - message = b('je moeder') + message = b'je moeder' signature = pkcs1.sign(message, self.priv, 'SHA-256') self.assertRaises(pkcs1.VerificationError, pkcs1.verify, message, signature, otherpub) diff --git a/tests/test_transform.py b/tests/test_transform.py index 7fe121b..fe0970c 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -15,37 +15,36 @@ # limitations under the License. import unittest -from rsa._compat import b from rsa.transform import int2bytes, bytes2int, _int2bytes class Test_int2bytes(unittest.TestCase): def test_accuracy(self): - self.assertEqual(int2bytes(123456789), b('\x07[\xcd\x15')) - self.assertEqual(_int2bytes(123456789), b('\x07[\xcd\x15')) + self.assertEqual(int2bytes(123456789), b'\x07[\xcd\x15') + self.assertEqual(_int2bytes(123456789), b'\x07[\xcd\x15') def test_codec_identity(self): self.assertEqual(bytes2int(int2bytes(123456789, 128)), 123456789) self.assertEqual(bytes2int(_int2bytes(123456789, 128)), 123456789) def test_chunk_size(self): - self.assertEqual(int2bytes(123456789, 6), b('\x00\x00\x07[\xcd\x15')) + self.assertEqual(int2bytes(123456789, 6), b'\x00\x00\x07[\xcd\x15') self.assertEqual(int2bytes(123456789, 7), - b('\x00\x00\x00\x07[\xcd\x15')) + b'\x00\x00\x00\x07[\xcd\x15') self.assertEqual(_int2bytes(123456789, 6), - b('\x00\x00\x07[\xcd\x15')) + b'\x00\x00\x07[\xcd\x15') self.assertEqual(_int2bytes(123456789, 7), - b('\x00\x00\x00\x07[\xcd\x15')) + b'\x00\x00\x00\x07[\xcd\x15') def test_zero(self): - self.assertEqual(int2bytes(0, 4), b('\x00') * 4) - self.assertEqual(int2bytes(0, 7), b('\x00') * 7) - self.assertEqual(int2bytes(0), b('\x00')) + self.assertEqual(int2bytes(0, 4), b'\x00' * 4) + self.assertEqual(int2bytes(0, 7), b'\x00' * 7) + self.assertEqual(int2bytes(0), b'\x00') - self.assertEqual(_int2bytes(0, 4), b('\x00') * 4) - self.assertEqual(_int2bytes(0, 7), b('\x00') * 7) - self.assertEqual(_int2bytes(0), b('\x00')) + self.assertEqual(_int2bytes(0, 4), b'\x00' * 4) + self.assertEqual(_int2bytes(0, 7), b'\x00' * 7) + self.assertEqual(_int2bytes(0), b'\x00') def test_correctness_against_base_implementation(self): # Slow test. From dc57888fe2ead24f111aee4c38427a8f411a5eb6 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Tue, 8 Nov 2016 13:23:52 +0500 Subject: [PATCH 037/142] Fixes a typo in docstrings (#83) --- rsa/key.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa/key.py b/rsa/key.py index eb30bbe..a3a3b65 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -182,7 +182,7 @@ class PublicKey(AbstractKey): This key is also known as the 'encryption key'. It contains the 'n' and 'e' values. - Supports attributes as well as dictionary-like access. Attribute accesss is + Supports attributes as well as dictionary-like access. Attribute access is faster, though. >>> PublicKey(5, 3) @@ -348,7 +348,7 @@ class PrivateKey(AbstractKey): This key is also known as the 'decryption key'. It contains the 'n', 'e', 'd', 'p', 'q' and other values. - Supports attributes as well as dictionary-like access. Attribute accesss is + Supports attributes as well as dictionary-like access. Attribute access is faster, though. >>> PrivateKey(3247, 65537, 833, 191, 17) From 0c906333048f1760d27e579ce5fd7a140ee2b62c Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Thu, 5 Jan 2017 09:51:12 -0300 Subject: [PATCH 038/142] Remove custom PrivateKey exponents/coefficient (#71) Thanks for the improvements! --- rsa/key.py | 57 ++++++++++++++++-------------------- tests/test_key.py | 7 +++++ tests/test_load_save_keys.py | 41 +++++++++++++++++++++++++- tox.ini | 1 + 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/rsa/key.py b/rsa/key.py index a3a3b65..8170916 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -34,6 +34,7 @@ """ import logging +import warnings from rsa._compat import range import rsa.prime @@ -42,6 +43,7 @@ import rsa.randnum import rsa.core + log = logging.getLogger(__name__) DEFAULT_EXPONENT = 65537 @@ -354,51 +356,30 @@ class PrivateKey(AbstractKey): >>> PrivateKey(3247, 65537, 833, 191, 17) PrivateKey(3247, 65537, 833, 191, 17) - exp1, exp2 and coef can be given, but if None or omitted they will be calculated: + exp1, exp2 and coef will be calculated: - >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287, exp2=4) + >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) >>> pk.exp1 55063 - >>> pk.exp2 # this is of course not a correct value, but it is the one we passed. - 4 - >>> pk.coef - 50797 - - If you give exp1, exp2 or coef, they will be used as-is: - - >>> pk = PrivateKey(1, 2, 3, 4, 5, 6, 7, 8) - >>> pk.exp1 - 6 >>> pk.exp2 - 7 + 10095 >>> pk.coef - 8 + 50797 """ __slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef') - def __init__(self, n, e, d, p, q, exp1=None, exp2=None, coef=None): + def __init__(self, n, e, d, p, q): AbstractKey.__init__(self, n, e) self.d = d self.p = p self.q = q - # Calculate the other values if they aren't supplied - if exp1 is None: - self.exp1 = int(d % (p - 1)) - else: - self.exp1 = exp1 - - if exp2 is None: - self.exp2 = int(d % (q - 1)) - else: - self.exp2 = exp2 - - if coef is None: - self.coef = rsa.common.inverse(q, p) - else: - self.coef = coef + # Calculate exponents and coefficient. + self.exp1 = int(d % (p - 1)) + self.exp2 = int(d % (q - 1)) + self.coef = rsa.common.inverse(q, p) def __getitem__(self, key): return getattr(self, key) @@ -510,8 +491,20 @@ def _load_pkcs1_der(cls, keyfile): if priv[0] != 0: raise ValueError('Unable to read this file, version %s != 0' % priv[0]) - as_ints = tuple(int(x) for x in priv[1:9]) - return cls(*as_ints) + as_ints = tuple(map(int, priv[1:6])) + key = cls(*as_ints) + + exp1, exp2, coef = map(int, priv[6:9]) + + if (key.exp1, key.exp2, key.coef) != (exp1, exp2, coef): + warnings.warn( + 'You have provided a malformed keyfile. Either the exponents ' + 'or the coefficient are incorrect. Using the correct values ' + 'instead.', + UserWarning, + ) + + return key def _save_pkcs1_der(self): """Saves the private key in PKCS#1 DER format. diff --git a/tests/test_key.py b/tests/test_key.py index afefa3a..9db30ce 100644 --- a/tests/test_key.py +++ b/tests/test_key.py @@ -41,6 +41,13 @@ def test_default_exponent(self): self.assertEqual(0x10001, priv.e) self.assertEqual(0x10001, pub.e) + def test_exponents_coefficient_calculation(self): + pk = rsa.key.PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) + + self.assertEqual(pk.exp1, 55063) + self.assertEqual(pk.exp2, 10095) + self.assertEqual(pk.coef, 50797) + def test_custom_getprime_func(self): # List of primes to test with, in order [p, q, p, q, ....] # By starting with two of the same primes, we test that this is diff --git a/tests/test_load_save_keys.py b/tests/test_load_save_keys.py index ef6584b..55bd5a4 100644 --- a/tests/test_load_save_keys.py +++ b/tests/test_load_save_keys.py @@ -17,10 +17,13 @@ """Unittest for saving and loading keys.""" import base64 -import unittest +import mock import os.path import pickle +import unittest +import warnings +from rsa._compat import range import rsa.key B64PRIV_DER = b'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt' @@ -80,6 +83,39 @@ def test_load_private_key(self): expected = rsa.key.PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) self.assertEqual(expected, key) + self.assertEqual(key.exp1, 55063) + self.assertEqual(key.exp2, 10095) + self.assertEqual(key.coef, 50797) + + @mock.patch('pyasn1.codec.der.decoder.decode') + def test_load_malformed_private_key(self, der_decode): + """Test loading malformed private DER keys.""" + + # Decode returns an invalid exp2 value. + der_decode.return_value = ( + [0, 3727264081, 65537, 3349121513, 65063, 57287, 55063, 0, 50797], + 0, + ) + + with warnings.catch_warnings(record=True) as w: + # Always print warnings + warnings.simplefilter('always') + + # Load 3 keys + for _ in range(3): + key = rsa.key.PrivateKey.load_pkcs1(PRIVATE_DER, 'DER') + + # Check that 3 warnings were generated. + self.assertEqual(3, len(w)) + + for warning in w: + self.assertTrue(issubclass(warning.category, UserWarning)) + self.assertIn('malformed', str(warning.message)) + + # Check that we are creating the key with correct values + self.assertEqual(key.exp1, 55063) + self.assertEqual(key.exp2, 10095) + self.assertEqual(key.coef, 50797) def test_save_private_key(self): """Test saving private DER keys.""" @@ -118,6 +154,9 @@ def test_load_private_key(self): expected = rsa.key.PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) self.assertEqual(expected, key) + self.assertEqual(key.exp1, 55063) + self.assertEqual(key.exp2, 10095) + self.assertEqual(key.coef, 50797) def test_save_private_key(self): """Test saving private PEM files.""" diff --git a/tox.ini b/tox.ini index be38566..ae49b27 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ deps=pyasn1 >=0.1.3 PyTest pytest-xdist pytest-cov + mock [testenv:py35] commands=py.test --doctest-modules rsa tests From 81f0e95dd17bbe99df2ac4958bd9511dd05c788f Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sun, 15 Jan 2017 12:30:37 -0300 Subject: [PATCH 039/142] Create PY2 constant to simplify compatibility decisions (#82) It's about time to get this merged, thanks again! --- rsa/_compat.py | 27 +++++++++++++++++---------- rsa/cli.py | 12 ++---------- tests/test_cli.py | 26 ++++++++++++-------------- tests/test_compat.py | 1 - 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index 6824bdb..1e51368 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -26,6 +26,8 @@ MAX_INT32 = (1 << 31) - 1 MAX_INT16 = (1 << 15) - 1 +PY2 = sys.version_info[0] == 2 + # Determine the word size of the processor. if MAX_INT == MAX_INT64: # 64-bit processor. @@ -37,19 +39,24 @@ # Else we just assume 64-bit processor keeping up with modern times. MACHINE_WORD_SIZE = 64 -# Range generator. -try: - # < Python3 +if PY2: + integer_types = (int, long) range = xrange -except NameError: - # Python3 +else: + integer_types = (int, ) range = range -# ``long`` is no more. Do type detection using this instead. -try: - integer_types = (int, long) -except NameError: - integer_types = (int,) + +def write_to_stdout(data): + """Writes bytes to stdout + + :type data: bytes + """ + if PY2: + sys.stdout.write(data) + else: + # On Py3 we must use the buffer interface to write bytes. + sys.stdout.buffer.write(data) def is_bytes(obj): diff --git a/rsa/cli.py b/rsa/cli.py index 7419780..6450af4 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -83,11 +83,7 @@ def keygen(): outfile.write(data) else: print('Writing private key to stdout', file=sys.stderr) - if sys.version_info[0] >= 3: - # on Py3 we must use the buffer interface to write bytes. - sys.stdout.buffer.write(data) - else: - sys.stdout.write(data) + rsa._compat.write_to_stdout(data) class CryptoOperation(object): @@ -193,11 +189,7 @@ def write_outfile(self, outdata, outname): outfile.write(outdata) else: print('Writing output to stdout', file=sys.stderr) - if sys.version_info[0] >= 3: - # on Py3 we must use the buffer interface to write bytes. - sys.stdout.buffer.write(outdata) - else: - sys.stdout.write(outdata) + rsa._compat.write_to_stdout(outdata) class EncryptOperation(CryptoOperation): diff --git a/tests/test_cli.py b/tests/test_cli.py index 4ae8da3..7ce57eb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -15,25 +15,23 @@ import rsa import rsa.cli import rsa.util +from rsa._compat import PY2 -if sys.version_info[0] < 3: - def make_buffer(): + +def make_buffer(): + if PY2: return BytesIO() + buf = StringIO() + buf.buffer = BytesIO() + return buf - def get_bytes_out(out): - # Python 2.x writes 'str' to stdout: +def get_bytes_out(out): + if PY2: + # Python 2.x writes 'str' to stdout return out.getvalue() -else: - def make_buffer(): - buf = StringIO() - buf.buffer = BytesIO() - return buf - - - def get_bytes_out(out): - # Python 3.x writes 'bytes' to stdout.buffer: - return out.buffer.getvalue() + # Python 3.x writes 'bytes' to stdout.buffer + return out.buffer.getvalue() @contextmanager diff --git a/tests/test_compat.py b/tests/test_compat.py index 0013155..a47f890 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -16,7 +16,6 @@ import unittest import struct -import sys from rsa._compat import byte, is_bytes, range From d3727172cedb409613739be6af197c3b03cc163d Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 16 Jan 2017 08:38:08 -0300 Subject: [PATCH 040/142] Implementation of bitwise XOR function for bytes object (#72) --- rsa/_compat.py | 21 ++++++++++++++++++++ tests/test_compat.py | 47 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index 1e51368..38bab08 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -99,6 +99,27 @@ def byte(num): return pack("B", num) +def xor_bytes(b1, b2): + """ + Returns the bitwise XOR result between two bytes objects, b1 ^ b2. + + Bitwise XOR operation is commutative, so order of parameters doesn't + generate different results. If parameters have different length, extra + length of the largest one is ignored. + + :param b1: + First bytes object. + :param b2: + Second bytes object. + :returns: + Bytes object, result of XOR operation. + """ + if PY2: + return ''.join(byte(ord(x) ^ ord(y)) for x, y in zip(b1, b2)) + + return bytes(x ^ y for x, y in zip(b1, b2)) + + def get_word_alignment(num, force_arch=64, _machine_word_size=MACHINE_WORD_SIZE): """ diff --git a/tests/test_compat.py b/tests/test_compat.py index a47f890..62e933f 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -17,10 +17,12 @@ import unittest import struct -from rsa._compat import byte, is_bytes, range +from rsa._compat import byte, is_bytes, range, xor_bytes class TestByte(unittest.TestCase): + """Tests for single bytes.""" + def test_byte(self): for i in range(256): byt = byte(i) @@ -33,3 +35,46 @@ def test_raises_StructError_on_overflow(self): def test_byte_literal(self): self.assertIsInstance(b'abc', bytes) + + +class TestBytes(unittest.TestCase): + """Tests for bytes objects.""" + + def setUp(self): + self.b1 = b'\xff\xff\xff\xff' + self.b2 = b'\x00\x00\x00\x00' + self.b3 = b'\xf0\xf0\xf0\xf0' + self.b4 = b'\x4d\x23\xca\xe2' + self.b5 = b'\x9b\x61\x3b\xdc' + self.b6 = b'\xff\xff' + + self.byte_strings = (self.b1, self.b2, self.b3, self.b4, self.b5, self.b6) + + def test_xor_bytes(self): + self.assertEqual(xor_bytes(self.b1, self.b2), b'\xff\xff\xff\xff') + self.assertEqual(xor_bytes(self.b1, self.b3), b'\x0f\x0f\x0f\x0f') + self.assertEqual(xor_bytes(self.b1, self.b4), b'\xb2\xdc\x35\x1d') + self.assertEqual(xor_bytes(self.b1, self.b5), b'\x64\x9e\xc4\x23') + self.assertEqual(xor_bytes(self.b2, self.b3), b'\xf0\xf0\xf0\xf0') + self.assertEqual(xor_bytes(self.b2, self.b4), b'\x4d\x23\xca\xe2') + self.assertEqual(xor_bytes(self.b2, self.b5), b'\x9b\x61\x3b\xdc') + self.assertEqual(xor_bytes(self.b3, self.b4), b'\xbd\xd3\x3a\x12') + self.assertEqual(xor_bytes(self.b3, self.b5), b'\x6b\x91\xcb\x2c') + self.assertEqual(xor_bytes(self.b4, self.b5), b'\xd6\x42\xf1\x3e') + + def test_xor_bytes_length(self): + self.assertEqual(xor_bytes(self.b1, self.b6), b'\x00\x00') + self.assertEqual(xor_bytes(self.b2, self.b6), b'\xff\xff') + self.assertEqual(xor_bytes(self.b3, self.b6), b'\x0f\x0f') + self.assertEqual(xor_bytes(self.b4, self.b6), b'\xb2\xdc') + self.assertEqual(xor_bytes(self.b5, self.b6), b'\x64\x9e') + self.assertEqual(xor_bytes(self.b6, b''), b'') + + def test_xor_bytes_commutative(self): + for first in self.byte_strings: + for second in self.byte_strings: + min_length = min(len(first), len(second)) + result = xor_bytes(first, second) + + self.assertEqual(result, xor_bytes(second, first)) + self.assertEqual(len(result), min_length) From 2d81e0fe4b89398876da8a9027d1465a5bb4434b Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Tue, 17 Jan 2017 13:33:08 -0300 Subject: [PATCH 041/142] Use iterative zip in Python 2 (#85) Good catch, thanks! --- rsa/_compat.py | 3 +++ rsa/common.py | 2 ++ rsa/key.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index 38bab08..71197a5 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -18,6 +18,7 @@ from __future__ import absolute_import +import itertools import sys from struct import pack @@ -42,9 +43,11 @@ if PY2: integer_types = (int, long) range = xrange + zip = itertools.izip else: integer_types = (int, ) range = range + zip = zip def write_to_stdout(data): diff --git a/rsa/common.py b/rsa/common.py index 4f8c0d9..7c8a082 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from rsa._compat import zip + """Common functionality shared by several modules.""" diff --git a/rsa/key.py b/rsa/key.py index 8170916..196f78d 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -491,7 +491,7 @@ def _load_pkcs1_der(cls, keyfile): if priv[0] != 0: raise ValueError('Unable to read this file, version %s != 0' % priv[0]) - as_ints = tuple(map(int, priv[1:6])) + as_ints = map(int, priv[1:6]) key = cls(*as_ints) exp1, exp2, coef = map(int, priv[6:9]) From d17c769929552994dd12c5730e4fbeda80f42f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 10:54:28 +0200 Subject: [PATCH 042/142] Added mock library as requirement, as it's used in unit tests. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f1c6af1..5d5cc64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ sphinx pyasn1>=0.1.3 tox wheel +mock>=2.0.0 From 88927a9fcf3f93475e640ce9b5a04cd7c2dfc405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 10:54:38 +0200 Subject: [PATCH 043/142] Moved sphinx down to other development requirements. --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5d5cc64..954a6f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -sphinx pyasn1>=0.1.3 + +sphinx tox wheel mock>=2.0.0 From 1d14c4e6c6c78acb2af553cdbc4fe23fc72024b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 11:31:09 +0200 Subject: [PATCH 044/142] Updated documentation for removal of bigfile/VARBLOCK support. --- doc/cli.rst | 55 ++++++++++++++++++++-------------------- doc/compatibility.rst | 4 +-- doc/reference.rst | 22 +--------------- doc/upgrading.rst | 20 ++++++++++++--- doc/usage.rst | 59 ++++--------------------------------------- 5 files changed, 52 insertions(+), 108 deletions(-) diff --git a/doc/cli.rst b/doc/cli.rst index af2b5f1..30864d7 100644 --- a/doc/cli.rst +++ b/doc/cli.rst @@ -13,31 +13,30 @@ on how to use them. Here is a short overview: .. index:: pyrsa-verify, pyrsa-priv2pub, pyrsa-encrypt-bigfile .. index:: pyrsa-decrypt-bigfile, pyrsa-decrypt-bigfile -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| Command | Usage | Core function | -+=======================+==================================================+=========================================+ -| pyrsa-keygen | Generates a new RSA keypair in PEM or DER format | :py:func:`rsa.newkeys` | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-encrypt | Encrypts a file. The file must be shorter than | :py:func:`rsa.encrypt` | -| | the key length in order to be encrypted. | | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-decrypt | Decrypts a file. | :py:func:`rsa.decrypt` | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-sign | Signs a file, outputs the signature. | :py:func:`rsa.sign` | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-verify | Verifies a signature. The result is written to | :py:func:`rsa.verify` | -| | the console as well as returned in the exit | | -| | status code. | | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-priv2pub | Reads a private key and outputs the | \- | -| | corresponding public key. | | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-encrypt-bigfile | Encrypts a file to an encrypted VARBLOCK file. | :py:func:`rsa.bigfile.encrypt_bigfile` | -| | The file can be larger than the key length, but | | -| | the output file is only compatible with | | -| | Python-RSA. | | -+-----------------------+--------------------------------------------------+-----------------------------------------+ -| pyrsa-decrypt-bigfile | Decrypts an encrypted VARBLOCK file. | :py:func:`rsa.bigfile.encrypt_bigfile` | -+-----------------------+--------------------------------------------------+-----------------------------------------+ - - ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| Command | Usage | Core function | ++=========================+==================================================+=========================================+ +| pyrsa-keygen | Generates a new RSA keypair in PEM or DER format | :py:func:`rsa.newkeys` | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| pyrsa-encrypt | Encrypts a file. The file must be shorter than | :py:func:`rsa.encrypt` | +| | the key length in order to be encrypted. | | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| pyrsa-decrypt | Decrypts a file. | :py:func:`rsa.decrypt` | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| pyrsa-sign | Signs a file, outputs the signature. | :py:func:`rsa.sign` | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| pyrsa-verify | Verifies a signature. The result is written to | :py:func:`rsa.verify` | +| | the console as well as returned in the exit | | +| | status code. | | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| pyrsa-priv2pub | Reads a private key and outputs the | \- | +| | corresponding public key. | | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| *pyrsa-encrypt-bigfile* | *Encrypts a file to an encrypted VARBLOCK file. | *Deprecated in Python-RSA 3.4 and | +| | The file can be larger than the key length, but | removed from version 4.0.* | +| | the output file is only compatible with | | +| | Python-RSA.* | | ++-------------------------+--------------------------------------------------+-----------------------------------------+ +| *pyrsa-decrypt-bigfile* | *Decrypts an encrypted VARBLOCK file.* | *Deprecated in Python-RSA 3.4 and | +| | | removed from version 4.0.* | ++-------------------------+--------------------------------------------------+-----------------------------------------+ diff --git a/doc/compatibility.rst b/doc/compatibility.rst index aedfcb6..d6e1dc4 100644 --- a/doc/compatibility.rst +++ b/doc/compatibility.rst @@ -25,7 +25,8 @@ Public keys: PKCS#1 v1.5 in PEM and DER format, ASN.1 type RSAPublicKey :ref:`VARBLOCK ` encryption: - Python-RSA only, not compatible with any other known application. + Deprecated in Python-RSA 3.4 and removed from Python-RSA 4.0. + Was Python-RSA only, not compatible with any other known application. .. _openssl: @@ -59,4 +60,3 @@ PKCS#8 format you need an external tool such as OpenSSL:: openssl rsa -in privatekey-pkcs8.pem -out privatekey.pem You can then extract the corresponding public key as described above. - diff --git a/doc/reference.rst b/doc/reference.rst index d1b0b6d..d81f742 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -49,32 +49,13 @@ Exceptions .. index:: VARBLOCK (file format) -Module: rsa.bigfile -------------------- - -.. warning:: - - The :py:mod:`rsa.bigfile` module is NOT recommended for general use, has been - deprecated since Python-RSA 3.4, and will be removed in a future release. It's - vulnerable to a number of attacks. See :ref:`bigfiles` for more information. - -The :py:mod:`rsa.bigfile` module contains functions for encrypting and -decrypting files that are larger than the RSA key. See -:ref:`bigfiles` for more information. - -.. autofunction:: rsa.bigfile.encrypt_bigfile - -.. autofunction:: rsa.bigfile.decrypt_bigfile - -.. _VARBLOCK: - The VARBLOCK file format ++++++++++++++++++++++++ .. warning:: The VARBLOCK format is NOT recommended for general use, has been deprecated since - Python-RSA 3.4, and will be removed in a future release. It's vulnerable to a + Python-RSA 3.4, and was removed in version 4.0. It's vulnerable to a number of attacks. See :ref:`bigfiles` for more information. The VARBLOCK file format allows us to encrypt files that are larger @@ -109,4 +90,3 @@ the core of the entire library. .. autofunction:: rsa.core.encrypt_int .. autofunction:: rsa.core.decrypt_int - diff --git a/doc/upgrading.rst b/doc/upgrading.rst index 0ec18eb..3381baa 100644 --- a/doc/upgrading.rst +++ b/doc/upgrading.rst @@ -1,6 +1,22 @@ Upgrading from older versions ============================= +From versions older than Python-RSA 4.0 +--------------------------------------- + +Support for the VARBLOCK/bigfile format has been dropped in version 4.0, after +being deprecated for a year. There is no alternative implementation in +Python-RSA 4.0. If you need this, or have ideas on how to do handle encryption +of large files securely and in a compatible way with existing standards, +`open a ticket to discuss this`_. + +.. _open a ticket to discuss this: + https://github.com/sybrenstuvel/python-rsa/issues/new + + +From versions older than Python-RSA 3.4 +--------------------------------------- + Previous versions of Python-RSA were less secure than the current version. In order to be able to gradually upgrade your software, those old versions will be available until Python-RSA 4.0. @@ -34,8 +50,7 @@ less secure code into your project. The random padding introduced in version 3.0 made things much more secure, but also requires a larger key to encrypt the same message. -You can either generate a new key with :py:func:`rsa.newkeys`, or use -:py:func:`rsa.bigfile.encrypt_bigfile` to encrypt your files. + Converting keys --------------- @@ -70,4 +85,3 @@ older version of Python-RSA, use the following:: old_priv_key.update(old_pub_key) priv_key = rsa.PrivateKey(**old_priv_key) - diff --git a/doc/usage.rst b/doc/usage.rst index a3d128d..b4f8426 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -198,7 +198,7 @@ You can create a detached signature for a message using the >>> (pubkey, privkey) = rsa.newkeys(512) >>> message = 'Go left at the blue tree' >>> signature = rsa.sign(message, privkey, 'SHA-1') - + This hashes the message using SHA-1. Other hash methods are also possible, check the :py:func:`rsa.sign` function documentation for details. The hash is then signed with the private key. @@ -285,7 +285,7 @@ Only using Python-RSA: the VARBLOCK format .. warning:: The VARBLOCK format is NOT recommended for general use, has been deprecated since - Python-RSA 3.4, and will be removed in a future release. It's vulnerable to a + Python-RSA 3.4, and has been removed in version 4.0. It's vulnerable to a number of attacks: 1. decrypt/encrypt_bigfile() does not implement `Authenticated encryption`_ nor @@ -299,55 +299,6 @@ Only using Python-RSA: the VARBLOCK format .. _Authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption .. _issue #19 on Github: https://github.com/sybrenstuvel/python-rsa/issues/13 - -As far as we know, there is no pure-Python AES encryption. Previous -versions of Python-RSA included functionality to encrypt large files -with just RSA, and so does this version. The format has been improved, -though. - -Encrypting works as follows: the input file is split into blocks that -are just large enough to encrypt with your RSA key. Every block is -then encrypted using RSA, and the encrypted blocks are assembled into -the output file. This file format is called the :ref:`VARBLOCK -` format. - -Decrypting works in reverse. The encrypted file is separated into -encrypted blocks. Those are decrypted, and assembled into the original -file. - -.. note:: - - The file will get larger after encryption, as each encrypted block - has 8 bytes of random padding and 3 more bytes of overhead. - -Since these encryption/decryption functions are potentially called on -very large files, they use another approach. Where the regular -functions store the message in memory in its entirety, these functions -work on one block at the time. As a result, you should call them with -:py:class:`file`-like objects as the parameters. - -Before using we of course need a keypair: - ->>> import rsa ->>> (pub_key, priv_key) = rsa.newkeys(512) - -Encryption works on file handles using the -:py:func:`rsa.bigfile.encrypt_bigfile` function: - ->>> from rsa.bigfile import * ->>> with open('inputfile', 'rb') as infile, open('outputfile', 'wb') as outfile: -... encrypt_bigfile(infile, outfile, pub_key) - -As does decryption using the :py:func:`rsa.bigfile.decrypt_bigfile` -function: - ->>> from rsa.bigfile import * ->>> with open('inputfile', 'rb') as infile, open('outputfile', 'wb') as outfile: -... decrypt_bigfile(infile, outfile, priv_key) - -.. note:: - - :py:func:`rsa.sign` and :py:func:`rsa.verify` work on arbitrarily - long files, so they do not have a "bigfile" equivalent. - - +As of Python-RSA version 4.0, the VARBLOCK format has been removed from the +library. For now, this section is kept here to document the issues with that +format, and ensure we don't do something like that again. From 9fe669331d5014ab8b43247eb529f34ba7264796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 11:31:33 +0200 Subject: [PATCH 045/142] Removed unused sphinx extension. --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 95317b2..154accb 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -28,7 +28,7 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.pngmath'] + 'sphinx.ext.coverage'] # I would like to add 'sphinx.ext.viewcode', but it causes a UnicodeDecodeError From b4dba2af917f807f90cd07296c8045c5990eea29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 11:31:46 +0200 Subject: [PATCH 046/142] Updated year in copyright statement --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 154accb..f209a4d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,7 +46,7 @@ # General information about the project. project = u'Python-RSA' -copyright = u'2011-2016, Sybren A. Stüvel' +copyright = u'2011-2017, Sybren A. Stüvel' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 736444cf20bb14f0ec6e2906ae63c8382f8b70cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 11:32:10 +0200 Subject: [PATCH 047/142] Removed easy_install from installation documentation Also worded the use of 'sudo' a bit more carefully. --- doc/installation.rst | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index 578dc86..fa0b16b 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -5,20 +5,15 @@ Installation can be done in various ways. The simplest form uses pip or easy_install. Either one will work:: pip install rsa - easy_install rsa -Depending on your system you may need to use ``sudo pip`` or ``sudo -easy_install``. +Depending on your system you may need to use ``sudo pip`` if you want to install +the library system-wide. Installation from source is also quite easy. Download the source and then type:: python setup.py install -or if that doesn't work:: - - sudo python setup.py install - The sources are tracked in our `Git repository`_ at Github. It also hosts the `issue tracker`_. @@ -49,7 +44,7 @@ pip to install the development requirements in a virtual environment:: Once these are installed, use Git_ to get a copy of the source:: - hg clone https://github.com/sybrenstuvel/python-rsa.git + git clone https://github.com/sybrenstuvel/python-rsa.git python setup.py develop .. _Git: https://git-scm.com/ From a478d4c23a75c76f1e5587b3c08b1f3a1f2cbf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 11:56:55 +0200 Subject: [PATCH 048/142] Don't print in unit tests --- tests/test_pkcs1.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index bd1fd81..2116284 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -74,10 +74,7 @@ def test_sign_verify(self): """Test happy flow of sign and verify""" message = b'je moeder' - print("\tMessage: %r" % message) - signature = pkcs1.sign(message, self.priv, 'SHA-256') - print("\tSignature: %r" % signature) self.assertTrue(pkcs1.verify(message, signature, self.pub)) From 395b8d661566f15632b0fb0e36fb8ea453ad9bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 12:12:29 +0200 Subject: [PATCH 049/142] Feature request #78: Expose function to find the hash method of a signature I've not used the name "find_method_hash" suggested in #78, as it's a bit vague. It's ok-ish for a private function `_find_method_hash`, but I thought `find_signature_hash` would be more descriptive. --- doc/reference.rst | 2 ++ rsa/__init__.py | 2 +- rsa/pkcs1.py | 22 +++++++++++++++++++++- tests/test_pkcs1.py | 10 +++++++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/doc/reference.rst b/doc/reference.rst index d81f742..9da7c6b 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -15,6 +15,8 @@ Functions .. autofunction:: rsa.verify +.. autofunction:: rsa.find_signature_hash + .. autofunction:: rsa.newkeys(keysize) diff --git a/rsa/__init__.py b/rsa/__init__.py index e69bbf3..95cd3fd 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -25,7 +25,7 @@ from rsa.key import newkeys, PrivateKey, PublicKey from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \ - VerificationError + VerificationError, find_signature_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = "2016-03-29" diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index bb08f4d..41f543c 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -294,6 +294,7 @@ def verify(message, signature, pub_key): :param signature: the signature block, as created with :py:func:`rsa.sign`. :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message. :raise VerificationError: when the signature doesn't match the message. + :returns: the name of the used hash. """ @@ -314,7 +315,26 @@ def verify(message, signature, pub_key): if expected != clearsig: raise VerificationError('Verification failed') - return True + return method_name + + +def find_signature_hash(signature, pub_key): + """Returns the hash name detected from the signature. + + If you also want to verify the message, use :py:func:`rsa.verify()` instead. + It also returns the name of the used hash. + + :param signature: the signature block, as created with :py:func:`rsa.sign`. + :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message. + :returns: the name of the used hash. + """ + + keylength = common.byte_size(pub_key.n) + encrypted = transform.bytes2int(signature) + decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) + clearsig = transform.int2bytes(decrypted, keylength) + + return _find_method_hash(clearsig) def yield_fixedblocks(infile, blocksize): diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 2116284..a8afea7 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -76,7 +76,15 @@ def test_sign_verify(self): message = b'je moeder' signature = pkcs1.sign(message, self.priv, 'SHA-256') - self.assertTrue(pkcs1.verify(message, signature, self.pub)) + self.assertEqual('SHA-256', pkcs1.verify(message, signature, self.pub)) + + def test_find_signature_hash(self): + """Test happy flow of sign and find_signature_hash""" + + message = b'je moeder' + signature = pkcs1.sign(message, self.priv, 'SHA-256') + + self.assertEqual('SHA-256', pkcs1.find_signature_hash(signature, self.pub)) def test_alter_message(self): """Altering the message should let the verification fail.""" From 7ebae9f96ec6aba420bfdf7f6467be0e6b30275c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 10 Apr 2017 12:17:33 +0200 Subject: [PATCH 050/142] Updated changelog --- CHANGELOG.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9c165fa..e715d22 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -14,6 +14,12 @@ Version 4.0 - in development - Dropped support for Python 2.6 - Dropped support for Psyco - Miller-Rabin iterations determined by bitsize of key. + [#58](https://github.com/sybrenstuvel/python-rsa/pull/58) +- Added function `rsa.find_signature_hash()` to return the name of the hashing + algorithm used to sign a message. `rsa.verify()` now also returns that name, + instead of always returning `True`. + [#78](https://github.com/sybrenstuvel/python-rsa/issues/13) + Version 3.4.2 - released 2016-03-29 From 000e84a97a8ab25a5d9b4185a2e02efc033f7e8a Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Tue, 18 Apr 2017 03:59:58 -0300 Subject: [PATCH 051/142] Ceiling division implementation (#88) Created as a new function as it will be needed by the new PKCS#1 2.0 implementation. Specifically, for the MGF1 function used in the OAEP encoding/decoding. This allows us not to have `math` dependencies --- rsa/common.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/rsa/common.py b/rsa/common.py index 7c8a082..f7aa2d1 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -76,11 +76,33 @@ def byte_size(number): :returns: The number of bytes required to hold a specific long number. """ - quanta, mod = divmod(bit_size(number), 8) - if mod or number == 0: + if number == 0: + return 1 + return ceil_div(bit_size(number), 8) + + +def ceil_div(num, div): + """ + Returns the ceiling function of a division between `num` and `div`. + + Usage:: + + >>> ceil_div(100, 7) + 15 + >>> ceil_div(100, 10) + 10 + >>> ceil_div(1, 4) + 1 + + :param num: Division's numerator, a number + :param div: Division's divisor, a number + + :return: Rounded up result of the division between the parameters. + """ + quanta, mod = divmod(num, div) + if mod: quanta += 1 return quanta - # return int(math.ceil(bit_size(number) / 8.0)) def extended_gcd(a, b): From 425eb24854f1c3397aaaba61fa1cf71c76b27c4b Mon Sep 17 00:00:00 2001 From: Justin Simon Date: Sun, 7 May 2017 04:39:47 -0500 Subject: [PATCH 052/142] Support signing a pre-calculated hash (#87) * Split the hashing out of the sign method This code change adds support to split the hashing of a message and the actual signing of the message. * Updating unit test and documentation This commit updates the unit test and usage docs. In addition, This change removes a redundant error check inside rsa.sign(). * Refactore unit tests and code comments Removed the print statements from the unit test and refactored a few code comments to improve readability. * Rename hash function The new hash function had the same name as a function in the standard library. This commit changes the name to avoid conflicts. * Rename hash function to compute_hash() This commit renames the hash function to compute_hash(). --- doc/usage.rst | 9 ++++++++ rsa/__init__.py | 5 +++-- rsa/pkcs1.py | 50 ++++++++++++++++++++++++++++++--------------- tests/test_pkcs1.py | 21 +++++++++++++++++++ 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/doc/usage.rst b/doc/usage.rst index b4f8426..6ac9e82 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -203,6 +203,15 @@ This hashes the message using SHA-1. Other hash methods are also possible, check the :py:func:`rsa.sign` function documentation for details. The hash is then signed with the private key. +It is possible to calculate the hash and signature in separate operations +(i.e for generating the hash on a client machine and then sign with a +private key on remote server). To hash a message use the :py:func:`rsa.compute_hash` +function and then use the :py:func:`rsa.sign_hash` function to sign the hash: + + >>> message = 'Go left at the blue tree' + >>> hash = rsa.compute_hash(message, 'SHA-1') + >>> signature = rsa.sign_hash(hash, privkey, 'SHA-1') + In order to verify the signature, use the :py:func:`rsa.verify` function. This function returns True if the verification is successful: diff --git a/rsa/__init__.py b/rsa/__init__.py index 95cd3fd..ce3a341 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -25,7 +25,7 @@ from rsa.key import newkeys, PrivateKey, PublicKey from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \ - VerificationError, find_signature_hash + VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = "2016-03-29" @@ -38,4 +38,5 @@ doctest.testmod() __all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey', - 'PrivateKey', 'DecryptionError', 'VerificationError'] + 'PrivateKey', 'DecryptionError', 'VerificationError', + 'compute_hash', 'sign_hash'] diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 41f543c..323bf48 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -245,17 +245,16 @@ def decrypt(crypto, priv_key): return cleartext[sep_idx + 1:] -def sign(message, priv_key, hash): - """Signs the message with the private key. +def sign_hash(hash_value, priv_key, hash_method): + """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 message: the message to sign. Can be an 8-bit string or a file-like - object. If ``message`` has a ``read()`` method, it is assumed to be a - file-like object. + + :param hash_value: A precomputed hash to sign (ignores message). Should be set to + None if needing to hash and sign message. :param priv_key: the :py:class:`rsa.PrivateKey` to sign with - :param hash: the hash method used on the message. Use 'MD5', 'SHA-1', + :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1', 'SHA-256', 'SHA-384' or 'SHA-512'. :return: a message signature block. :raise OverflowError: if the private key is too small to contain the @@ -264,15 +263,12 @@ def sign(message, priv_key, hash): """ # Get the ASN1 code for this hash method - if hash not in HASH_ASN1: - raise ValueError('Invalid hash method: %s' % hash) - asn1code = HASH_ASN1[hash] - - # Calculate the hash - hash = _hash(message, hash) + if hash_method not in HASH_ASN1: + raise ValueError('Invalid hash method: %s' % hash_method) + asn1code = HASH_ASN1[hash_method] # Encrypt the hash with the private key - cleartext = asn1code + hash + cleartext = asn1code + hash_value keylength = common.byte_size(priv_key.n) padded = _pad_for_signing(cleartext, keylength) @@ -283,6 +279,28 @@ def sign(message, priv_key, hash): return block +def sign(message, priv_key, hash_method): + """Signs the message 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 message: the message to sign. Can be an 8-bit string or a file-like + object. If ``message`` has a ``read()`` method, it is assumed to be a + file-like object. + :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-256', 'SHA-384' or 'SHA-512'. + :return: a message signature block. + :raise OverflowError: if the private key is too small to contain the + requested hash. + + """ + + msg_hash = compute_hash(message, hash_method) + return sign_hash(msg_hash, priv_key, hash_method) + + def verify(message, signature, pub_key): """Verifies that the signature matches the message. @@ -305,7 +323,7 @@ def verify(message, signature, pub_key): # Get the hash method method_name = _find_method_hash(clearsig) - message_hash = _hash(message, method_name) + message_hash = compute_hash(message, method_name) # Reconstruct the expected padded hash cleartext = HASH_ASN1[method_name] + message_hash @@ -358,7 +376,7 @@ def yield_fixedblocks(infile, blocksize): break -def _hash(message, method_name): +def compute_hash(message, method_name): """Returns the message digest. :param message: the signed message. Can be an 8-bit string or a file-like diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index a8afea7..a96c8be 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -111,3 +111,24 @@ def test_multiple_signings(self): signature2 = pkcs1.sign(message, self.priv, 'SHA-1') self.assertEqual(signature1, signature2) + + def test_split_hash_sign(self): + """Hashing and then signing should match with directly signing the message. """ + + message = b'je moeder' + msg_hash = pkcs1.compute_hash(message, 'SHA-256') + signature1 = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-256') + + # Calculate the signature using the unified method + signature2 = pkcs1.sign(message, self.priv, 'SHA-256') + + self.assertEqual(signature1, signature2) + + def test_hash_sign_verify(self): + """Test happy flow of hash, sign, and verify""" + + message = b'je moeder' + msg_hash = pkcs1.compute_hash(message, 'SHA-256') + signature = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-256') + + self.assertTrue(pkcs1.verify(message, signature, self.pub)) From d00852509aa3702827941882941dc1c76368cf8c Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 10 Jun 2017 06:29:32 -0300 Subject: [PATCH 053/142] PKCS#1 2.0: Implementation of MGF1 (#89) Implementation of the Mask Generation Function `MGF1` used in the OAEP encoding step. For more information, the MGF1 specification is at https://tools.ietf.org/html/rfc2437#section-10.2.1 --- rsa/pkcs1.py | 2 +- rsa/pkcs1_v2.py | 111 +++++++++++++++++++++++++++++++++++++++++ tests/test_pkcs1_v2.py | 83 ++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 rsa/pkcs1_v2.py create mode 100644 tests/test_pkcs1_v2.py diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 323bf48..59190ed 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -250,7 +250,7 @@ def sign_hash(hash_value, priv_key, hash_method): 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 priv_key: the :py:class:`rsa.PrivateKey` to sign with diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py new file mode 100644 index 0000000..4ae69b3 --- /dev/null +++ b/rsa/pkcs1_v2.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2011 Sybren A. Stüvel +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Functions for PKCS#1 version 2 encryption and signing + +This module implements certain functionality from PKCS#1 version 2. Main +documentation is RFC 2437: https://tools.ietf.org/html/rfc2437 +""" + +from rsa._compat import range +from rsa import ( + common, + pkcs1, + transform, +) + +HASH_METHOD_TO_BYTE_LENGTH = { + 'MD5': 16, + 'SHA-1': 20, + 'SHA-256': 28, + 'SHA-384': 48, + 'SHA-512': 64, +} + + +def mgf1(seed, length, hasher='SHA-1'): + """ + MGF1 is a Mask Generation Function based on a hash function. + + A mask generation function takes an octet string of variable length and a + desired output length as input, and outputs an octet string of the desired + length. The plaintext-awareness of RSAES-OAEP relies on the random nature of + the output of the mask generation function, which in turn relies on the + random nature of the underlying hash. + + :param bytes seed: seed from which mask is generated, an octet string + :param int length: intended length in octets of the mask, at most 2^32(hLen) + :param str hasher: hash function (hLen denotes the length in octets of the hash + function output) + + :return: mask, an octet string of length `length` + :rtype: bytes + + :raise OverflowError: when `length` is too large for the specified `hasher` + :raise ValueError: when specified `hasher` is invalid + """ + + try: + hash_length = HASH_METHOD_TO_BYTE_LENGTH[hasher] + except KeyError: + raise ValueError( + 'Invalid `hasher` specified. Please select one of: {hash_list}'.format( + hash_list=', '.join(sorted(HASH_METHOD_TO_BYTE_LENGTH.keys())) + ) + ) + + # If l > 2^32(hLen), output "mask too long" and stop. + if length > (2**32 * hash_length): + raise OverflowError( + "Desired length should be at most 2**32 times the hasher's output " + "length ({hash_length} for {hasher} function)".format( + hash_length=hash_length, + hasher=hasher, + ) + ) + + # Looping `counter` from 0 to ceil(l / hLen)-1, build `output` based on the + # hashes formed by (`seed` + C), being `C` an octet string of length 4 + # generated by converting `counter` with the primitive I2OSP + output = b''.join( + pkcs1.compute_hash( + seed + transform.int2bytes(counter, fill_size=4), + method_name=hasher, + ) + for counter in range(common.ceil_div(length, hash_length) + 1) + ) + + # Output the leading `length` octets of `output` as the octet string mask. + return output[:length] + + +__all__ = [ + 'mgf1', +] + +if __name__ == '__main__': + print('Running doctests 1000x or until failure') + import doctest + + for count in range(1000): + (failures, tests) = doctest.testmod() + if failures: + break + + if count and count % 100 == 0: + print('%i times' % count) + + print('Doctests done') diff --git a/tests/test_pkcs1_v2.py b/tests/test_pkcs1_v2.py new file mode 100644 index 0000000..1d8f001 --- /dev/null +++ b/tests/test_pkcs1_v2.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2011 Sybren A. Stüvel +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests PKCS #1 version 2 functionality. + +Most of the mocked values come from the test vectors found at: +http://www.itomorrowmag.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm +""" + +import unittest + +from rsa import pkcs1_v2 + + +class MGFTest(unittest.TestCase): + def test_oaep_int_db_mask(self): + seed = ( + b'\xaa\xfd\x12\xf6\x59\xca\xe6\x34\x89\xb4\x79\xe5\x07\x6d\xde\xc2' + b'\xf0\x6c\xb5\x8f' + ) + db = ( + b'\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18\x90' + b'\xaf\xd8\x07\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xd4\x36\xe9\x95\x69' + b'\xfd\x32\xa7\xc8\xa0\x5b\xbc\x90\xd3\x2c\x49' + ) + masked_db = ( + b'\xdc\xd8\x7d\x5c\x68\xf1\xee\xa8\xf5\x52\x67\xc3\x1b\x2e\x8b\xb4' + b'\x25\x1f\x84\xd7\xe0\xb2\xc0\x46\x26\xf5\xaf\xf9\x3e\xdc\xfb\x25' + b'\xc9\xc2\xb3\xff\x8a\xe1\x0e\x83\x9a\x2d\xdb\x4c\xdc\xfe\x4f\xf4' + b'\x77\x28\xb4\xa1\xb7\xc1\x36\x2b\xaa\xd2\x9a\xb4\x8d\x28\x69\xd5' + b'\x02\x41\x21\x43\x58\x11\x59\x1b\xe3\x92\xf9\x82\xfb\x3e\x87\xd0' + b'\x95\xae\xb4\x04\x48\xdb\x97\x2f\x3a\xc1\x4f\x7b\xc2\x75\x19\x52' + b'\x81\xce\x32\xd2\xf1\xb7\x6d\x4d\x35\x3e\x2d' + ) + + # dbMask = MGF(seed, length(DB)) + db_mask = pkcs1_v2.mgf1(seed, length=len(db)) + expected_db_mask = ( + b'\x06\xe1\xde\xb2\x36\x9a\xa5\xa5\xc7\x07\xd8\x2c\x8e\x4e\x93\x24' + b'\x8a\xc7\x83\xde\xe0\xb2\xc0\x46\x26\xf5\xaf\xf9\x3e\xdc\xfb\x25' + b'\xc9\xc2\xb3\xff\x8a\xe1\x0e\x83\x9a\x2d\xdb\x4c\xdc\xfe\x4f\xf4' + b'\x77\x28\xb4\xa1\xb7\xc1\x36\x2b\xaa\xd2\x9a\xb4\x8d\x28\x69\xd5' + b'\x02\x41\x21\x43\x58\x11\x59\x1b\xe3\x92\xf9\x82\xfb\x3e\x87\xd0' + b'\x95\xae\xb4\x04\x48\xdb\x97\x2f\x3a\xc1\x4e\xaf\xf4\x9c\x8c\x3b' + b'\x7c\xfc\x95\x1a\x51\xec\xd1\xdd\xe6\x12\x64' + ) + + self.assertEqual(db_mask, expected_db_mask) + + # seedMask = MGF(maskedDB, length(seed)) + seed_mask = pkcs1_v2.mgf1(masked_db, length=len(seed)) + expected_seed_mask = ( + b'\x41\x87\x0b\x5a\xb0\x29\xe6\x57\xd9\x57\x50\xb5\x4c\x28\x3c\x08' + b'\x72\x5d\xbe\xa9' + ) + + self.assertEqual(seed_mask, expected_seed_mask) + + def test_invalid_hasher(self): + """Tests an invalid hasher generates an exception""" + with self.assertRaises(ValueError): + pkcs1_v2.mgf1(b'\x06\xe1\xde\xb2', length=8, hasher='SHA2') + + def test_invalid_length(self): + with self.assertRaises(OverflowError): + pkcs1_v2.mgf1(b'\x06\xe1\xde\xb2', length=2**50) From c0b7bff6196f2ebb7984bc117ddb41c3875b4ce8 Mon Sep 17 00:00:00 2001 From: Jon Banafato Date: Thu, 29 Jun 2017 10:55:06 -0400 Subject: [PATCH 054/142] Declare Python 3.6 support (#93) Add Python 3.6 to the Trove classifiers in `setup.py`. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index dc2ce39..81302bf 100755 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Security :: Cryptography', ], install_requires=[ From 801504febc9c5485273e8f3b5efdce0272e55002 Mon Sep 17 00:00:00 2001 From: Florian Motlik Date: Tue, 4 Jul 2017 12:15:20 +0200 Subject: [PATCH 055/142] Fix Shield links (#94) --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4710363..965c401 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,9 @@ Pure Python RSA implementation ============================== [![PyPI](https://img.shields.io/pypi/v/rsa.svg)](https://pypi.python.org/pypi/rsa) -[![Build Status](https://travis-ci.org/sybrenstuvel/python-rsa.svg?branch=master)] - (https://travis-ci.org/sybrenstuvel/python-rsa) -[![Coverage Status](https://coveralls.io/repos/github/sybrenstuvel/python-rsa/badge.svg?branch=master)] - (https://coveralls.io/github/sybrenstuvel/python-rsa?branch=master) -[![Code Climate](https://img.shields.io/codeclimate/github/sybrenstuvel/python-rsa.svg)] - (https://codeclimate.com/github/sybrenstuvel/python-rsa) +[![Build Status](https://travis-ci.org/sybrenstuvel/python-rsa.svg?branch=master)](https://travis-ci.org/sybrenstuvel/python-rsa) +[![Coverage Status](https://coveralls.io/repos/github/sybrenstuvel/python-rsa/badge.svg?branch=master)](https://coveralls.io/github/sybrenstuvel/python-rsa?branch=master) +[![Code Climate](https://img.shields.io/codeclimate/github/sybrenstuvel/python-rsa.svg)](https://codeclimate.com/github/sybrenstuvel/python-rsa) [Python-RSA](https://stuvel.eu/rsa) is a pure-Python RSA implementation. It supports encryption and decryption, signing and verifying signatures, and key From f10db18fd32db967a06ddb52104307acb602ce6b Mon Sep 17 00:00:00 2001 From: David Larlet Date: Thu, 31 Aug 2017 12:06:16 -0400 Subject: [PATCH 056/142] Typos (#95) --- doc/usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/usage.rst b/doc/usage.rst index 6ac9e82..4fbc7e5 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -13,13 +13,13 @@ and a public key. The private key is called *private* for a reason. Never share this key with anyone. -The public key is used for encypting a message such that it can only +The public key is used for encrypting a message such that it can only be read by the owner of the private key. As such it's also referred to as the *encryption key*. Decrypting a message can only be done using the private key, hence it's also called the *decryption key*. The private key is used for signing a message. With this signature and -the public key, the receiver can verifying that a message was signed +the public key, the receiver can verify that a message was signed by the owner of the private key, and that the message was not modified after signing. From 8affa1362ed6a9c70af286026118870538b3bdab Mon Sep 17 00:00:00 2001 From: Joost Rijneveld Date: Mon, 9 Oct 2017 19:00:17 +0200 Subject: [PATCH 057/142] Prevent __pycache__ from being committed (#100) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a90d26c..410404c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /.coverage /.coverage.* /.cache/ +__pycache__/ /build/ /doc/_build/ From 4d3bde66eefd4ee6dd188d5b002867b9e4594b91 Mon Sep 17 00:00:00 2001 From: Joost Rijneveld Date: Sun, 22 Oct 2017 17:48:34 +0200 Subject: [PATCH 058/142] Use tox-travis to run tox when using TravisCI (#102) * Use tox-travis to run tox when using TravisCI * Always run coveralls after TravisCI It appears coveralls no longer comments twice --- .travis.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index a365e3b..f41b88f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,16 @@ language: python -# Python 3.5 specified to make tox environment 'py35' work. -# See: https://github.com/travis-ci/travis-ci/issues/4794 -python: - - 3.5 - # Environment changes have to be manually synced with 'tox.ini'. # See: https://github.com/travis-ci/travis-ci/issues/3024 -env: - - TOXENV=py27 - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=py35 - - TOXENV=pypy +python: + - 2.7 + - 3.3 + - 3.4 + - 3.5 + - "pypy" install: + - pip install tox-travis - pip install -r requirements.txt - pip install coveralls @@ -22,6 +18,4 @@ script: - tox after_success: - # Coveralls submission only for py35 environment, because of being the only - # one that executes doctest-modules testing, according to tox.ini. - - if [ ${TOXENV} = "py35" ]; then coveralls; fi + - coveralls From 3bdc82e799d00738801bd430ca4e57a09819c1b6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 22 Oct 2017 18:02:24 +0300 Subject: [PATCH 059/142] Drop support for EOL Python 3.3 --- .travis.yml | 1 - README.md | 2 +- setup.py | 4 +++- speed.sh | 1 - tox.ini | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f41b88f..821469c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ language: python # See: https://github.com/travis-ci/travis-ci/issues/3024 python: - 2.7 - - 3.3 - 3.4 - 3.5 - "pypy" diff --git a/README.md b/README.md index 965c401..86005fb 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,4 @@ Those modules are marked as deprecated in version 3.4. Furthermore, in 4.0 the I/O functions will be streamlined to always work with bytes on all supported versions of Python. -Version 4.0 will drop support for Python 2.6, and possibly for Python 3.3. +Version 4.0 will drop support for Python 2.6 and 3.3. diff --git a/setup.py b/setup.py index 81302bf..dc237c8 100755 --- a/setup.py +++ b/setup.py @@ -35,12 +35,14 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Security :: Cryptography', ], install_requires=[ diff --git a/speed.sh b/speed.sh index 73d5935..e06d825 100755 --- a/speed.sh +++ b/speed.sh @@ -29,7 +29,6 @@ check_command() { python_versions=" pypy python2.7 - python3.3 python3.4 python3.5 " diff --git a/tox.ini b/tox.ini index ae49b27..a449130 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py27,py33,py34,py35,pypy +envlist = py27,py34,py35,pypy [pytest] addopts = -v --cov rsa --cov-report term-missing From 2c1d51210ca713c8ba3c39cab49e7d958fa7f9f6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 22 Oct 2017 18:01:16 +0300 Subject: [PATCH 060/142] Fix typo --- CHANGELOG.txt | 2 +- README.md | 2 +- doc/installation.rst | 2 +- doc/usage.rst | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index e715d22..ea179ee 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -38,7 +38,7 @@ Version 3.4.1 - released 2016-03-26 Version 3.4 - released 2016-03-17 ---------------------------------------- -- Moved development to Github: https://github.com/sybrenstuvel/python-rsa +- Moved development to GitHub: https://github.com/sybrenstuvel/python-rsa - Solved side-channel vulnerability by implementing blinding, fixes #19 - Deprecated the VARBLOCK format and rsa.bigfile module due to security issues, see https://github.com/sybrenstuvel/python-rsa/issues/13 diff --git a/README.md b/README.md index 86005fb..0df9196 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Download and install using: or download it from the [Python Package Index](https://pypi.python.org/pypi/rsa). -The source code is maintained at [Github](https://github.com/sybrenstuvel/python-rsa/) and is +The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python-rsa/) and is licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/doc/installation.rst b/doc/installation.rst index fa0b16b..32dc257 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -16,7 +16,7 @@ then type:: The sources are tracked in our `Git repository`_ at -Github. It also hosts the `issue tracker`_. +GitHub. It also hosts the `issue tracker`_. .. _`Git repository`: https://github.com/sybrenstuvel/python-rsa.git .. _`issue tracker`: https://github.com/sybrenstuvel/python-rsa/issues diff --git a/doc/usage.rst b/doc/usage.rst index 4fbc7e5..980f99c 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -303,10 +303,10 @@ Only using Python-RSA: the VARBLOCK format 2. decrypt/encrypt_bigfile() does not use hybrid encryption (it uses plain RSA) and has no method for chaining, so block reordering is possible. - See `issue #19 on Github`_ for more information. + See `issue #19 on GitHub`_ for more information. .. _Authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption -.. _issue #19 on Github: https://github.com/sybrenstuvel/python-rsa/issues/13 +.. _issue #19 on GitHub: https://github.com/sybrenstuvel/python-rsa/issues/13 As of Python-RSA version 4.0, the VARBLOCK format has been removed from the library. For now, this section is kept here to document the issues with that From fa9b7875c561d7bc566ebaf143805cdf747b0420 Mon Sep 17 00:00:00 2001 From: Joost Rijneveld Date: Fri, 13 Oct 2017 11:39:54 +0200 Subject: [PATCH 061/142] Remove duplicate hash method definition There is no need to specify this list in PKCS1_v2 when it is already specified in PKCS1. This does rely on the digest_size attribute being available, but pkcs1.py already depends heavily on the specific API of hashlib. --- rsa/pkcs1_v2.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py index 4ae69b3..d6d2423 100644 --- a/rsa/pkcs1_v2.py +++ b/rsa/pkcs1_v2.py @@ -27,14 +27,6 @@ transform, ) -HASH_METHOD_TO_BYTE_LENGTH = { - 'MD5': 16, - 'SHA-1': 20, - 'SHA-256': 28, - 'SHA-384': 48, - 'SHA-512': 64, -} - def mgf1(seed, length, hasher='SHA-1'): """ @@ -59,11 +51,11 @@ def mgf1(seed, length, hasher='SHA-1'): """ try: - hash_length = HASH_METHOD_TO_BYTE_LENGTH[hasher] + hash_length = pkcs1.HASH_METHODS[hasher]().digest_size except KeyError: raise ValueError( 'Invalid `hasher` specified. Please select one of: {hash_list}'.format( - hash_list=', '.join(sorted(HASH_METHOD_TO_BYTE_LENGTH.keys())) + hash_list=', '.join(sorted(pkcs1.HASH_METHODS.keys())) ) ) From 2a020f358b4e8712d2388f12044ec3646be0a3c0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 22 Oct 2017 22:16:33 +0300 Subject: [PATCH 062/142] Add support for Python 3.6 --- .travis.yml | 1 + tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 821469c..662cde5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - 2.7 - 3.4 - 3.5 + - 3.6 - "pypy" install: diff --git a/tox.ini b/tox.ini index a449130..faa4b21 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py27,py34,py35,pypy +envlist = py27,py34,py35,py36,pypy [pytest] addopts = -v --cov rsa --cov-report term-missing @@ -14,7 +14,7 @@ deps=pyasn1 >=0.1.3 pytest-cov mock -[testenv:py35] +[testenv:py36] commands=py.test --doctest-modules rsa tests [pep8] From b738e66ef7ca1b5fc024269e489e6b65319efa1d Mon Sep 17 00:00:00 2001 From: Joost Rijneveld Date: Mon, 5 Feb 2018 13:01:33 +0100 Subject: [PATCH 063/142] Remove keysize requirements / recommendations This library is probably not the right place to recommend key sizes or have discussions about the relation between key sizes and hash function outputs. --- doc/usage.rst | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/doc/usage.rst b/doc/usage.rst index 980f99c..b1244d4 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -90,32 +90,6 @@ generate them for you, then load them in your Python code. OpenSSL generates a 4096-bit key in 3.5 seconds on the same machine as used above. See :ref:`openssl` for more information. -Key size requirements ---------------------- - -Python-RSA version 3.0 introduced PKCS#1-style random padding. This -means that 11 bytes (88 bits) of your key are no longer usable for -encryption, so keys smaller than this are unusable. The larger the -key, the higher the security. - -Creating signatures also requires a key of a certain size, depending -on the used hash method: - -+-------------+-----------------------------------+ -| Hash method | Suggested minimum key size (bits) | -+=============+===================================+ -| MD5 | 360 | -+-------------+-----------------------------------+ -| SHA-1 | 368 | -+-------------+-----------------------------------+ -| SHA-256 | 496 | -+-------------+-----------------------------------+ -| SHA-384 | 624 | -+-------------+-----------------------------------+ -| SHA-512 | 752 | -+-------------+-----------------------------------+ - - Encryption and decryption ------------------------- From 173be1fcf5bdf59e92c1d42072bc3497cb3ed704 Mon Sep 17 00:00:00 2001 From: Joost Rijneveld Date: Thu, 12 Oct 2017 14:46:41 +0200 Subject: [PATCH 064/142] Add support for SHA224 for PKCS1 signatures --- doc/compatibility.rst | 2 +- rsa/pkcs1.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/compatibility.rst b/doc/compatibility.rst index d6e1dc4..be4d295 100644 --- a/doc/compatibility.rst +++ b/doc/compatibility.rst @@ -16,7 +16,7 @@ Encryption: Signatures: PKCS#1 v1.5 using the following hash methods: - MD5, SHA-1, SHA-256, SHA-384, SHA-512 + MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 Private keys: PKCS#1 v1.5 in PEM and DER format, ASN.1 type RSAPrivateKey diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 59190ed..b516cec 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -38,6 +38,7 @@ HASH_ASN1 = { 'MD5': b'\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10', 'SHA-1': b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14', + 'SHA-224': b'\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c', 'SHA-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20', 'SHA-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30', 'SHA-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40', @@ -46,6 +47,7 @@ HASH_METHODS = { 'MD5': hashlib.md5, 'SHA-1': hashlib.sha1, + 'SHA-224': hashlib.sha224, 'SHA-256': hashlib.sha256, 'SHA-384': hashlib.sha384, 'SHA-512': hashlib.sha512, @@ -255,7 +257,7 @@ def sign_hash(hash_value, priv_key, hash_method): None if needing to hash and sign 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-256', 'SHA-384' or 'SHA-512'. + 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'. :return: a message signature block. :raise OverflowError: if the private key is too small to contain the requested hash. @@ -290,7 +292,7 @@ def sign(message, priv_key, hash_method): file-like object. :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-256', 'SHA-384' or 'SHA-512'. + 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'. :return: a message signature block. :raise OverflowError: if the private key is too small to contain the requested hash. From da7145a8835820d63539909cfe5dcc8954e4013a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 5 Feb 2018 13:34:10 +0100 Subject: [PATCH 065/142] Changed unittest to cover the new SHA-224 hash method --- tests/test_pkcs1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index a96c8be..5377b30 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -128,7 +128,7 @@ def test_hash_sign_verify(self): """Test happy flow of hash, sign, and verify""" message = b'je moeder' - msg_hash = pkcs1.compute_hash(message, 'SHA-256') - signature = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-256') + msg_hash = pkcs1.compute_hash(message, 'SHA-224') + signature = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-224') self.assertTrue(pkcs1.verify(message, signature, self.pub)) From 83e273b566343d4276e82d6f26c8a859f3ec048d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 5 Feb 2018 13:37:33 +0100 Subject: [PATCH 066/142] Updated CHANGELOG.md for pull request #104 --- CHANGELOG.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ea179ee..0206190 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -19,7 +19,8 @@ Version 4.0 - in development algorithm used to sign a message. `rsa.verify()` now also returns that name, instead of always returning `True`. [#78](https://github.com/sybrenstuvel/python-rsa/issues/13) - +- Add support for SHA-224 for PKCS1 signatures. + [#104](https://github.com/sybrenstuvel/python-rsa/pull/104) Version 3.4.2 - released 2016-03-29 From 7d03fe40f8d136a3a90eeaf883e47a1c6bfc21f1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Apr 2018 15:35:17 +0300 Subject: [PATCH 067/142] Show README.md on PyPI --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index dc237c8..242ba9b 100755 --- a/setup.py +++ b/setup.py @@ -16,10 +16,15 @@ from setuptools import setup +with open('README.md') as f: + long_description = f.read() + if __name__ == '__main__': setup(name='rsa', version='4.0-alpha', description='Pure-Python RSA implementation', + long_description=long_description, + long_description_content_type='text/markdown', author='Sybren A. Stuvel', author_email='sybren@stuvel.eu', maintainer='Sybren A. Stuvel', From b459ad516c6af1980ceff6762c43699b17341d12 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 13 Jun 2018 21:21:05 -0700 Subject: [PATCH 068/142] Include license file in the generated wheel package The wheel package format supports including the license file. This is done using the [metadata] section in the setup.cfg file. For additional information on this feature, see: https://wheel.readthedocs.io/en/stable/index.html#including-the-license-in-the-generated-wheel-file Helps the project comply with its own license: > 4. Redistribution. You may reproduce and distribute copies of the Work > or Derivative Works thereof in any medium, with or without > modifications, and in Source or Object form, provided that You meet the > following conditions: > > a. You must give any other recipients of the Work or Derivative Works > a copy of this License; and ... --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index a38d4c4..435dfc1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,6 @@ verbosity=2 [bdist_wheel] universal = 1 + +[metadata] +license_file = LICENSE From 6aadcedda80b6f797f8d274803a18170b3d53a1f Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 13 Jun 2018 21:24:59 -0700 Subject: [PATCH 069/142] Update all pypi.python.org URLs to pypi.org For details on the new PyPI, see the blog post: https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0df9196..8cd39c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Pure Python RSA implementation ============================== -[![PyPI](https://img.shields.io/pypi/v/rsa.svg)](https://pypi.python.org/pypi/rsa) +[![PyPI](https://img.shields.io/pypi/v/rsa.svg)](https://pypi.org/project/rsa/) [![Build Status](https://travis-ci.org/sybrenstuvel/python-rsa.svg?branch=master)](https://travis-ci.org/sybrenstuvel/python-rsa) [![Coverage Status](https://coveralls.io/repos/github/sybrenstuvel/python-rsa/badge.svg?branch=master)](https://coveralls.io/github/sybrenstuvel/python-rsa?branch=master) [![Code Climate](https://img.shields.io/codeclimate/github/sybrenstuvel/python-rsa.svg)](https://codeclimate.com/github/sybrenstuvel/python-rsa) @@ -18,7 +18,7 @@ Download and install using: pip install rsa -or download it from the [Python Package Index](https://pypi.python.org/pypi/rsa). +or download it from the [Python Package Index](https://pypi.org/project/rsa/). The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python-rsa/) and is licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0) From 57b3bfd973d038ad8981803df0b389df7058abfa Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 14 Jun 2018 05:28:11 -0700 Subject: [PATCH 070/142] Enable pip cache in Travis CI Slightly speed up builds and reduce load on PyPI servers. For more information, see: https://docs.travis-ci.com/user/caching/#pip-cache --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 662cde5..9df782d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +cache: pip # Environment changes have to be manually synced with 'tox.ini'. # See: https://github.com/travis-ci/travis-ci/issues/3024 From c2211eecc5e51d771cc9681e2abd3e3abf0542b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 10 Jul 2018 21:51:14 +0200 Subject: [PATCH 071/142] Transitioned from `requirements.txt` to Pipenv for package management --- CHANGELOG.txt | 1 + Pipfile | 15 ++++ Pipfile.lock | 214 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 6 -- 4 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 requirements.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 0206190..8af5290 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -21,6 +21,7 @@ Version 4.0 - in development [#78](https://github.com/sybrenstuvel/python-rsa/issues/13) - Add support for SHA-224 for PKCS1 signatures. [#104](https://github.com/sybrenstuvel/python-rsa/pull/104) +- Transitioned from `requirements.txt` to Pipenv for package management. Version 3.4.2 - released 2016-03-29 diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..eff3ff2 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"pyasn1" = ">=0.1.3" + +[dev-packages] +tox = "*" +mock = ">=2.0.0" +Sphinx = "*" + +[requires] +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..75f0639 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,214 @@ +{ + "_meta": { + "hash": { + "sha256": "2ca0d4936d26f0885b8644d32f485514bb382ce23ade465229a20c24137df278" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "pyasn1": { + "hashes": [ + "sha256:a66dcda18dbf6e4663bde70eb30af3fc4fe1acb2d14c4867a861681887a5f9a2", + "sha256:fb81622d8f3509f0026b0683fe90fea27be7284d3826a5f2edf97f69151ab0fc" + ], + "index": "pypi", + "version": "==0.4.3" + } + }, + "develop": { + "alabaster": { + "hashes": [ + "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456", + "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7" + ], + "version": "==0.7.11" + }, + "babel": { + "hashes": [ + "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", + "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + ], + "version": "==2.6.0" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "imagesize": { + "hashes": [ + "sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18", + "sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315" + ], + "version": "==1.0.0" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, + "mock": { + "hashes": [ + "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", + "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", + "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" + ], + "version": "==17.1" + }, + "pbr": { + "hashes": [ + "sha256:4f2b11d95917af76e936811be8361b2b19616e5ef3b55956a429ec7864378e0c", + "sha256:e0f23b61ec42473723b2fec2f33fb12558ff221ee551962f01dd4de9053c2055" + ], + "version": "==4.1.0" + }, + "pluggy": { + "hashes": [ + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", + "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", + "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" + ], + "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==0.6.0" + }, + "py": { + "hashes": [ + "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", + "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + ], + "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==1.5.4" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "version": "==2.2.0" + }, + "pyparsing": { + "hashes": [ + "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", + "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" + ], + "version": "==2.2.0" + }, + "pytz": { + "hashes": [ + "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", + "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" + ], + "version": "==2018.5" + }, + "requests": { + "hashes": [ + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + ], + "version": "==2.19.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", + "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + ], + "version": "==1.2.1" + }, + "sphinx": { + "hashes": [ + "sha256:85f7e32c8ef07f4ba5aeca728e0f7717bef0789fba8458b8d9c5c294cad134f3", + "sha256:d45480a229edf70d84ca9fae3784162b1bc75ee47e480ffe04a4b7f21a95d76d" + ], + "index": "pypi", + "version": "==1.7.5" + }, + "sphinxcontrib-websupport": { + "hashes": [ + "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", + "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + ], + "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==1.1.0" + }, + "tox": { + "hashes": [ + "sha256:8df73fb0eae939692d67a095c49081b1afb948eca51879e5dc1868d9b0ad11de", + "sha256:9f09ec569b5019ed030d3ed3d486a9263e8964a9752253a98f5d67b46e954055" + ], + "index": "pypi", + "version": "==3.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version < '4'", + "version": "==1.23" + }, + "virtualenv": { + "hashes": [ + "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", + "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" + ], + "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==16.0.0" + } + } +} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 954a6f3..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -pyasn1>=0.1.3 - -sphinx -tox -wheel -mock>=2.0.0 From ad510185b54412f390f2ffcb2f8362326a1b0885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 11:27:27 +0200 Subject: [PATCH 072/142] Fixed testing with Travis CI and Tox - Added missing test dependencies. - Updated `.travis.yml` and `tox.ini` to install deps and run with pipenv. - Added testing on Python 3.7. --- .gitignore | 9 +-- .travis.yml | 10 +-- Pipfile | 3 + Pipfile.lock | 167 +++++++++++++++++++++++++++++++++++++++++---------- tox.ini | 17 +++--- 5 files changed, 157 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index 410404c..1f5a640 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,11 @@ /distribute*.tar.gz /distribute*.egg -/.tox/ -/.coverage -/.coverage.* -/.cache/ +.tox/ +.coverage +.coverage.* +.cache/ +.pytest_cache/ __pycache__/ /build/ diff --git a/.travis.yml b/.travis.yml index 9df782d..c9de6d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,15 @@ python: - 3.4 - 3.5 - 3.6 + - 3.7 - "pypy" install: - - pip install tox-travis - - pip install -r requirements.txt - - pip install coveralls + - pip install pipenv + - pipenv install --dev --ignore-pipfile script: - - tox + - pipenv run py.test tox after_success: - - coveralls + - pipenv run coveralls diff --git a/Pipfile b/Pipfile index eff3ff2..00e58f5 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,9 @@ name = "pypi" tox = "*" mock = ">=2.0.0" Sphinx = "*" +coveralls = "*" +pytest = "*" +pytest-cov = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 75f0639..03fc240 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2ca0d4936d26f0885b8644d32f485514bb382ce23ade465229a20c24137df278" + "sha256": "a86e76a85c3a86f6a44f1b5f48205749c451c830746cbc535c66e72d8f5313cb" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "pyasn1": { "hashes": [ - "sha256:a66dcda18dbf6e4663bde70eb30af3fc4fe1acb2d14c4867a861681887a5f9a2", - "sha256:fb81622d8f3509f0026b0683fe90fea27be7284d3826a5f2edf97f69151ab0fc" + "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca", + "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137" ], "index": "pypi", - "version": "==0.4.3" + "version": "==0.4.4" } }, "develop": { @@ -33,6 +33,21 @@ ], "version": "==0.7.11" }, + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==1.2.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, "babel": { "hashes": [ "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", @@ -42,10 +57,10 @@ }, "certifi": { "hashes": [ - "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", - "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", + "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" ], - "version": "==2018.4.16" + "version": "==2018.8.24" }, "chardet": { "hashes": [ @@ -54,6 +69,65 @@ ], "version": "==3.0.4" }, + "colorama": { + "hashes": [ + "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", + "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.3.9" + }, + "coverage": { + "hashes": [ + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", + "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" + ], + "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4'", + "version": "==4.5.1" + }, + "coveralls": { + "hashes": [ + "sha256:9dee67e78ec17b36c52b778247762851c8e19a893c9a14e921a2fc37f05fac22", + "sha256:aec5a1f5e34224b9089664a1b62217732381c7de361b6ed1b3c394d7187b352a" + ], + "index": "pypi", + "version": "==1.5.0" + }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "version": "==0.6.2" + }, "docutils": { "hashes": [ "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", @@ -71,10 +145,11 @@ }, "imagesize": { "hashes": [ - "sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18", - "sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315" + "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", + "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" ], - "version": "==1.0.0" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==1.1.0" }, "jinja2": { "hashes": [ @@ -97,6 +172,14 @@ "index": "pypi", "version": "==2.0.0" }, + "more-itertools": { + "hashes": [ + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + ], + "version": "==4.3.0" + }, "packaging": { "hashes": [ "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", @@ -106,27 +189,26 @@ }, "pbr": { "hashes": [ - "sha256:4f2b11d95917af76e936811be8361b2b19616e5ef3b55956a429ec7864378e0c", - "sha256:e0f23b61ec42473723b2fec2f33fb12558ff221ee551962f01dd4de9053c2055" + "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", + "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa" ], - "version": "==4.1.0" + "version": "==4.2.0" }, "pluggy": { "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", - "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", - "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" ], - "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.3.*'", - "version": "==0.6.0" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==0.7.1" }, "py": { "hashes": [ - "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", - "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", + "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" ], - "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.3.*'", - "version": "==1.5.4" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==1.6.0" }, "pygments": { "hashes": [ @@ -142,6 +224,22 @@ ], "version": "==2.2.0" }, + "pytest": { + "hashes": [ + "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", + "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" + ], + "index": "pypi", + "version": "==3.8.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", + "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762" + ], + "index": "pypi", + "version": "==2.6.0" + }, "pytz": { "hashes": [ "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", @@ -172,34 +270,41 @@ }, "sphinx": { "hashes": [ - "sha256:85f7e32c8ef07f4ba5aeca728e0f7717bef0789fba8458b8d9c5c294cad134f3", - "sha256:d45480a229edf70d84ca9fae3784162b1bc75ee47e480ffe04a4b7f21a95d76d" + "sha256:95acd6648902333647a0e0564abdb28a74b0a76d2333148aa35e5ed1f56d3c4b", + "sha256:c091dbdd5cc5aac6eb95d591a819fd18bccec90ffb048ec465b165a48b839b45" ], "index": "pypi", - "version": "==1.7.5" + "version": "==1.8.0" }, "sphinxcontrib-websupport": { "hashes": [ "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" ], - "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*'", + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", "version": "==1.1.0" }, + "toml": { + "hashes": [ + "sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", + "sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957" + ], + "version": "==0.9.6" + }, "tox": { "hashes": [ - "sha256:8df73fb0eae939692d67a095c49081b1afb948eca51879e5dc1868d9b0ad11de", - "sha256:9f09ec569b5019ed030d3ed3d486a9263e8964a9752253a98f5d67b46e954055" + "sha256:433bb93c57edae263150767e672a0d468ab4fefcc1958eb4013e56a670bb851e", + "sha256:bfb4e4efb7c61a54bc010a5c00fdbe0973bc4bdf04090bfcd3c93c901006177c" ], "index": "pypi", - "version": "==3.1.1" + "version": "==3.3.0" }, "urllib3": { "hashes": [ "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version < '4'", + "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4'", "version": "==1.23" }, "virtualenv": { @@ -207,7 +312,7 @@ "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" ], - "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*'", + "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7'", "version": "==16.0.0" } } diff --git a/tox.ini b/tox.ini index faa4b21..aa4a28e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,20 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py27,py34,py35,py36,pypy +envlist = py27,py34,py35,py36,p37,pypy [pytest] addopts = -v --cov rsa --cov-report term-missing [testenv] -commands=py.test [] -deps=pyasn1 >=0.1.3 - coverage >=3.5 - PyTest - pytest-xdist - pytest-cov - mock +deps = pipenv +commands= + pipenv install --dev --ignore-pipfile + pipenv run py.test tests [testenv:py36] -commands=py.test --doctest-modules rsa tests +commands= + pipenv install --dev --ignore-pipfile + pipenv run py.test --doctest-modules rsa tests [pep8] max-line-length = 100 From ae61397ce7a57969b14eb900f30b76e666170e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 12:05:47 +0200 Subject: [PATCH 073/142] Moved `get_word_alignment()` from `_compat.py` to `machine_size.py` In preparation of removal of Python 2.7 support, I only want to have compatibility code for Python 2.7 in `_compat.py`, and not other kinds of 'compatibility'. --- rsa/machine_size.py | 74 +++++++++++++++++++++++++++++++++++++++++++++ rsa/transform.py | 6 ++-- 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 rsa/machine_size.py diff --git a/rsa/machine_size.py b/rsa/machine_size.py new file mode 100644 index 0000000..2a871b8 --- /dev/null +++ b/rsa/machine_size.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2011 Sybren A. Stüvel +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Detection of 32-bit and 64-bit machines and byte alignment.""" + +import sys + +MAX_INT = sys.maxsize +MAX_INT64 = (1 << 63) - 1 +MAX_INT32 = (1 << 31) - 1 +MAX_INT16 = (1 << 15) - 1 + +# Determine the word size of the processor. +if MAX_INT == MAX_INT64: + # 64-bit processor. + MACHINE_WORD_SIZE = 64 +elif MAX_INT == MAX_INT32: + # 32-bit processor. + MACHINE_WORD_SIZE = 32 +else: + # Else we just assume 64-bit processor keeping up with modern times. + MACHINE_WORD_SIZE = 64 + + +def get_word_alignment(num, force_arch=64, + _machine_word_size=MACHINE_WORD_SIZE): + """ + Returns alignment details for the given number based on the platform + Python is running on. + + :param num: + Unsigned integral number. + :param force_arch: + If you don't want to use 64-bit unsigned chunks, set this to + anything other than 64. 32-bit chunks will be preferred then. + Default 64 will be used when on a 64-bit machine. + :param _machine_word_size: + (Internal) The machine word size used for alignment. + :returns: + 4-tuple:: + + (word_bits, word_bytes, + max_uint, packing_format_type) + """ + max_uint64 = 0xffffffffffffffff + max_uint32 = 0xffffffff + max_uint16 = 0xffff + max_uint8 = 0xff + + if force_arch == 64 and _machine_word_size >= 64 and num > max_uint32: + # 64-bit unsigned integer. + return 64, 8, max_uint64, "Q" + elif num > max_uint16: + # 32-bit unsigned integer + return 32, 4, max_uint32, "L" + elif num > max_uint8: + # 16-bit unsigned integer. + return 16, 2, max_uint16, "H" + else: + # 8-bit unsigned integer. + return 8, 1, max_uint8, "B" diff --git a/rsa/transform.py b/rsa/transform.py index acf044d..628d0af 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -24,8 +24,8 @@ import binascii from struct import pack -from rsa import common -from rsa._compat import byte, is_integer, get_word_alignment +from rsa._compat import byte, is_integer +from rsa import common, machine_size def bytes2int(raw_bytes): @@ -181,7 +181,7 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): # Pack the integer one machine word at a time into bytes. num = number - word_bits, _, max_uint, pack_type = get_word_alignment(num) + word_bits, _, max_uint, pack_type = machine_size.get_word_alignment(num) pack_format = ">%s" % pack_type while num > 0: raw_bytes = pack(pack_format, num & max_uint) + raw_bytes From 7619f14036519d19cbfd217823a354635c5c78fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 12:07:24 +0200 Subject: [PATCH 074/142] Updated changelog --- CHANGELOG.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8af5290..ca58abf 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -11,8 +11,8 @@ Version 4.0 - in development - rsa._version200 - Removed CLI commands that use the VARBLOCK/bigfile format. - Ensured that PublicKey.save_pkcs1() and PrivateKey.save_pkcs1() always return bytes. -- Dropped support for Python 2.6 -- Dropped support for Psyco +- Dropped support for Python 2.6 and 3.3. +- Dropped support for Psyco. - Miller-Rabin iterations determined by bitsize of key. [#58](https://github.com/sybrenstuvel/python-rsa/pull/58) - Added function `rsa.find_signature_hash()` to return the name of the hashing From 11bf33264c620684fcae6c248df30c0a444babdf Mon Sep 17 00:00:00 2001 From: yjqiang <13307130285@fudan.edu.cn> Date: Wed, 12 Sep 2018 10:07:01 +0800 Subject: [PATCH 075/142] speedup "if A and B" if mostly A is True then we should judge B at first --- rsa/key.py | 2 +- rsa/parallel.py | 2 +- rsa/pkcs1.py | 2 +- rsa/pkcs1_v2.py | 2 +- rsa/prime.py | 2 +- rsa/randnum.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rsa/key.py b/rsa/key.py index 196f78d..1004412 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -783,7 +783,7 @@ def newkeys(nbits, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT): if failures: break - if (count and count % 10 == 0) or count == 1: + if (count % 10 == 0 and count) or count == 1: print('%i times' % count) except KeyboardInterrupt: print('Aborted') diff --git a/rsa/parallel.py b/rsa/parallel.py index edaf6bc..a3fe312 100644 --- a/rsa/parallel.py +++ b/rsa/parallel.py @@ -95,7 +95,7 @@ def getprime(nbits, poolsize): if failures: break - if count and count % 10 == 0: + if count % 10 == 0 and count: print('%i times' % count) print('Doctests done') diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index b516cec..84f0e3b 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -433,7 +433,7 @@ def _find_method_hash(clearsig): if failures: break - if count and count % 100 == 0: + if count % 100 == 0 and count: print('%i times' % count) print('Doctests done') diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py index d6d2423..5f9c7dd 100644 --- a/rsa/pkcs1_v2.py +++ b/rsa/pkcs1_v2.py @@ -97,7 +97,7 @@ def mgf1(seed, length, hasher='SHA-1'): if failures: break - if count and count % 100 == 0: + if count % 100 == 0 and count: print('%i times' % count) print('Doctests done') diff --git a/rsa/prime.py b/rsa/prime.py index d8c1f04..3d63542 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -195,7 +195,7 @@ def are_relatively_prime(a, b): if failures: break - if count and count % 100 == 0: + if count % 100 == 0 and count: print('%i times' % count) print('Doctests done') diff --git a/rsa/randnum.py b/rsa/randnum.py index 3c788a5..310acaa 100644 --- a/rsa/randnum.py +++ b/rsa/randnum.py @@ -88,7 +88,7 @@ def randint(maxvalue): if value <= maxvalue: break - if tries and tries % 10 == 0: + if tries % 10 == 0 and tries: # After a lot of tries to get the right number of bits but still # smaller than maxvalue, decrease the number of bits by 1. That'll # dramatically increase the chances to get a large enough number. From bf66e6f3108e5181ebf35e44daa613a2cd3ef242 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 15 Sep 2018 15:05:04 -0700 Subject: [PATCH 076/142] Remove unsued nose configuration The use of nose for testing was previously removed and replaced by pytest. --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 435dfc1..ed8a958 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[nosetests] -verbosity=2 - [bdist_wheel] universal = 1 From 2e07440a40030b95f07fe05b2f3a00d9732e0a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 12:19:22 +0200 Subject: [PATCH 077/142] Typo in `.travis.yml` --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9de6d2..a3d5720 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: - pipenv install --dev --ignore-pipfile script: - - pipenv run py.test tox + - pipenv run tox after_success: - pipenv run coveralls From f4e2e1e1de70a3c3a8153047d4c91e9249a7d047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 12:29:02 +0200 Subject: [PATCH 078/142] Remove Travis CI testing for Python 3.7 Python 3.7 is not yet supported by Travis CI. See https://github.com/travis-ci/travis-ci/issues/9815 Also fixed a mistake in the test config. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a3d5720..0df42d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,15 @@ cache: pip # Environment changes have to be manually synced with 'tox.ini'. # See: https://github.com/travis-ci/travis-ci/issues/3024 + +# Python 3.7 is not yet supported by Travis CI. +# See: https://github.com/travis-ci/travis-ci/issues/9815 + python: - 2.7 - 3.4 - 3.5 - 3.6 - - 3.7 - "pypy" install: @@ -16,7 +19,7 @@ install: - pipenv install --dev --ignore-pipfile script: - - pipenv run tox + - pipenv run py.test after_success: - pipenv run coveralls From fd4cc60b7ac4c33bfc02c80377772244f2441625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 12:34:56 +0200 Subject: [PATCH 079/142] Fixes for Travis CI - Remove `--ignore-pipfile`. This option makes Pipenv install from the `Pipfile.lock` only, ignoring the `Pipfile` itself. In a sense this is good, because it tests with the locked dependencies. However, it breaks when you lock on Python 3.6 but test on Python 3.4 or 2.7. We'll have to re-visit this when dropping support for older Pythons. - pypy 3.6 -> 3.5 - Drop `pypy` from Travis CI testing We still test with 'pypy3.5'. However, Pipenv has an issue with pypy 2.7. See https://github.com/pypa/pipenv/issues/2449 - Pypy3.5 seems to want pathlib2 when running on Travis CI --- .travis.yml | 17 +++++++++++------ Pipfile | 1 + tox.ini | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0df42d2..9b4da02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,20 @@ cache: pip # See: https://github.com/travis-ci/travis-ci/issues/9815 python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 - - "pypy" + - "2.7" + - "3.4" + - "3.5" + - "3.6" + - "3.7-dev" + +# This is blocked by https://github.com/pypa/pipenv/issues/2449, +# also see https://github.com/pypa/pipenv/projects/7 +# - "pypy" + - "pypy3.5" install: - pip install pipenv - - pipenv install --dev --ignore-pipfile + - pipenv install --dev script: - pipenv run py.test diff --git a/Pipfile b/Pipfile index 00e58f5..89ec5fd 100644 --- a/Pipfile +++ b/Pipfile @@ -13,6 +13,7 @@ Sphinx = "*" coveralls = "*" pytest = "*" pytest-cov = "*" +pathlib2 = {version = "*", markers="python_version < '3.6'"} [requires] python_version = "3.6" diff --git a/tox.ini b/tox.ini index aa4a28e..a3109e4 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ addopts = -v --cov rsa --cov-report term-missing [testenv] deps = pipenv commands= - pipenv install --dev --ignore-pipfile + pipenv install --dev pipenv run py.test tests [testenv:py36] From 1971c3e8d144705323e1523bbe8b1c67f5569509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 13:39:55 +0200 Subject: [PATCH 080/142] Declare Python 3.7 as supported --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 242ba9b..5fccadb 100755 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Security :: Cryptography', From 0d13454e8598157d9c1500e7463737c9c28c4710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 13:40:13 +0200 Subject: [PATCH 081/142] Reword README.md for release of 4.0 --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8cd39c3..c5e0f2d 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,10 @@ The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -Plans for the future +Major changes in 4.0 -------------------- -Version 3.4 is the last version in the 3.x range. Version 4.0 will drop the following modules, +Version 3.4 was the last version in the 3.x range. Version 4.0 drops the following modules, as they are insecure: - `rsa._version133` @@ -35,9 +35,9 @@ as they are insecure: - `rsa.bigfile` - `rsa.varblock` -Those modules are marked as deprecated in version 3.4. +Those modules were marked as deprecated in version 3.4. -Furthermore, in 4.0 the I/O functions will be streamlined to always work with bytes on all +Furthermore, in 4.0 the I/O functions is streamlined to always work with bytes on all supported versions of Python. -Version 4.0 will drop support for Python 2.6 and 3.3. +Version 4.0 drops support for Python 2.6 and 3.3. From 368a31f431f24843189b5d4da6b8530fa8bd47d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 16 Sep 2018 13:42:15 +0200 Subject: [PATCH 082/142] Mark 4.0 as released --- CHANGELOG.txt | 2 +- doc/conf.py | 2 +- rsa/__init__.py | 4 ++-- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ca58abf..f8ed650 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,7 @@ Python-RSA changelog ======================================== -Version 4.0 - in development +Version 4.0 - released 2018-09-16 ---------------------------------------- - Removed deprecated modules: diff --git a/doc/conf.py b/doc/conf.py index f209a4d..3331a86 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,7 +46,7 @@ # General information about the project. project = u'Python-RSA' -copyright = u'2011-2017, Sybren A. Stüvel' +copyright = u'2011-2018, Sybren A. Stüvel' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/rsa/__init__.py b/rsa/__init__.py index ce3a341..9b05c6c 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -28,8 +28,8 @@ VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" -__date__ = "2016-03-29" -__version__ = '4.0-alpha' +__date__ = "2018-09-16" +__version__ = '4.0' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 5fccadb..18f339e 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.0-alpha', + version='4.0', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From 3930881bb20d4789887ed31c04c957131ab2ddde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 23 Oct 2018 23:07:01 +0200 Subject: [PATCH 083/142] Fixed testing on older Python versions --- Pipfile | 4 ++ Pipfile.lock | 147 ++++++++++++++++++++++++++++++--------------------- tox.ini | 2 +- 3 files changed, 93 insertions(+), 60 deletions(-) diff --git a/Pipfile b/Pipfile index 89ec5fd..0411098 100644 --- a/Pipfile +++ b/Pipfile @@ -15,5 +15,9 @@ pytest = "*" pytest-cov = "*" pathlib2 = {version = "*", markers="python_version < '3.6'"} +# Required by pytest, not sure why it's not being picked up. +funcsigs = {version = "*", markers="python_version < '3.3'"} +scandir = {version = "*", markers="python_version < '3.3'"} + [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 03fc240..3546a6c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a86e76a85c3a86f6a44f1b5f48205749c451c830746cbc535c66e72d8f5313cb" + "sha256": "6de821570183c80777f192330ab933aeab96f259a4c32a50c9e3731b3333eee2" }, "pipfile-spec": 6, "requires": { @@ -28,17 +28,16 @@ "develop": { "alabaster": { "hashes": [ - "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456", - "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7" + "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", + "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" ], - "version": "==0.7.11" + "version": "==0.7.12" }, "atomicwrites": { "hashes": [ "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" ], - "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*'", "version": "==1.2.1" }, "attrs": { @@ -57,10 +56,10 @@ }, "certifi": { "hashes": [ - "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", - "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.8.24" + "version": "==2018.10.15" }, "chardet": { "hashes": [ @@ -69,18 +68,11 @@ ], "version": "==3.0.4" }, - "colorama": { - "hashes": [ - "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", - "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" - ], - "markers": "sys_platform == 'win32'", - "version": "==0.3.9" - }, "coverage": { "hashes": [ "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:0bf8cbbd71adfff0ef1f3a1531e6402d13b7b01ac50a79c97ca15f030dba6306", "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", @@ -109,18 +101,18 @@ "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:f05a636b4564104120111800021a92e43397bc12a5c72fed7036be8556e0029e", "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" ], - "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4'", "version": "==4.5.1" }, "coveralls": { "hashes": [ - "sha256:9dee67e78ec17b36c52b778247762851c8e19a893c9a14e921a2fc37f05fac22", - "sha256:aec5a1f5e34224b9089664a1b62217732381c7de361b6ed1b3c394d7187b352a" + "sha256:ab638e88d38916a6cedbf80a9cd8992d5fa55c77ab755e262e00b36792b7cd6d", + "sha256:b2388747e2529fa4c669fb1e3e2756e4e07b6ee56c7d9fce05f35ccccc913aa0" ], "index": "pypi", - "version": "==1.5.0" + "version": "==1.5.1" }, "docopt": { "hashes": [ @@ -136,6 +128,22 @@ ], "version": "==0.14" }, + "filelock": { + "hashes": [ + "sha256:86fe6af56ae08ebc9c66d54ba3398c35b98916d0862d782b276a65816ff39392", + "sha256:97694f181bdf58f213cca0a7cb556dc7bf90e2f8eb9aa3151260adac56701afb" + ], + "version": "==3.0.9" + }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "index": "pypi", + "markers": "python_version < '3.3'", + "version": "==1.0.2" + }, "idna": { "hashes": [ "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", @@ -148,7 +156,6 @@ "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", "version": "==1.1.0" }, "jinja2": { @@ -182,33 +189,40 @@ }, "packaging": { "hashes": [ - "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", - "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" + "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", + "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9" ], - "version": "==17.1" + "version": "==18.0" + }, + "pathlib2": { + "hashes": [ + "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", + "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" + ], + "index": "pypi", + "markers": "python_version < '3.6'", + "version": "==2.3.2" }, "pbr": { "hashes": [ - "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", - "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa" + "sha256:ab94783019179bf48f5784edc63f5bc8328ec5ff93f33591567f266d21ac7323", + "sha256:bfcff1a3878eebf559392c2130a17f612a03f96a0d44c3559d9c1e62a4235a2d" ], - "version": "==4.2.0" + "version": "==5.0.0" }, "pluggy": { "hashes": [ - "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", - "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", + "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", - "version": "==0.7.1" + "version": "==0.8.0" }, "py": { "hashes": [ - "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", - "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", - "version": "==1.6.0" + "version": "==1.7.0" }, "pygments": { "hashes": [ @@ -219,18 +233,18 @@ }, "pyparsing": { "hashes": [ - "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", - "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" + "sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", + "sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401" ], - "version": "==2.2.0" + "version": "==2.2.2" }, "pytest": { "hashes": [ - "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", - "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" + "sha256:212be78a6fa5352c392738a49b18f74ae9aeec1040f47c81cadbfd8d1233c310", + "sha256:6f6c1efc8d0ccc21f8f6c34d8330baca883cf109b66b3df954b0a117e5528fb4" ], "index": "pypi", - "version": "==3.8.0" + "version": "==3.9.2" }, "pytest-cov": { "hashes": [ @@ -249,10 +263,28 @@ }, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", + "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" ], - "version": "==2.19.1" + "version": "==2.20.0" + }, + "scandir": { + "hashes": [ + "sha256:04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", + "sha256:1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", + "sha256:1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", + "sha256:346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", + "sha256:44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", + "sha256:61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", + "sha256:a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", + "sha256:c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", + "sha256:c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", + "sha256:c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", + "sha256:f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd" + ], + "index": "pypi", + "markers": "python_version < '3.3'", + "version": "==1.9.0" }, "six": { "hashes": [ @@ -270,49 +302,46 @@ }, "sphinx": { "hashes": [ - "sha256:95acd6648902333647a0e0564abdb28a74b0a76d2333148aa35e5ed1f56d3c4b", - "sha256:c091dbdd5cc5aac6eb95d591a819fd18bccec90ffb048ec465b165a48b839b45" + "sha256:652eb8c566f18823a022bb4b6dbc868d366df332a11a0226b5bc3a798a479f17", + "sha256:d222626d8356de702431e813a05c68a35967e3d66c6cd1c2c89539bb179a7464" ], "index": "pypi", - "version": "==1.8.0" + "version": "==1.8.1" }, "sphinxcontrib-websupport": { "hashes": [ "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'", "version": "==1.1.0" }, "toml": { "hashes": [ - "sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", - "sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957" + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" ], - "version": "==0.9.6" + "version": "==0.10.0" }, "tox": { "hashes": [ - "sha256:433bb93c57edae263150767e672a0d468ab4fefcc1958eb4013e56a670bb851e", - "sha256:bfb4e4efb7c61a54bc010a5c00fdbe0973bc4bdf04090bfcd3c93c901006177c" + "sha256:217fb84aecf9792a98f93f07cfcaf014205a76c64e52bd7c2b4135458e6ad2a1", + "sha256:4baeb3d8ebdcd9f43afce38aa67d06f1165a87d221d5bb21e8b39a0d4880c134" ], "index": "pypi", - "version": "==3.3.0" + "version": "==3.5.2" }, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae", + "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59" ], - "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4'", - "version": "==1.23" + "version": "==1.24" }, "virtualenv": { "hashes": [ "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" ], - "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7'", "version": "==16.0.0" } } diff --git a/tox.ini b/tox.ini index a3109e4..9b034e2 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ addopts = -v --cov rsa --cov-report term-missing [testenv] deps = pipenv commands= - pipenv install --dev + pipenv install --dev --deploy pipenv run py.test tests [testenv:py36] From 0eaeeadc0411cab8c9f6b1155c13729ce9c7714a Mon Sep 17 00:00:00 2001 From: Alexey Sveshnikov Date: Wed, 19 Sep 2018 00:58:48 +0300 Subject: [PATCH 084/142] Use utf-8 when reading README; Use io.open --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 18f339e..ad2603e 100755 --- a/setup.py +++ b/setup.py @@ -14,9 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +# io.open is needed for projects that support Python 2.7. It ensures open() +# defaults to text mode with universal newlines, and accepts an argument to +# specify the text encoding Python 3 only projects can skip this import. +from io import open from setuptools import setup -with open('README.md') as f: +with open('README.md', encoding='utf-8') as f: long_description = f.read() if __name__ == '__main__': From 7424c69bde598a145b7d3807e6c367ac4b922bff Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 10 Oct 2018 05:48:54 -0700 Subject: [PATCH 085/142] Fix BytesWarning in tests Shouldn't try to coerce bytes to a string. Instead, print the repr value (e.g. b'mybytestring'). When running tests with the Python `-b` option, fixes warnings of the form: .../python-rsa/tests/test_strings.py:34: BytesWarning: str() on a bytes instance print("\tMessage: %s" % message) .../python-rsa/tests/test_strings.py:37: BytesWarning: str() on a bytes instance print("\tEncrypted: %s" % encrypted) .../python-rsa/tests/test_strings.py:40: BytesWarning: str() on a bytes instance print("\tDecrypted: %s" % decrypted) --- tests/test_strings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_strings.py b/tests/test_strings.py index 28fa091..26404ae 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -31,12 +31,12 @@ def setUp(self): def test_enc_dec(self): message = unicode_string.encode('utf-8') - print("\tMessage: %s" % message) + print("\tMessage: %r" % message) encrypted = rsa.encrypt(message, self.pub) - print("\tEncrypted: %s" % encrypted) + print("\tEncrypted: %r" % encrypted) decrypted = rsa.decrypt(encrypted, self.priv) - print("\tDecrypted: %s" % decrypted) + print("\tDecrypted: %r" % decrypted) self.assertEqual(message, decrypted) From 1e22d2eb03c7a29a0befe1febf951af9e46ce470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 14:05:42 +0200 Subject: [PATCH 086/142] Drop support for Python 2 and 3.4 Some of our development dependencies, like Sphinx, have already dropped support for Python 2. This makes it harder for this project to update its dependencies. Since Python 2 only has a few more months to live, I think it's fine to drop support now. Python 3.4 has already reached its end-of-life date. Python-RSA now only supports Python 3.5 and newer. Python 3.5 support is intended to last until its end-of-life date of 2019-09-13: https://devguide.python.org/#status-of-python-branches --- .travis.yml | 21 ++- CHANGELOG.txt | 7 + Pipfile | 6 +- Pipfile.lock | 374 ++++++++++++++++++++++++------------------- README.md | 5 + doc/installation.rst | 24 +-- setup.py | 3 - tox.ini | 2 +- 8 files changed, 246 insertions(+), 196 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b4da02..d27ebaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,22 @@ language: python +dist: bionic # required for Python >= 3.7 cache: pip # Environment changes have to be manually synced with 'tox.ini'. # See: https://github.com/travis-ci/travis-ci/issues/3024 -# Python 3.7 is not yet supported by Travis CI. -# See: https://github.com/travis-ci/travis-ci/issues/9815 - python: - - "2.7" - - "3.4" - - "3.5" - "3.6" - - "3.7-dev" + - "3.7" + +matrix: + include: + - python: 3.5 + dist: xenial # Bionic has no Python 3.5 -# This is blocked by https://github.com/pypa/pipenv/issues/2449, -# also see https://github.com/pypa/pipenv/projects/7 -# - "pypy" - - "pypy3.5" + # Disabled, see https://github.com/sybrenstuvel/python-rsa/issues/131 + #- python: pypy3.5 + # dist: xenial # Bionic has no Python 3.5 install: - pip install pipenv diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f8ed650..cbc62fb 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,13 @@ Python-RSA changelog ======================================== +Version 4.1 - in development +---------------------------------------- + +- Dropped support for Python 2 and 3.4. +- Move to Python 3.7 in Pipfile (so `pipenv install` creates a Python 3.7 virtualenv). + + Version 4.0 - released 2018-09-16 ---------------------------------------- diff --git a/Pipfile b/Pipfile index 0411098..a03274c 100644 --- a/Pipfile +++ b/Pipfile @@ -15,9 +15,5 @@ pytest = "*" pytest-cov = "*" pathlib2 = {version = "*", markers="python_version < '3.6'"} -# Required by pytest, not sure why it's not being picked up. -funcsigs = {version = "*", markers="python_version < '3.3'"} -scandir = {version = "*", markers="python_version < '3.3'"} - [requires] -python_version = "3.6" +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 3546a6c..6a399e6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "6de821570183c80777f192330ab933aeab96f259a4c32a50c9e3731b3333eee2" + "sha256": "65309370bad59af42cbf3b538e9a050dece94209b8b9657eb4e9f1ca8feedda4" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3.7" }, "sources": [ { @@ -18,11 +18,11 @@ "default": { "pyasn1": { "hashes": [ - "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca", - "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137" + "sha256:3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", + "sha256:b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86" ], "index": "pypi", - "version": "==0.4.4" + "version": "==0.4.6" } }, "develop": { @@ -35,31 +35,31 @@ }, "atomicwrites": { "hashes": [ - "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", - "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" ], - "version": "==1.2.1" + "version": "==1.3.0" }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "version": "==18.2.0" + "version": "==19.1.0" }, "babel": { "hashes": [ - "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", - "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", + "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" ], - "version": "==2.6.0" + "version": "==2.7.0" }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", + "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" ], - "version": "==2018.10.15" + "version": "==2019.6.16" }, "chardet": { "hashes": [ @@ -70,49 +70,48 @@ }, "coverage": { "hashes": [ - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:0bf8cbbd71adfff0ef1f3a1531e6402d13b7b01ac50a79c97ca15f030dba6306", - "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:f05a636b4564104120111800021a92e43397bc12a5c72fed7036be8556e0029e", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" - ], - "version": "==4.5.1" + "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", + "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", + "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", + "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", + "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", + "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", + "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", + "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", + "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", + "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", + "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", + "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", + "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", + "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", + "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", + "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", + "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", + "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", + "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", + "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", + "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", + "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", + "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", + "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", + "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", + "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", + "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", + "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", + "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", + "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", + "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", + "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" + ], + "version": "==4.5.4" }, "coveralls": { "hashes": [ - "sha256:ab638e88d38916a6cedbf80a9cd8992d5fa55c77ab755e262e00b36792b7cd6d", - "sha256:b2388747e2529fa4c669fb1e3e2756e4e07b6ee56c7d9fce05f35ccccc913aa0" + "sha256:9bc5a1f92682eef59f688a8f280207190d9a6afb84cef8f567fa47631a784060", + "sha256:fb51cddef4bc458de347274116df15d641a735d3f0a580a9472174e2e62f408c" ], "index": "pypi", - "version": "==1.5.1" + "version": "==1.8.2" }, "docopt": { "hashes": [ @@ -122,34 +121,25 @@ }, "docutils": { "hashes": [ - "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", + "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", + "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" ], - "version": "==0.14" + "version": "==0.15.2" }, "filelock": { "hashes": [ - "sha256:86fe6af56ae08ebc9c66d54ba3398c35b98916d0862d782b276a65816ff39392", - "sha256:97694f181bdf58f213cca0a7cb556dc7bf90e2f8eb9aa3151260adac56701afb" + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" ], - "version": "==3.0.9" - }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "index": "pypi", - "markers": "python_version < '3.3'", - "version": "==1.0.2" + "version": "==3.0.12" }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "imagesize": { "hashes": [ @@ -158,162 +148,204 @@ ], "version": "==1.1.0" }, + "importlib-metadata": { + "hashes": [ + "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", + "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" + ], + "version": "==0.19" + }, "jinja2": { "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" ], - "version": "==2.10" + "version": "==2.10.1" }, "markupsafe": { "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" - ], - "version": "==1.0" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" }, "mock": { "hashes": [ - "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", - "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", + "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" ], "index": "pypi", - "version": "==2.0.0" + "version": "==3.0.5" }, "more-itertools": { "hashes": [ - "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", - "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", - "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" ], - "version": "==4.3.0" + "version": "==7.2.0" }, "packaging": { "hashes": [ - "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", - "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9" + "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", + "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" ], - "version": "==18.0" + "version": "==19.1" }, "pathlib2": { "hashes": [ - "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", - "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" + "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", + "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" ], "index": "pypi", "markers": "python_version < '3.6'", - "version": "==2.3.2" - }, - "pbr": { - "hashes": [ - "sha256:ab94783019179bf48f5784edc63f5bc8328ec5ff93f33591567f266d21ac7323", - "sha256:bfcff1a3878eebf559392c2130a17f612a03f96a0d44c3559d9c1e62a4235a2d" - ], - "version": "==5.0.0" + "version": "==2.3.4" }, "pluggy": { "hashes": [ - "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", - "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" + "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", + "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" ], - "version": "==0.8.0" + "version": "==0.12.0" }, "py": { "hashes": [ - "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", - "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" ], - "version": "==1.7.0" + "version": "==1.8.0" }, "pygments": { "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", + "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" ], - "version": "==2.2.0" + "version": "==2.4.2" }, "pyparsing": { "hashes": [ - "sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", - "sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401" + "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", + "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" ], - "version": "==2.2.2" + "version": "==2.4.2" }, "pytest": { "hashes": [ - "sha256:212be78a6fa5352c392738a49b18f74ae9aeec1040f47c81cadbfd8d1233c310", - "sha256:6f6c1efc8d0ccc21f8f6c34d8330baca883cf109b66b3df954b0a117e5528fb4" + "sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", + "sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77" ], "index": "pypi", - "version": "==3.9.2" + "version": "==5.0.1" }, "pytest-cov": { "hashes": [ - "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", - "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762" + "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", + "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a" ], "index": "pypi", - "version": "==2.6.0" + "version": "==2.7.1" }, "pytz": { "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" + "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", + "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" ], - "version": "==2018.5" + "version": "==2019.2" }, "requests": { "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], - "version": "==2.20.0" - }, - "scandir": { - "hashes": [ - "sha256:04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", - "sha256:1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", - "sha256:1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", - "sha256:346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", - "sha256:44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", - "sha256:61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", - "sha256:a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", - "sha256:c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", - "sha256:c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", - "sha256:c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", - "sha256:f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd" - ], - "index": "pypi", - "markers": "python_version < '3.3'", - "version": "==1.9.0" + "version": "==2.22.0" }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "snowballstemmer": { "hashes": [ - "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", - "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" ], - "version": "==1.2.1" + "version": "==1.9.0" }, "sphinx": { "hashes": [ - "sha256:652eb8c566f18823a022bb4b6dbc868d366df332a11a0226b5bc3a798a479f17", - "sha256:d222626d8356de702431e813a05c68a35967e3d66c6cd1c2c89539bb179a7464" + "sha256:22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", + "sha256:f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427" ], "index": "pypi", - "version": "==1.8.1" + "version": "==2.1.2" }, - "sphinxcontrib-websupport": { + "sphinxcontrib-applehelp": { "hashes": [ - "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", - "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", + "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" ], - "version": "==1.1.0" + "version": "==1.0.1" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", + "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" + ], + "version": "==1.0.1" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", + "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" + ], + "version": "==1.0.2" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", + "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" + ], + "version": "==1.0.2" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", + "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" + ], + "version": "==1.1.3" }, "toml": { "hashes": [ @@ -324,25 +356,39 @@ }, "tox": { "hashes": [ - "sha256:217fb84aecf9792a98f93f07cfcaf014205a76c64e52bd7c2b4135458e6ad2a1", - "sha256:4baeb3d8ebdcd9f43afce38aa67d06f1165a87d221d5bb21e8b39a0d4880c134" + "sha256:dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", + "sha256:ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd" ], "index": "pypi", - "version": "==3.5.2" + "version": "==3.13.2" }, "urllib3": { "hashes": [ - "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae", - "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59" + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" ], - "version": "==1.24" + "version": "==1.25.3" }, "virtualenv": { "hashes": [ - "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", - "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" + "sha256:6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d", + "sha256:909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285" + ], + "version": "==16.7.2" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + }, + "zipp": { + "hashes": [ + "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", + "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" ], - "version": "==16.0.0" + "version": "==0.5.2" } } } diff --git a/README.md b/README.md index c5e0f2d..8c43464 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,11 @@ or download it from the [Python Package Index](https://pypi.org/project/rsa/). The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python-rsa/) and is licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +Major changes in 4.1 +-------------------- + +Version 4.0 was the last version to support Python 2 and 3.4. Version 4.1 is compatible with Python 3.5+ only. + Major changes in 4.0 -------------------- diff --git a/doc/installation.rst b/doc/installation.rst index 32dc257..e8f7170 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -1,13 +1,13 @@ Installation ============ -Installation can be done in various ways. The simplest form uses pip -or easy_install. Either one will work:: +Installation can be done in various ways. The simplest form uses pip:: pip install rsa Depending on your system you may need to use ``sudo pip`` if you want to install -the library system-wide. +the library system-wide, or use ``pip install --user rsa`` to install the +library in your home directory. Installation from source is also quite easy. Download the source and then type:: @@ -25,6 +25,9 @@ GitHub. It also hosts the `issue tracker`_. Dependencies ------------ +Python-RSA is compatible with Python versions 3.5 and newer. The last +version with Python 2.7 support was Python-RSA 4.0. + Python-RSA has very few dependencies. As a matter of fact, to use it you only need Python itself. Loading and saving keys does require an extra module, though: pyasn1. If you used pip or easy_install like @@ -34,17 +37,14 @@ described above, you should be ready to go. Development dependencies ------------------------ -In order to start developing on Python-RSA you need a bit more. Use -pip to install the development requirements in a virtual environment:: - - virtualenv -p /path/to/your-python-version python-rsa-venv - . python-rsa-venv/bin/activate - pip install -r python-rsa/requirements.txt +In order to start developing on Python-RSA, use Git_ to get a copy of +the source:: + git clone https://github.com/sybrenstuvel/python-rsa.git -Once these are installed, use Git_ to get a copy of the source:: +Use pipenv_ to install the development requirements in a virtual environment:: - git clone https://github.com/sybrenstuvel/python-rsa.git - python setup.py develop + pipenv install --dev .. _Git: https://git-scm.com/ +.. _pipenv: https://docs.pipenv.org/ diff --git a/setup.py b/setup.py index ad2603e..2fc08ef 100755 --- a/setup.py +++ b/setup.py @@ -44,10 +44,7 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/tox.ini b/tox.ini index 9b034e2..0401412 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py27,py34,py35,py36,p37,pypy +envlist = py35,py36,p37 [pytest] addopts = -v --cov rsa --cov-report term-missing From 29476709271e6988d4df0e1fb1f900bdf8a8c8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 14:17:20 +0200 Subject: [PATCH 087/142] Bumped version to 4.1-dev0 --- rsa/__init__.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 9b05c6c..41f1557 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -28,8 +28,8 @@ VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" -__date__ = "2018-09-16" -__version__ = '4.0' +__date__ = '2019-08-04' +__version__ = '4.1-dev0' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 2fc08ef..cb07110 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.0', + version='4.1-dev0', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From 65a81053d596c62ad2532c7e0e38e68ef61304bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 16:42:44 +0200 Subject: [PATCH 088/142] Updated CHANGELOG for fix of #129 --- CHANGELOG.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index cbc62fb..4b7468b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -6,6 +6,8 @@ Version 4.1 - in development - Dropped support for Python 2 and 3.4. - Move to Python 3.7 in Pipfile (so `pipenv install` creates a Python 3.7 virtualenv). +- Fix [#129](https://github.com/sybrenstuvel/python-rsa/issues/129) Installing from source + gives UnicodeDecodeError. Version 4.0 - released 2018-09-16 From ded036cf988b0cf4b20002d88434282f30762638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 15:02:20 +0200 Subject: [PATCH 089/142] Removed compatibility code for Python 2.7 and 3.4 --- rsa/_compat.py | 109 +---------------------- rsa/cli.py | 6 +- rsa/common.py | 2 - rsa/core.py | 4 +- rsa/key.py | 1 - rsa/machine_size.py | 74 ---------------- rsa/parallel.py | 3 - rsa/pem.py | 6 +- rsa/pkcs1.py | 1 - rsa/pkcs1_v2.py | 1 - rsa/prime.py | 1 - rsa/randnum.py | 4 +- rsa/transform.py | 165 +++-------------------------------- rsa/util.py | 2 - tests/test_cli.py | 10 +-- tests/test_compat.py | 4 +- tests/test_load_save_keys.py | 3 +- tests/test_pem.py | 17 ++-- tests/test_pkcs1.py | 6 +- tests/test_prime.py | 1 - tests/test_transform.py | 23 +---- 21 files changed, 37 insertions(+), 406 deletions(-) delete mode 100644 rsa/machine_size.py diff --git a/rsa/_compat.py b/rsa/_compat.py index 71197a5..843583c 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -16,77 +16,12 @@ """Python compatibility wrappers.""" -from __future__ import absolute_import - import itertools import sys from struct import pack -MAX_INT = sys.maxsize -MAX_INT64 = (1 << 63) - 1 -MAX_INT32 = (1 << 31) - 1 -MAX_INT16 = (1 << 15) - 1 - -PY2 = sys.version_info[0] == 2 - -# Determine the word size of the processor. -if MAX_INT == MAX_INT64: - # 64-bit processor. - MACHINE_WORD_SIZE = 64 -elif MAX_INT == MAX_INT32: - # 32-bit processor. - MACHINE_WORD_SIZE = 32 -else: - # Else we just assume 64-bit processor keeping up with modern times. - MACHINE_WORD_SIZE = 64 - -if PY2: - integer_types = (int, long) - range = xrange - zip = itertools.izip -else: - integer_types = (int, ) - range = range - zip = zip - - -def write_to_stdout(data): - """Writes bytes to stdout - - :type data: bytes - """ - if PY2: - sys.stdout.write(data) - else: - # On Py3 we must use the buffer interface to write bytes. - sys.stdout.buffer.write(data) - - -def is_bytes(obj): - """ - Determines whether the given value is a byte string. - - :param obj: - The value to test. - :returns: - ``True`` if ``value`` is a byte string; ``False`` otherwise. - """ - return isinstance(obj, bytes) - -def is_integer(obj): - """ - Determines whether the given value is an integer. - - :param obj: - The value to test. - :returns: - ``True`` if ``value`` is an integer; ``False`` otherwise. - """ - return isinstance(obj, integer_types) - - -def byte(num): +def byte(num):## XXX """ Converts a number between 0 and 255 (both inclusive) to a base-256 (byte) representation. @@ -117,46 +52,4 @@ def xor_bytes(b1, b2): :returns: Bytes object, result of XOR operation. """ - if PY2: - return ''.join(byte(ord(x) ^ ord(y)) for x, y in zip(b1, b2)) - return bytes(x ^ y for x, y in zip(b1, b2)) - - -def get_word_alignment(num, force_arch=64, - _machine_word_size=MACHINE_WORD_SIZE): - """ - Returns alignment details for the given number based on the platform - Python is running on. - - :param num: - Unsigned integral number. - :param force_arch: - If you don't want to use 64-bit unsigned chunks, set this to - anything other than 64. 32-bit chunks will be preferred then. - Default 64 will be used when on a 64-bit machine. - :param _machine_word_size: - (Internal) The machine word size used for alignment. - :returns: - 4-tuple:: - - (word_bits, word_bytes, - max_uint, packing_format_type) - """ - max_uint64 = 0xffffffffffffffff - max_uint32 = 0xffffffff - max_uint16 = 0xffff - max_uint8 = 0xff - - if force_arch == 64 and _machine_word_size >= 64 and num > max_uint32: - # 64-bit unsigned integer. - return 64, 8, max_uint64, "Q" - elif num > max_uint16: - # 32-bit unsigned integer - return 32, 4, max_uint32, "L" - elif num > max_uint8: - # 16-bit unsigned integer. - return 16, 2, max_uint16, "H" - else: - # 8-bit unsigned integer. - return 8, 1, max_uint8, "B" diff --git a/rsa/cli.py b/rsa/cli.py index 6450af4..77bd4ee 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -19,8 +19,6 @@ These scripts are called by the executables defined in setup.py. """ -from __future__ import with_statement, print_function - import abc import sys from optparse import OptionParser @@ -83,7 +81,7 @@ def keygen(): outfile.write(data) else: print('Writing private key to stdout', file=sys.stderr) - rsa._compat.write_to_stdout(data) + sys.stdout.buffer.write(data) class CryptoOperation(object): @@ -189,7 +187,7 @@ def write_outfile(self, outdata, outname): outfile.write(outdata) else: print('Writing output to stdout', file=sys.stderr) - rsa._compat.write_to_stdout(outdata) + sys.stdout.buffer.write(outdata) class EncryptOperation(CryptoOperation): diff --git a/rsa/common.py b/rsa/common.py index f7aa2d1..a4337f6 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -14,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from rsa._compat import zip - """Common functionality shared by several modules.""" diff --git a/rsa/core.py b/rsa/core.py index b3114d9..0660881 100644 --- a/rsa/core.py +++ b/rsa/core.py @@ -20,11 +20,9 @@ mathematically on integers. """ -from rsa._compat import is_integer - def assert_int(var, name): - if is_integer(var): + if isinstance(var, int): return raise TypeError('%s should be an integer, not %s' % (name, var.__class__)) diff --git a/rsa/key.py b/rsa/key.py index 1004412..1565967 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -36,7 +36,6 @@ import logging import warnings -from rsa._compat import range import rsa.prime import rsa.pem import rsa.common diff --git a/rsa/machine_size.py b/rsa/machine_size.py deleted file mode 100644 index 2a871b8..0000000 --- a/rsa/machine_size.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Detection of 32-bit and 64-bit machines and byte alignment.""" - -import sys - -MAX_INT = sys.maxsize -MAX_INT64 = (1 << 63) - 1 -MAX_INT32 = (1 << 31) - 1 -MAX_INT16 = (1 << 15) - 1 - -# Determine the word size of the processor. -if MAX_INT == MAX_INT64: - # 64-bit processor. - MACHINE_WORD_SIZE = 64 -elif MAX_INT == MAX_INT32: - # 32-bit processor. - MACHINE_WORD_SIZE = 32 -else: - # Else we just assume 64-bit processor keeping up with modern times. - MACHINE_WORD_SIZE = 64 - - -def get_word_alignment(num, force_arch=64, - _machine_word_size=MACHINE_WORD_SIZE): - """ - Returns alignment details for the given number based on the platform - Python is running on. - - :param num: - Unsigned integral number. - :param force_arch: - If you don't want to use 64-bit unsigned chunks, set this to - anything other than 64. 32-bit chunks will be preferred then. - Default 64 will be used when on a 64-bit machine. - :param _machine_word_size: - (Internal) The machine word size used for alignment. - :returns: - 4-tuple:: - - (word_bits, word_bytes, - max_uint, packing_format_type) - """ - max_uint64 = 0xffffffffffffffff - max_uint32 = 0xffffffff - max_uint16 = 0xffff - max_uint8 = 0xff - - if force_arch == 64 and _machine_word_size >= 64 and num > max_uint32: - # 64-bit unsigned integer. - return 64, 8, max_uint64, "Q" - elif num > max_uint16: - # 32-bit unsigned integer - return 32, 4, max_uint32, "L" - elif num > max_uint8: - # 16-bit unsigned integer. - return 16, 2, max_uint16, "H" - else: - # 8-bit unsigned integer. - return 8, 1, max_uint8, "B" diff --git a/rsa/parallel.py b/rsa/parallel.py index a3fe312..ef9f07f 100644 --- a/rsa/parallel.py +++ b/rsa/parallel.py @@ -24,11 +24,8 @@ """ -from __future__ import print_function - import multiprocessing as mp -from rsa._compat import range import rsa.prime import rsa.randnum diff --git a/rsa/pem.py b/rsa/pem.py index 2ddfae8..0650e64 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -18,15 +18,13 @@ import base64 -from rsa._compat import is_bytes, range - def _markers(pem_marker): """ Returns the start and end PEM markers, as bytes. """ - if not is_bytes(pem_marker): + if not isinstance(pem_marker, bytes): pem_marker = pem_marker.encode('ascii') return (b'-----BEGIN ' + pem_marker + b'-----', @@ -49,7 +47,7 @@ def load_pem(contents, pem_marker): """ # We want bytes, not text. If it's text, it can be converted to ASCII bytes. - if not is_bytes(contents): + if not isinstance(contents, bytes): contents = contents.encode('ascii') (pem_start, pem_end) = _markers(pem_marker) diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 84f0e3b..310f22c 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -31,7 +31,6 @@ import hashlib import os -from rsa._compat import range from rsa import common, transform, core # ASN.1 codes that describe the hash algorithm used. diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py index 5f9c7dd..6242a71 100644 --- a/rsa/pkcs1_v2.py +++ b/rsa/pkcs1_v2.py @@ -20,7 +20,6 @@ documentation is RFC 2437: https://tools.ietf.org/html/rfc2437 """ -from rsa._compat import range from rsa import ( common, pkcs1, diff --git a/rsa/prime.py b/rsa/prime.py index 3d63542..a45f659 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -20,7 +20,6 @@ Roberto Tamassia, 2002. """ -from rsa._compat import range import rsa.common import rsa.randnum diff --git a/rsa/randnum.py b/rsa/randnum.py index 310acaa..1f0a4e5 100644 --- a/rsa/randnum.py +++ b/rsa/randnum.py @@ -19,9 +19,9 @@ # Source inspired by code by Yesudeep Mangalapilly import os +import struct from rsa import common, transform -from rsa._compat import byte def read_random_bits(nbits): @@ -40,7 +40,7 @@ def read_random_bits(nbits): if rbits > 0: randomvalue = ord(os.urandom(1)) randomvalue >>= (8 - rbits) - randomdata = byte(randomvalue) + randomdata + randomdata = struct.pack("B", randomvalue) + randomdata return randomdata diff --git a/rsa/transform.py b/rsa/transform.py index 628d0af..bce9f74 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -19,16 +19,10 @@ From bytes to a number, number to bytes, etc. """ -from __future__ import absolute_import +import math -import binascii -from struct import pack -from rsa._compat import byte, is_integer -from rsa import common, machine_size - - -def bytes2int(raw_bytes): +def bytes2int(raw_bytes: bytes) -> int: r"""Converts a list of bytes or an 8-bit string to an integer. When using unicode strings, encode it to some encoding like UTF8 first. @@ -39,110 +33,14 @@ def bytes2int(raw_bytes): 8405007 """ + return int.from_bytes(raw_bytes, 'big', signed=False) - return int(binascii.hexlify(raw_bytes), 16) - - -def _int2bytes(number, block_size=None): - r"""Converts a number to a string of bytes. - - Usage:: - - >>> _int2bytes(123456789) - b'\x07[\xcd\x15' - >>> bytes2int(_int2bytes(123456789)) - 123456789 - - >>> _int2bytes(123456789, 6) - b'\x00\x00\x07[\xcd\x15' - >>> bytes2int(_int2bytes(123456789, 128)) - 123456789 - - >>> _int2bytes(123456789, 3) - Traceback (most recent call last): - ... - OverflowError: Needed 4 bytes for number, but block size is 3 - @param number: the number to convert - @param block_size: the number of bytes to output. If the number encoded to - bytes is less than this, the block will be zero-padded. When not given, - the returned block is not padded. - - @throws OverflowError when block_size is given and the number takes up more - bytes than fit into the block. +def int2bytes(number: int, fill_size: int=0) -> bytes: """ + Convert an unsigned integer to bytes (big-endian):: - # Type checking - if not is_integer(number): - raise TypeError("You must pass an integer for 'number', not %s" % - number.__class__) - - if number < 0: - raise ValueError('Negative numbers cannot be used: %i' % number) - - # Do some bounds checking - if number == 0: - needed_bytes = 1 - raw_bytes = [b'\x00'] - else: - needed_bytes = common.byte_size(number) - raw_bytes = [] - - # You cannot compare None > 0 in Python 3x. It will fail with a TypeError. - if block_size and block_size > 0: - if needed_bytes > block_size: - raise OverflowError('Needed %i bytes for number, but block size ' - 'is %i' % (needed_bytes, block_size)) - - # Convert the number to bytes. - while number > 0: - raw_bytes.insert(0, byte(number & 0xFF)) - number >>= 8 - - # Pad with zeroes to fill the block - if block_size and block_size > 0: - padding = (block_size - needed_bytes) * b'\x00' - else: - padding = b'' - - return padding + b''.join(raw_bytes) - - -def bytes_leading(raw_bytes, needle=b'\x00'): - """ - Finds the number of prefixed byte occurrences in the haystack. - - Useful when you want to deal with padding. - - :param raw_bytes: - Raw bytes. - :param needle: - The byte to count. Default \x00. - :returns: - The number of leading needle bytes. - """ - - leading = 0 - # Indexing keeps compatibility between Python 2.x and Python 3.x - _byte = needle[0] - for x in raw_bytes: - if x == _byte: - leading += 1 - else: - break - return leading - - -def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): - """ - Convert an unsigned integer to bytes (base-256 representation):: - - Does not preserve leading zeros if you don't specify a chunk size or - fill size. - - .. NOTE: - You must not specify both fill_size and chunk_size. Only one - of them is allowed. + Does not preserve leading zeros if you don't specify a fill size. :param number: Integer value @@ -150,15 +48,6 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): If the optional fill size is given the length of the resulting byte string is expected to be the fill size and will be padded with prefix zero bytes to satisfy that length. - :param chunk_size: - If optional chunk size is given and greater than zero, pad the front of - the byte string with binary zeros so that the length is a multiple of - ``chunk_size``. - :param overflow: - ``False`` (default). If this is ``True``, no ``OverflowError`` - will be raised when the fill_size is shorter than the length - of the generated byte sequence. Instead the byte sequence will - be returned as is. :returns: Raw bytes (base-256 representation). :raises: @@ -171,42 +60,12 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): if number < 0: raise ValueError("Number must be an unsigned integer: %d" % number) - if fill_size and chunk_size: - raise ValueError("You can either fill or pad chunks, but not both") - - # Ensure these are integers. - number & 1 - - raw_bytes = b'' - - # Pack the integer one machine word at a time into bytes. - num = number - word_bits, _, max_uint, pack_type = machine_size.get_word_alignment(num) - pack_format = ">%s" % pack_type - while num > 0: - raw_bytes = pack(pack_format, num & max_uint) + raw_bytes - num >>= word_bits - # Obtain the index of the first non-zero byte. - zero_leading = bytes_leading(raw_bytes) - if number == 0: - raw_bytes = b'\x00' - # De-padding. - raw_bytes = raw_bytes[zero_leading:] - - length = len(raw_bytes) - if fill_size and fill_size > 0: - if not overflow and length > fill_size: - raise OverflowError( - "Need %d bytes for number, but fill size is %d" % - (length, fill_size) - ) - raw_bytes = raw_bytes.rjust(fill_size, b'\x00') - elif chunk_size and chunk_size > 0: - remainder = length % chunk_size - if remainder: - padding_size = chunk_size - remainder - raw_bytes = raw_bytes.rjust(length + padding_size, b'\x00') - return raw_bytes + bytes_required = max(1, math.ceil(number.bit_length() / 8)) + + if fill_size > 0: + return number.to_bytes(fill_size, 'big') + + return number.to_bytes(bytes_required, 'big') if __name__ == '__main__': diff --git a/rsa/util.py b/rsa/util.py index 29d5eb1..c44d04c 100644 --- a/rsa/util.py +++ b/rsa/util.py @@ -16,8 +16,6 @@ """Utility functions.""" -from __future__ import with_statement, print_function - import sys from optparse import OptionParser diff --git a/tests/test_cli.py b/tests/test_cli.py index 7ce57eb..7cf7ed4 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -15,21 +15,15 @@ import rsa import rsa.cli import rsa.util -from rsa._compat import PY2 -def make_buffer(): - if PY2: - return BytesIO() +def make_buffer() -> StringIO: buf = StringIO() buf.buffer = BytesIO() return buf -def get_bytes_out(out): - if PY2: - # Python 2.x writes 'str' to stdout - return out.getvalue() +def get_bytes_out(out: StringIO) -> bytes: # Python 3.x writes 'bytes' to stdout.buffer return out.buffer.getvalue() diff --git a/tests/test_compat.py b/tests/test_compat.py index 62e933f..a047402 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -17,7 +17,7 @@ import unittest import struct -from rsa._compat import byte, is_bytes, range, xor_bytes +from rsa._compat import byte, xor_bytes class TestByte(unittest.TestCase): @@ -26,7 +26,7 @@ class TestByte(unittest.TestCase): def test_byte(self): for i in range(256): byt = byte(i) - self.assertTrue(is_bytes(byt)) + self.assertIsInstance(byt, bytes) self.assertEqual(ord(byt), i) def test_raises_StructError_on_overflow(self): diff --git a/tests/test_load_save_keys.py b/tests/test_load_save_keys.py index 55bd5a4..c0ee06c 100644 --- a/tests/test_load_save_keys.py +++ b/tests/test_load_save_keys.py @@ -17,13 +17,12 @@ """Unittest for saving and loading keys.""" import base64 -import mock import os.path import pickle import unittest import warnings +from unittest import mock -from rsa._compat import range import rsa.key B64PRIV_DER = b'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt' diff --git a/tests/test_pem.py b/tests/test_pem.py index 5fb9600..b9bd93c 100644 --- a/tests/test_pem.py +++ b/tests/test_pem.py @@ -17,7 +17,6 @@ import unittest -from rsa._compat import is_bytes from rsa.pem import _markers import rsa.key @@ -79,13 +78,13 @@ class TestByteOutput(unittest.TestCase): def test_bytes_public(self): key = rsa.key.PublicKey.load_pkcs1_openssl_pem(public_key_pem) - self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) - self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + self.assertIsInstance(key.save_pkcs1(format='DER'), bytes) + self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes) def test_bytes_private(self): key = rsa.key.PrivateKey.load_pkcs1(private_key_pem) - self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) - self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + self.assertIsInstance(key.save_pkcs1(format='DER'), bytes) + self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes) class TestByteInput(unittest.TestCase): @@ -93,10 +92,10 @@ class TestByteInput(unittest.TestCase): def test_bytes_public(self): key = rsa.key.PublicKey.load_pkcs1_openssl_pem(public_key_pem.encode('ascii')) - self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) - self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + self.assertIsInstance(key.save_pkcs1(format='DER'), bytes) + self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes) def test_bytes_private(self): key = rsa.key.PrivateKey.load_pkcs1(private_key_pem.encode('ascii')) - self.assertTrue(is_bytes(key.save_pkcs1(format='DER'))) - self.assertTrue(is_bytes(key.save_pkcs1(format='PEM'))) + self.assertIsInstance(key.save_pkcs1(format='DER'), bytes) + self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes) diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 5377b30..1704ffd 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -21,7 +21,7 @@ import rsa from rsa import pkcs1 -from rsa._compat import byte, is_bytes +from rsa._compat import byte class BinaryTest(unittest.TestCase): @@ -46,8 +46,8 @@ def test_decoding_failure(self): # Alter the encrypted stream a = encrypted[5] - if is_bytes(a): - a = ord(a) + self.assertIsInstance(a, int) + altered_a = (a + 1) % 256 encrypted = encrypted[:5] + byte(altered_a) + encrypted[6:] diff --git a/tests/test_prime.py b/tests/test_prime.py index f3bda9b..75b80b3 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -18,7 +18,6 @@ import unittest -from rsa._compat import range import rsa.prime import rsa.randnum diff --git a/tests/test_transform.py b/tests/test_transform.py index fe0970c..83c3934 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -15,37 +15,26 @@ # limitations under the License. import unittest -from rsa.transform import int2bytes, bytes2int, _int2bytes +from rsa.transform import int2bytes, bytes2int class Test_int2bytes(unittest.TestCase): def test_accuracy(self): self.assertEqual(int2bytes(123456789), b'\x07[\xcd\x15') - self.assertEqual(_int2bytes(123456789), b'\x07[\xcd\x15') def test_codec_identity(self): self.assertEqual(bytes2int(int2bytes(123456789, 128)), 123456789) - self.assertEqual(bytes2int(_int2bytes(123456789, 128)), 123456789) def test_chunk_size(self): self.assertEqual(int2bytes(123456789, 6), b'\x00\x00\x07[\xcd\x15') self.assertEqual(int2bytes(123456789, 7), b'\x00\x00\x00\x07[\xcd\x15') - self.assertEqual(_int2bytes(123456789, 6), - b'\x00\x00\x07[\xcd\x15') - self.assertEqual(_int2bytes(123456789, 7), - b'\x00\x00\x00\x07[\xcd\x15') - def test_zero(self): self.assertEqual(int2bytes(0, 4), b'\x00' * 4) self.assertEqual(int2bytes(0, 7), b'\x00' * 7) self.assertEqual(int2bytes(0), b'\x00') - self.assertEqual(_int2bytes(0, 4), b'\x00' * 4) - self.assertEqual(_int2bytes(0, 7), b'\x00' * 7) - self.assertEqual(_int2bytes(0), b'\x00') - def test_correctness_against_base_implementation(self): # Slow test. values = [ @@ -54,26 +43,16 @@ def test_correctness_against_base_implementation(self): 1 << 77, ] for value in values: - self.assertEqual(int2bytes(value), _int2bytes(value), - "Boom %d" % value) self.assertEqual(bytes2int(int2bytes(value)), value, "Boom %d" % value) - self.assertEqual(bytes2int(_int2bytes(value)), - value, - "Boom %d" % value) def test_raises_OverflowError_when_chunk_size_is_insufficient(self): self.assertRaises(OverflowError, int2bytes, 123456789, 3) self.assertRaises(OverflowError, int2bytes, 299999999999, 4) - self.assertRaises(OverflowError, _int2bytes, 123456789, 3) - self.assertRaises(OverflowError, _int2bytes, 299999999999, 4) - def test_raises_ValueError_when_negative_integer(self): self.assertRaises(ValueError, int2bytes, -1) - self.assertRaises(ValueError, _int2bytes, -1) def test_raises_TypeError_when_not_integer(self): self.assertRaises(TypeError, int2bytes, None) - self.assertRaises(TypeError, _int2bytes, None) From 6760eb76e665dc81863a82110164c4b3b38e7ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 15:47:11 +0200 Subject: [PATCH 090/142] Added mypy for static type checking --- .gitignore | 1 + Pipfile | 3 ++- Pipfile.lock | 54 +++++++++++++++++++++++++++++++++++++++++++++- rsa/cli.py | 10 ++++----- setup.cfg | 13 +++++++++++ tests/test_cli.py | 40 ++++++++++++++++------------------ tests/test_mypy.py | 27 +++++++++++++++++++++++ tox.ini | 3 --- 8 files changed, 119 insertions(+), 32 deletions(-) create mode 100644 tests/test_mypy.py diff --git a/.gitignore b/.gitignore index 1f5a640..e31443a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ .coverage .coverage.* .cache/ +.mypy_cache/ .pytest_cache/ __pycache__/ diff --git a/Pipfile b/Pipfile index a03274c..b22cab3 100644 --- a/Pipfile +++ b/Pipfile @@ -13,7 +13,8 @@ Sphinx = "*" coveralls = "*" pytest = "*" pytest-cov = "*" -pathlib2 = {version = "*", markers="python_version < '3.6'"} +pathlib2 = {version = "*",markers = "python_version < '3.6'"} +mypy = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 6a399e6..9ec9947 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "65309370bad59af42cbf3b538e9a050dece94209b8b9657eb4e9f1ca8feedda4" + "sha256": "376fdc997e7b1f113fdc6c27ce5d1ba88f01a3dbe64f03d54132e5f7b5cd24a4" }, "pipfile-spec": 6, "requires": { @@ -210,6 +210,30 @@ ], "version": "==7.2.0" }, + "mypy": { + "hashes": [ + "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", + "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", + "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", + "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", + "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", + "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", + "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", + "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", + "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", + "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", + "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91" + ], + "index": "pypi", + "version": "==0.720" + }, + "mypy-extensions": { + "hashes": [ + "sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", + "sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e" + ], + "version": "==0.4.1" + }, "packaging": { "hashes": [ "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", @@ -362,6 +386,34 @@ "index": "pypi", "version": "==3.13.2" }, + "typed-ast": { + "hashes": [ + "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", + "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", + "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", + "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", + "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", + "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", + "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", + "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", + "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", + "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", + "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", + "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", + "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", + "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", + "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + ], + "version": "==1.4.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", + "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", + "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" + ], + "version": "==3.7.4" + }, "urllib3": { "hashes": [ "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", diff --git a/rsa/cli.py b/rsa/cli.py index 77bd4ee..cbf3f97 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -21,9 +21,11 @@ import abc import sys +import typing from optparse import OptionParser import rsa +import rsa.key import rsa.pkcs1 HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) @@ -84,14 +86,12 @@ def keygen(): sys.stdout.buffer.write(data) -class CryptoOperation(object): +class CryptoOperation(metaclass=abc.ABCMeta): """CLI callable that operates with input, output, and a key.""" - __metaclass__ = abc.ABCMeta - keyname = 'public' # or 'private' usage = 'usage: %%prog [options] %(keyname)s_key' - description = None + description = '' operation = 'decrypt' operation_past = 'decrypted' operation_progressive = 'decrypting' @@ -102,7 +102,7 @@ class CryptoOperation(object): expected_cli_args = 1 has_output = True - key_class = rsa.PublicKey + key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey] def __init__(self): self.usage = self.usage % self.__class__.__dict__ diff --git a/setup.cfg b/setup.cfg index ed8a958..c5601b1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,16 @@ universal = 1 [metadata] license_file = LICENSE + +[flake8] +max-line-length = 100 + +[pep8] +max-line-length = 100 + +[mypy] +python_version = 3.7 +warn_unused_ignores = True +ignore_missing_imports = True +follow_imports = skip +incremental = True diff --git a/tests/test_cli.py b/tests/test_cli.py index 7cf7ed4..1cd92c2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,41 +4,37 @@ from __future__ import print_function -import unittest -import sys import functools -from contextlib import contextmanager - +import io import os -from io import StringIO, BytesIO +import sys +import typing +import unittest +from contextlib import contextmanager, redirect_stdout, redirect_stderr import rsa import rsa.cli import rsa.util -def make_buffer() -> StringIO: - buf = StringIO() - buf.buffer = BytesIO() - return buf +@contextmanager +def captured_output() -> typing.Generator: + """Captures output to stdout and stderr""" + # According to mypy, we're not supposed to change buf_out.buffer. + # However, this is just a test, and it works, hence the 'type: ignore'. + buf_out = io.StringIO() + buf_out.buffer = io.BytesIO() # type: ignore -def get_bytes_out(out: StringIO) -> bytes: - # Python 3.x writes 'bytes' to stdout.buffer - return out.buffer.getvalue() + buf_err = io.StringIO() + buf_err.buffer = io.BytesIO() # type: ignore + with redirect_stdout(buf_out), redirect_stderr(buf_err): + yield buf_out, buf_err -@contextmanager -def captured_output(): - """Captures output to stdout and stderr""" - new_out, new_err = make_buffer(), make_buffer() - old_out, old_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = new_out, new_err - yield new_out, new_err - finally: - sys.stdout, sys.stderr = old_out, old_err +def get_bytes_out(buf) -> bytes: + return buf.buffer.getvalue() @contextmanager diff --git a/tests/test_mypy.py b/tests/test_mypy.py new file mode 100644 index 0000000..c2a9745 --- /dev/null +++ b/tests/test_mypy.py @@ -0,0 +1,27 @@ +import pathlib +import unittest + +import mypy.api + +test_modules = ['rsa', 'tests'] + + +class MypyRunnerTest(unittest.TestCase): + def test_run_mypy(self): + proj_root = pathlib.Path(__file__).parent.parent + args = ['--incremental', '--ignore-missing-imports'] + [str(proj_root / dirname) for dirname + in test_modules] + + result = mypy.api.run(args) + + stdout, stderr, status = result + + messages = [] + if stderr: + messages.append(stderr) + if stdout: + messages.append(stdout) + if status: + messages.append('Mypy failed with status %d' % status) + if messages: + self.fail('\n'.join(['Mypy errors:'] + messages)) diff --git a/tox.ini b/tox.ini index 0401412..c9f1370 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,3 @@ commands= commands= pipenv install --dev --ignore-pipfile pipenv run py.test --doctest-modules rsa tests - -[pep8] -max-line-length = 100 From b6cebd53fcafd3088fc8361f6d3466166f75410b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 16:41:01 +0200 Subject: [PATCH 091/142] Added type annotations + some fixes to get them correct One functional change: `CryptoOperation.read_infile()` now reads bytes from `sys.stdin` instead of text. This is necessary to be consistent with the rest of the code, which all deals with bytes. --- rsa/_compat.py | 7 ++-- rsa/cli.py | 44 +++++++++++++---------- rsa/common.py | 19 +++++----- rsa/core.py | 6 ++-- rsa/key.py | 95 +++++++++++++++++++++++++------------------------ rsa/parallel.py | 4 +-- rsa/pem.py | 10 ++++-- rsa/pkcs1.py | 36 +++++++++---------- rsa/pkcs1_v2.py | 2 +- rsa/prime.py | 12 +++---- rsa/randnum.py | 8 ++--- rsa/util.py | 2 +- 12 files changed, 129 insertions(+), 116 deletions(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index 843583c..b31331e 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -21,14 +21,11 @@ from struct import pack -def byte(num):## XXX +def byte(num: int): """ Converts a number between 0 and 255 (both inclusive) to a base-256 (byte) representation. - Use it as a replacement for ``chr`` where you are expecting a byte - because this will work on all current versions of Python:: - :param num: An unsigned integer between 0 and 255 (both inclusive). :returns: @@ -37,7 +34,7 @@ def byte(num):## XXX return pack("B", num) -def xor_bytes(b1, b2): +def xor_bytes(b1: bytes, b2: bytes) -> bytes: """ Returns the bitwise XOR result between two bytes objects, b1 ^ b2. diff --git a/rsa/cli.py b/rsa/cli.py index cbf3f97..60bc07c 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -22,20 +22,21 @@ import abc import sys import typing -from optparse import OptionParser +import optparse import rsa import rsa.key import rsa.pkcs1 HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) +Indexable = typing.Union[typing.Tuple, typing.List[str]] -def keygen(): +def keygen() -> None: """Key generator.""" # Parse the CLI options - parser = OptionParser(usage='usage: %prog [options] keysize', + parser = optparse.OptionParser(usage='usage: %prog [options] keysize', description='Generates a new RSA keypair of "keysize" bits.') parser.add_option('--pubout', type='string', @@ -104,13 +105,14 @@ class CryptoOperation(metaclass=abc.ABCMeta): key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey] - def __init__(self): + def __init__(self) -> None: self.usage = self.usage % self.__class__.__dict__ self.input_help = self.input_help % self.__class__.__dict__ self.output_help = self.output_help % self.__class__.__dict__ @abc.abstractmethod - def perform_operation(self, indata, key, cli_args): + def perform_operation(self, indata: bytes, key: rsa.key.AbstractKey, + cli_args: Indexable): """Performs the program's operation. Implement in a subclass. @@ -118,7 +120,7 @@ def perform_operation(self, indata, key, cli_args): :returns: the data to write to the output. """ - def __call__(self): + def __call__(self) -> None: """Runs the program.""" (cli, cli_args) = self.parse_cli() @@ -133,13 +135,13 @@ def __call__(self): if self.has_output: self.write_outfile(outdata, cli.output) - def parse_cli(self): + def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]: """Parse the CLI options :returns: (cli_opts, cli_args) """ - parser = OptionParser(usage=self.usage, description=self.description) + parser = optparse.OptionParser(usage=self.usage, description=self.description) parser.add_option('-i', '--input', type='string', help=self.input_help) @@ -158,7 +160,7 @@ def parse_cli(self): return cli, cli_args - def read_key(self, filename, keyform): + def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey: """Reads a public or private key.""" print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr) @@ -167,7 +169,7 @@ def read_key(self, filename, keyform): return self.key_class.load_pkcs1(keydata, keyform) - def read_infile(self, inname): + def read_infile(self, inname: str) -> bytes: """Read the input file""" if inname: @@ -176,9 +178,9 @@ def read_infile(self, inname): return infile.read() print('Reading input from stdin', file=sys.stderr) - return sys.stdin.read() + return sys.stdin.buffer.read() - def write_outfile(self, outdata, outname): + def write_outfile(self, outdata: bytes, outname: str) -> None: """Write the output file""" if outname: @@ -200,9 +202,10 @@ class EncryptOperation(CryptoOperation): operation_past = 'encrypted' operation_progressive = 'encrypting' - def perform_operation(self, indata, pub_key, cli_args=None): + def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, + cli_args: Indexable=()): """Encrypts files.""" - + assert isinstance(pub_key, rsa.key.PublicKey) return rsa.encrypt(indata, pub_key) @@ -217,9 +220,10 @@ class DecryptOperation(CryptoOperation): operation_progressive = 'decrypting' key_class = rsa.PrivateKey - def perform_operation(self, indata, priv_key, cli_args=None): + def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, + cli_args: Indexable=()): """Decrypts files.""" - + assert isinstance(priv_key, rsa.key.PrivateKey) return rsa.decrypt(indata, priv_key) @@ -239,8 +243,10 @@ class SignOperation(CryptoOperation): output_help = ('Name of the file to write the signature to. Written ' 'to stdout if this option is not present.') - def perform_operation(self, indata, priv_key, cli_args): + def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, + cli_args: Indexable): """Signs files.""" + assert isinstance(priv_key, rsa.key.PrivateKey) hash_method = cli_args[1] if hash_method not in HASH_METHODS: @@ -264,8 +270,10 @@ class VerifyOperation(CryptoOperation): expected_cli_args = 2 has_output = False - def perform_operation(self, indata, pub_key, cli_args): + def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, + cli_args: Indexable): """Verifies files.""" + assert isinstance(pub_key, rsa.key.PublicKey) signature_file = cli_args[1] diff --git a/rsa/common.py b/rsa/common.py index a4337f6..b983b98 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -16,17 +16,18 @@ """Common functionality shared by several modules.""" +import typing + class NotRelativePrimeError(ValueError): - def __init__(self, a, b, d, msg=None): - super(NotRelativePrimeError, self).__init__( - msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d)) + def __init__(self, a, b, d, msg=''): + super().__init__(msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d)) self.a = a self.b = b self.d = d -def bit_size(num): +def bit_size(num: int) -> int: """ Number of bits needed to represent a integer excluding any prefix 0 bits. @@ -54,7 +55,7 @@ def bit_size(num): raise TypeError('bit_size(num) only supports integers, not %r' % type(num)) -def byte_size(number): +def byte_size(number: int) -> int: """ Returns the number of bytes required to hold a specific long number. @@ -79,7 +80,7 @@ def byte_size(number): return ceil_div(bit_size(number), 8) -def ceil_div(num, div): +def ceil_div(num: int, div: int) -> int: """ Returns the ceiling function of a division between `num` and `div`. @@ -103,7 +104,7 @@ def ceil_div(num, div): return quanta -def extended_gcd(a, b): +def extended_gcd(a: int, b: int) -> typing.Tuple[int, int, int]: """Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb """ # r = gcd(a,b) i = multiplicitive inverse of a mod b @@ -128,7 +129,7 @@ def extended_gcd(a, b): return a, lx, ly # Return only positive values -def inverse(x, n): +def inverse(x: int, n: int) -> int: """Returns the inverse of x % n under multiplication, a.k.a x^-1 (mod n) >>> inverse(7, 4) @@ -145,7 +146,7 @@ def inverse(x, n): return inv -def crt(a_values, modulo_values): +def crt(a_values: typing.Iterable[int], modulo_values: typing.Iterable[int]) -> int: """Chinese Remainder Theorem. Calculates x such that x = a[i] (mod m[i]) for each i. diff --git a/rsa/core.py b/rsa/core.py index 0660881..42f7bac 100644 --- a/rsa/core.py +++ b/rsa/core.py @@ -21,14 +21,14 @@ """ -def assert_int(var, name): +def assert_int(var: int, name: str): if isinstance(var, int): return raise TypeError('%s should be an integer, not %s' % (name, var.__class__)) -def encrypt_int(message, ekey, n): +def encrypt_int(message: int, ekey: int, n: int) -> int: """Encrypts a message using encryption key 'ekey', working modulo n""" assert_int(message, 'message') @@ -44,7 +44,7 @@ def encrypt_int(message, ekey, n): return pow(message, ekey, n) -def decrypt_int(cyphertext, dkey, n): +def decrypt_int(cyphertext: int, dkey: int, n: int) -> int: """Decrypts a cypher text using the decryption key 'dkey', working modulo n""" assert_int(cyphertext, 'cyphertext') diff --git a/rsa/key.py b/rsa/key.py index 1565967..05c77ef 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -34,6 +34,7 @@ """ import logging +import typing import warnings import rsa.prime @@ -47,17 +48,17 @@ DEFAULT_EXPONENT = 65537 -class AbstractKey(object): +class AbstractKey: """Abstract superclass for private and public keys.""" __slots__ = ('n', 'e') - def __init__(self, n, e): + def __init__(self, n: int, e: int) -> None: self.n = n self.e = e @classmethod - def _load_pkcs1_pem(cls, keyfile): + def _load_pkcs1_pem(cls, keyfile: bytes) -> 'AbstractKey': """Loads a key in PKCS#1 PEM format, implement in a subclass. :param keyfile: contents of a PEM-encoded file that contains @@ -69,7 +70,7 @@ def _load_pkcs1_pem(cls, keyfile): """ @classmethod - def _load_pkcs1_der(cls, keyfile): + def _load_pkcs1_der(cls, keyfile: bytes) -> 'AbstractKey': """Loads a key in PKCS#1 PEM format, implement in a subclass. :param keyfile: contents of a DER-encoded file that contains @@ -80,14 +81,14 @@ def _load_pkcs1_der(cls, keyfile): :rtype: AbstractKey """ - def _save_pkcs1_pem(self): + def _save_pkcs1_pem(self) -> bytes: """Saves the key in PKCS#1 PEM format, implement in a subclass. :returns: the PEM-encoded key. :rtype: bytes """ - def _save_pkcs1_der(self): + def _save_pkcs1_der(self) -> bytes: """Saves the key in PKCS#1 DER format, implement in a subclass. :returns: the DER-encoded key. @@ -95,7 +96,7 @@ def _save_pkcs1_der(self): """ @classmethod - def load_pkcs1(cls, keyfile, format='PEM'): + def load_pkcs1(cls, keyfile: bytes, format='PEM') -> 'AbstractKey': """Loads a key in PKCS#1 DER or PEM format. :param keyfile: contents of a DER- or PEM-encoded file that contains @@ -117,7 +118,7 @@ def load_pkcs1(cls, keyfile, format='PEM'): return method(keyfile) @staticmethod - def _assert_format_exists(file_format, methods): + def _assert_format_exists(file_format: str, methods: typing.Mapping[str, typing.Callable]) -> typing.Callable: """Checks whether the given file format exists in 'methods'. """ @@ -128,7 +129,7 @@ def _assert_format_exists(file_format, methods): raise ValueError('Unsupported format: %r, try one of %s' % (file_format, formats)) - def save_pkcs1(self, format='PEM'): + def save_pkcs1(self, format='PEM') -> bytes: """Saves the key in PKCS#1 DER or PEM format. :param format: the format to save; 'PEM' or 'DER' @@ -145,7 +146,7 @@ def save_pkcs1(self, format='PEM'): method = self._assert_format_exists(format, methods) return method() - def blind(self, message, r): + def blind(self, message: int, r: int) -> int: """Performs blinding on the message using random number 'r'. :param message: the message, as integer, to blind. @@ -162,7 +163,7 @@ def blind(self, message, r): return (message * pow(r, self.e, self.n)) % self.n - def unblind(self, blinded, r): + def unblind(self, blinded: int, r: int) -> int: """Performs blinding on the message using random number 'r'. :param blinded: the blinded message, as integer, to unblind. @@ -206,18 +207,18 @@ class PublicKey(AbstractKey): def __getitem__(self, key): return getattr(self, key) - def __repr__(self): + def __repr__(self) -> str: return 'PublicKey(%i, %i)' % (self.n, self.e) - def __getstate__(self): + def __getstate__(self) -> typing.Tuple[int, int]: """Returns the key as tuple for pickling.""" return self.n, self.e - def __setstate__(self, state): + def __setstate__(self, state: typing.Tuple[int, int]) -> None: """Sets the key from tuple.""" self.n, self.e = state - def __eq__(self, other): + def __eq__(self, other: typing.Any) -> bool: if other is None: return False @@ -226,14 +227,14 @@ def __eq__(self, other): return self.n == other.n and self.e == other.e - def __ne__(self, other): + def __ne__(self, other: typing.Any) -> bool: return not (self == other) - def __hash__(self): + def __hash__(self) -> int: return hash((self.n, self.e)) @classmethod - def _load_pkcs1_der(cls, keyfile): + def _load_pkcs1_der(cls, keyfile: bytes) -> 'PublicKey': """Loads a key in PKCS#1 DER format. :param keyfile: contents of a DER-encoded file that contains the public @@ -259,7 +260,7 @@ def _load_pkcs1_der(cls, keyfile): (priv, _) = decoder.decode(keyfile, asn1Spec=AsnPubKey()) return cls(n=int(priv['modulus']), e=int(priv['publicExponent'])) - def _save_pkcs1_der(self): + def _save_pkcs1_der(self) -> bytes: """Saves the public key in PKCS#1 DER format. :returns: the DER-encoded public key. @@ -277,7 +278,7 @@ def _save_pkcs1_der(self): return encoder.encode(asn_key) @classmethod - def _load_pkcs1_pem(cls, keyfile): + def _load_pkcs1_pem(cls, keyfile: bytes) -> 'PublicKey': """Loads a PKCS#1 PEM-encoded public key file. The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and @@ -291,7 +292,7 @@ def _load_pkcs1_pem(cls, keyfile): der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY') return cls._load_pkcs1_der(der) - def _save_pkcs1_pem(self): + def _save_pkcs1_pem(self) -> bytes: """Saves a PKCS#1 PEM-encoded public key file. :return: contents of a PEM-encoded file that contains the public key. @@ -302,7 +303,7 @@ def _save_pkcs1_pem(self): return rsa.pem.save_pem(der, 'RSA PUBLIC KEY') @classmethod - def load_pkcs1_openssl_pem(cls, keyfile): + def load_pkcs1_openssl_pem(cls, keyfile: bytes) -> 'PublicKey': """Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL. These files can be recognised in that they start with BEGIN PUBLIC KEY @@ -321,14 +322,12 @@ def load_pkcs1_openssl_pem(cls, keyfile): return cls.load_pkcs1_openssl_der(der) @classmethod - def load_pkcs1_openssl_der(cls, keyfile): + def load_pkcs1_openssl_der(cls, keyfile: bytes) -> 'PublicKey': """Loads a PKCS#1 DER-encoded public key file from OpenSSL. :param keyfile: contents of a DER-encoded file that contains the public key, from OpenSSL. :return: a PublicKey object - :rtype: bytes - """ from rsa.asn1 import OpenSSLPubKey @@ -369,7 +368,7 @@ class PrivateKey(AbstractKey): __slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef') - def __init__(self, n, e, d, p, q): + def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None: AbstractKey.__init__(self, n, e) self.d = d self.p = p @@ -383,18 +382,18 @@ def __init__(self, n, e, d, p, q): def __getitem__(self, key): return getattr(self, key) - def __repr__(self): - return 'PrivateKey(%(n)i, %(e)i, %(d)i, %(p)i, %(q)i)' % self + def __repr__(self) -> str: + return 'PrivateKey(%i, %i, %i, %i, %i)' % (self.n, self.e, self.d, self.p, self.q) - def __getstate__(self): + def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]: """Returns the key as tuple for pickling.""" return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef - def __setstate__(self, state): + def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]): """Sets the key from tuple.""" self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state - def __eq__(self, other): + def __eq__(self, other: typing.Any) -> bool: if other is None: return False @@ -410,13 +409,13 @@ def __eq__(self, other): self.exp2 == other.exp2 and self.coef == other.coef) - def __ne__(self, other): + def __ne__(self, other: typing.Any) -> bool: return not (self == other) - def __hash__(self): + def __hash__(self) -> int: return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef)) - def blinded_decrypt(self, encrypted): + def blinded_decrypt(self, encrypted: int) -> int: """Decrypts the message using blinding to prevent side-channel attacks. :param encrypted: the encrypted message @@ -432,7 +431,7 @@ def blinded_decrypt(self, encrypted): return self.unblind(decrypted, blind_r) - def blinded_encrypt(self, message): + def blinded_encrypt(self, message: int) -> int: """Encrypts the message using blinding to prevent side-channel attacks. :param message: the message to encrypt @@ -448,7 +447,7 @@ def blinded_encrypt(self, message): return self.unblind(encrypted, blind_r) @classmethod - def _load_pkcs1_der(cls, keyfile): + def _load_pkcs1_der(cls, keyfile: bytes) -> 'PrivateKey': """Loads a key in PKCS#1 DER format. :param keyfile: contents of a DER-encoded file that contains the private @@ -505,7 +504,7 @@ def _load_pkcs1_der(cls, keyfile): return key - def _save_pkcs1_der(self): + def _save_pkcs1_der(self) -> bytes: """Saves the private key in PKCS#1 DER format. :returns: the DER-encoded private key. @@ -543,7 +542,7 @@ class AsnPrivKey(univ.Sequence): return encoder.encode(asn_key) @classmethod - def _load_pkcs1_pem(cls, keyfile): + def _load_pkcs1_pem(cls, keyfile: bytes) -> 'PrivateKey': """Loads a PKCS#1 PEM-encoded private key file. The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and @@ -558,7 +557,7 @@ def _load_pkcs1_pem(cls, keyfile): der = rsa.pem.load_pem(keyfile, b'RSA PRIVATE KEY') return cls._load_pkcs1_der(der) - def _save_pkcs1_pem(self): + def _save_pkcs1_pem(self) -> bytes: """Saves a PKCS#1 PEM-encoded private key file. :return: contents of a PEM-encoded file that contains the private key. @@ -569,7 +568,7 @@ def _save_pkcs1_pem(self): return rsa.pem.save_pem(der, b'RSA PRIVATE KEY') -def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True): +def find_p_q(nbits: int, getprime_func=rsa.prime.getprime, accurate=True) -> typing.Tuple[int, int]: """Returns a tuple of two different primes of nbits bits each. The resulting p * q has exacty 2 * nbits bits, and the returned p and q @@ -647,7 +646,7 @@ def is_acceptable(p, q): return max(p, q), min(p, q) -def calculate_keys_custom_exponent(p, q, exponent): +def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> typing.Tuple[int, int]: """Calculates an encryption and a decryption key given p, q and an exponent, and returns them as a tuple (e, d) @@ -677,7 +676,7 @@ def calculate_keys_custom_exponent(p, q, exponent): return exponent, d -def calculate_keys(p, q): +def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]: """Calculates an encryption and a decryption key given p and q, and returns them as a tuple (e, d) @@ -690,7 +689,10 @@ def calculate_keys(p, q): return calculate_keys_custom_exponent(p, q, DEFAULT_EXPONENT) -def gen_keys(nbits, getprime_func, accurate=True, exponent=DEFAULT_EXPONENT): +def gen_keys(nbits: int, + getprime_func: typing.Callable[[int], int], + accurate=True, + exponent=DEFAULT_EXPONENT) -> typing.Tuple[int, int, int, int]: """Generate RSA keys of nbits bits. Returns (p, q, e, d). Note: this can take a long time, depending on the key size. @@ -718,7 +720,8 @@ def gen_keys(nbits, getprime_func, accurate=True, exponent=DEFAULT_EXPONENT): return p, q, e, d -def newkeys(nbits, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT): +def newkeys(nbits: int, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT) \ + -> typing.Tuple[PublicKey, PrivateKey]: """Generates public and private keys, and returns them as (pub, priv). The public key is also known as the 'encryption key', and is a @@ -753,9 +756,9 @@ def newkeys(nbits, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT): # Determine which getprime function to use if poolsize > 1: from rsa import parallel - import functools - getprime_func = functools.partial(parallel.getprime, poolsize=poolsize) + def getprime_func(nbits): + return parallel.getprime(nbits, poolsize=poolsize) else: getprime_func = rsa.prime.getprime diff --git a/rsa/parallel.py b/rsa/parallel.py index ef9f07f..e81bcad 100644 --- a/rsa/parallel.py +++ b/rsa/parallel.py @@ -30,7 +30,7 @@ import rsa.randnum -def _find_prime(nbits, pipe): +def _find_prime(nbits: int, pipe) -> None: while True: integer = rsa.randnum.read_random_odd_int(nbits) @@ -40,7 +40,7 @@ def _find_prime(nbits, pipe): return -def getprime(nbits, poolsize): +def getprime(nbits: int, poolsize: int) -> int: """Returns a prime number that can be stored in 'nbits' bits. Works in multiple threads at the same time. diff --git a/rsa/pem.py b/rsa/pem.py index 0650e64..02c7691 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -17,9 +17,13 @@ """Functions that load and write PEM-encoded files.""" import base64 +import typing +# Should either be ASCII strings or bytes. +FlexiText = typing.Union[str, bytes] -def _markers(pem_marker): + +def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]: """ Returns the start and end PEM markers, as bytes. """ @@ -31,7 +35,7 @@ def _markers(pem_marker): b'-----END ' + pem_marker + b'-----') -def load_pem(contents, pem_marker): +def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes: """Loads a PEM file. :param contents: the contents of the file to interpret @@ -97,7 +101,7 @@ def load_pem(contents, pem_marker): return base64.standard_b64decode(pem) -def save_pem(contents, pem_marker): +def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes: """Saves a PEM file. :param contents: the contents to encode in PEM format diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 310f22c..39ebc49 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -30,8 +30,9 @@ import hashlib import os +import typing -from rsa import common, transform, core +from . import common, transform, core, key # ASN.1 codes that describe the hash algorithm used. HASH_ASN1 = { @@ -65,7 +66,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 @@ -111,7 +112,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. @@ -145,7 +146,7 @@ def _pad_for_signing(message, target_length): message]) -def encrypt(message, pub_key): +def encrypt(message: bytes, pub_key: key.PublicKey): """Encrypts the given message using PKCS#1 v1.5 :param message: the message to encrypt. Must be a byte string no longer than @@ -177,7 +178,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 @@ -246,14 +247,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'. @@ -280,7 +280,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 @@ -302,7 +302,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. @@ -337,7 +337,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. @@ -356,7 +356,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. @@ -377,7 +377,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 @@ -394,18 +394,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. diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py index 6242a71..b751399 100644 --- a/rsa/pkcs1_v2.py +++ b/rsa/pkcs1_v2.py @@ -27,7 +27,7 @@ ) -def mgf1(seed, length, hasher='SHA-1'): +def mgf1(seed: bytes, length: int, hasher='SHA-1') -> bytes: """ MGF1 is a Mask Generation Function based on a hash function. diff --git a/rsa/prime.py b/rsa/prime.py index a45f659..dcd60dd 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -26,7 +26,7 @@ __all__ = ['getprime', 'are_relatively_prime'] -def gcd(p, q): +def gcd(p: int, q: int) -> int: """Returns the greatest common divisor of p and q >>> gcd(48, 180) @@ -38,7 +38,7 @@ def gcd(p, q): return p -def get_primality_testing_rounds(number): +def get_primality_testing_rounds(number: int) -> int: """Returns minimum number of rounds for Miller-Rabing primality testing, based on number bitsize. @@ -64,7 +64,7 @@ def get_primality_testing_rounds(number): return 10 -def miller_rabin_primality_testing(n, k): +def miller_rabin_primality_testing(n: int, k: int) -> bool: """Calculates whether n is composite (which is always correct) or prime (which theoretically is incorrect with error probability 4**-k), by applying Miller-Rabin primality testing. @@ -117,7 +117,7 @@ def miller_rabin_primality_testing(n, k): return True -def is_prime(number): +def is_prime(number: int) -> bool: """Returns True if the number is prime, and False otherwise. >>> is_prime(2) @@ -143,7 +143,7 @@ def is_prime(number): return miller_rabin_primality_testing(number, k + 1) -def getprime(nbits): +def getprime(nbits: int) -> int: """Returns a prime number that can be stored in 'nbits' bits. >>> p = getprime(128) @@ -171,7 +171,7 @@ def getprime(nbits): # Retry if not prime -def are_relatively_prime(a, b): +def are_relatively_prime(a: int, b: int) -> bool: """Returns True if a and b are relatively prime, and False if they are not. diff --git a/rsa/randnum.py b/rsa/randnum.py index 1f0a4e5..e9bfc87 100644 --- a/rsa/randnum.py +++ b/rsa/randnum.py @@ -24,7 +24,7 @@ from rsa import common, transform -def read_random_bits(nbits): +def read_random_bits(nbits: int) -> bytes: """Reads 'nbits' random bits. If nbits isn't a whole number of bytes, an extra byte will be appended with @@ -45,7 +45,7 @@ def read_random_bits(nbits): return randomdata -def read_random_int(nbits): +def read_random_int(nbits: int) -> int: """Reads a random integer of approximately nbits bits. """ @@ -59,7 +59,7 @@ def read_random_int(nbits): return value -def read_random_odd_int(nbits): +def read_random_odd_int(nbits: int) -> int: """Reads a random odd integer of approximately nbits bits. >>> read_random_odd_int(512) & 1 @@ -72,7 +72,7 @@ def read_random_odd_int(nbits): return value | 1 -def randint(maxvalue): +def randint(maxvalue: int) -> int: """Returns a random integer x with 1 <= x <= maxvalue May take a very long time in specific situations. If maxvalue needs N bits diff --git a/rsa/util.py b/rsa/util.py index c44d04c..e0c7134 100644 --- a/rsa/util.py +++ b/rsa/util.py @@ -22,7 +22,7 @@ import rsa.key -def private_to_public(): +def private_to_public() -> None: """Reads a private key and outputs the corresponding public key.""" # Parse the CLI options From 222fb5f44796c739b71afff74998e06ff22ca111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 17:22:46 +0200 Subject: [PATCH 092/142] Switch from Pipenv to Poetry Poetry has a nicer interface, performs more tasks than Pipenv, and is generally more pleasant to use. --- .travis.yml | 8 +- Pipfile | 20 -- Pipfile.lock | 446 ----------------------------------------- poetry.lock | 535 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 48 +++++ tox.ini | 16 +- 6 files changed, 596 insertions(+), 477 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/.travis.yml b/.travis.yml index d27ebaf..d0bd7cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,11 @@ matrix: # dist: xenial # Bionic has no Python 3.5 install: - - pip install pipenv - - pipenv install --dev + - pip install poetry + - poetry install -v script: - - pipenv run py.test + - poetry run py.test tests/ after_success: - - pipenv run coveralls + - poetry run coveralls diff --git a/Pipfile b/Pipfile deleted file mode 100644 index b22cab3..0000000 --- a/Pipfile +++ /dev/null @@ -1,20 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -"pyasn1" = ">=0.1.3" - -[dev-packages] -tox = "*" -mock = ">=2.0.0" -Sphinx = "*" -coveralls = "*" -pytest = "*" -pytest-cov = "*" -pathlib2 = {version = "*",markers = "python_version < '3.6'"} -mypy = "*" - -[requires] -python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 9ec9947..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,446 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "376fdc997e7b1f113fdc6c27ce5d1ba88f01a3dbe64f03d54132e5f7b5cd24a4" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "pyasn1": { - "hashes": [ - "sha256:3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", - "sha256:b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86" - ], - "index": "pypi", - "version": "==0.4.6" - } - }, - "develop": { - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, - "attrs": { - "hashes": [ - "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", - "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" - ], - "version": "==19.1.0" - }, - "babel": { - "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" - ], - "version": "==2.7.0" - }, - "certifi": { - "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" - ], - "version": "==2019.6.16" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "coverage": { - "hashes": [ - "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", - "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", - "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", - "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", - "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", - "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", - "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", - "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", - "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", - "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", - "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", - "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", - "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", - "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", - "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", - "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", - "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", - "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", - "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", - "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", - "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", - "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", - "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", - "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", - "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", - "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", - "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", - "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", - "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", - "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", - "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", - "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" - ], - "version": "==4.5.4" - }, - "coveralls": { - "hashes": [ - "sha256:9bc5a1f92682eef59f688a8f280207190d9a6afb84cef8f567fa47631a784060", - "sha256:fb51cddef4bc458de347274116df15d641a735d3f0a580a9472174e2e62f408c" - ], - "index": "pypi", - "version": "==1.8.2" - }, - "docopt": { - "hashes": [ - "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" - ], - "version": "==0.6.2" - }, - "docutils": { - "hashes": [ - "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", - "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", - "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" - ], - "version": "==0.15.2" - }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" - }, - "imagesize": { - "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" - ], - "version": "==1.1.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" - ], - "version": "==0.19" - }, - "jinja2": { - "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" - ], - "version": "==2.10.1" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "version": "==1.1.1" - }, - "mock": { - "hashes": [ - "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", - "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" - ], - "index": "pypi", - "version": "==3.0.5" - }, - "more-itertools": { - "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" - ], - "version": "==7.2.0" - }, - "mypy": { - "hashes": [ - "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", - "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", - "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", - "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", - "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", - "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", - "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", - "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", - "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", - "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", - "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91" - ], - "index": "pypi", - "version": "==0.720" - }, - "mypy-extensions": { - "hashes": [ - "sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", - "sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e" - ], - "version": "==0.4.1" - }, - "packaging": { - "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" - ], - "version": "==19.1" - }, - "pathlib2": { - "hashes": [ - "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", - "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" - ], - "index": "pypi", - "markers": "python_version < '3.6'", - "version": "==2.3.4" - }, - "pluggy": { - "hashes": [ - "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", - "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" - ], - "version": "==0.12.0" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" - ], - "version": "==1.8.0" - }, - "pygments": { - "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" - ], - "version": "==2.4.2" - }, - "pyparsing": { - "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" - ], - "version": "==2.4.2" - }, - "pytest": { - "hashes": [ - "sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", - "sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77" - ], - "index": "pypi", - "version": "==5.0.1" - }, - "pytest-cov": { - "hashes": [ - "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", - "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a" - ], - "index": "pypi", - "version": "==2.7.1" - }, - "pytz": { - "hashes": [ - "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", - "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" - ], - "version": "==2019.2" - }, - "requests": { - "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" - ], - "version": "==2.22.0" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "snowballstemmer": { - "hashes": [ - "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" - ], - "version": "==1.9.0" - }, - "sphinx": { - "hashes": [ - "sha256:22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", - "sha256:f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427" - ], - "index": "pypi", - "version": "==2.1.2" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", - "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" - ], - "version": "==1.0.1" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", - "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" - ], - "version": "==1.0.1" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", - "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" - ], - "version": "==1.0.2" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", - "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" - ], - "version": "==1.0.2" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", - "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" - ], - "version": "==1.1.3" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" - ], - "version": "==0.10.0" - }, - "tox": { - "hashes": [ - "sha256:dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", - "sha256:ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd" - ], - "index": "pypi", - "version": "==3.13.2" - }, - "typed-ast": { - "hashes": [ - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "version": "==1.4.0" - }, - "typing-extensions": { - "hashes": [ - "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", - "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", - "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" - ], - "version": "==3.7.4" - }, - "urllib3": { - "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" - ], - "version": "==1.25.3" - }, - "virtualenv": { - "hashes": [ - "sha256:6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d", - "sha256:909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285" - ], - "version": "==16.7.2" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "zipp": { - "hashes": [ - "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", - "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" - ], - "version": "==0.5.2" - } - } -} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..4477d26 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,535 @@ +[[package]] +category = "dev" +description = "A configurable sidebar-enabled Sphinx theme" +name = "alabaster" +optional = false +python-versions = "*" +version = "0.7.12" + +[[package]] +category = "dev" +description = "Atomic file writes." +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.1.0" + +[[package]] +category = "dev" +description = "Internationalization utilities" +name = "babel" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.7.0" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +category = "dev" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2019.6.16" + +[[package]] +category = "dev" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.4.1" + +[[package]] +category = "dev" +description = "Code coverage measurement for Python" +name = "coverage" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" +version = "4.5.4" + +[[package]] +category = "dev" +description = "Show coverage stats online via coveralls.io" +name = "coveralls" +optional = false +python-versions = "*" +version = "1.8.2" + +[package.dependencies] +coverage = ">=3.6,<5.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[[package]] +category = "dev" +description = "Pythonic argument parser, that will make you smile" +name = "docopt" +optional = false +python-versions = "*" +version = "0.6.2" + +[[package]] +category = "dev" +description = "Docutils -- Python Documentation Utilities" +name = "docutils" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.15.2" + +[[package]] +category = "dev" +description = "A platform independent file lock." +name = "filelock" +optional = false +python-versions = "*" +version = "3.0.12" + +[[package]] +category = "dev" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8" + +[[package]] +category = "dev" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +name = "imagesize" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +name = "importlib-metadata" +optional = false +python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" +version = "0.19" + +[package.dependencies] +zipp = ">=0.5" + +[[package]] +category = "dev" +description = "A small but fast and easy to use stand-alone template engine written in pure python." +name = "jinja2" +optional = false +python-versions = "*" +version = "2.10.1" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[[package]] +category = "dev" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.4" +version = "7.2.0" + +[[package]] +category = "dev" +description = "Optional static typing for Python" +name = "mypy" +optional = false +python-versions = "*" +version = "0.720" + +[package.dependencies] +mypy-extensions = ">=0.4.0,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[[package]] +category = "dev" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "mypy-extensions" +optional = false +python-versions = "*" +version = "0.4.1" + +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.1" + +[package.dependencies] +attrs = "*" +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "dev" +description = "Object-oriented filesystem paths" +marker = "python_version >= \"3.5\" and python_version < \"3.6\" or python_version < \"3.6\"" +name = "pathlib2" +optional = false +python-versions = "*" +version = "2.3.4" + +[package.dependencies] +six = "*" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.12.0" + +[package.dependencies] +importlib-metadata = ">=0.12" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.0" + +[[package]] +category = "main" +description = "ASN.1 types and codecs" +name = "pyasn1" +optional = false +python-versions = "*" +version = "0.4.6" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.4.2" + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.2" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.0.1" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +importlib-metadata = ">=0.12" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.dependencies.pathlib2] +python = "<3.6" +version = ">=2.2.0" + +[[package]] +category = "dev" +description = "Pytest plugin for measuring coverage." +name = "pytest-cov" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.7.1" + +[package.dependencies] +coverage = ">=4.4" +pytest = ">=3.6" + +[[package]] +category = "dev" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2019.2" + +[[package]] +category = "dev" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.22.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.9" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "1.12.0" + +[[package]] +category = "dev" +description = "This package provides 23 stemmers for 22 languages generated from Snowball algorithms." +name = "snowballstemmer" +optional = false +python-versions = "*" +version = "1.9.0" + +[[package]] +category = "dev" +description = "Python documentation generator" +name = "sphinx" +optional = false +python-versions = ">=3.5" +version = "2.1.2" + +[package.dependencies] +Jinja2 = ">=2.3" +Pygments = ">=2.0" +alabaster = ">=0.7,<0.8" +babel = ">=1.3,<2.0 || >2.0" +colorama = ">=0.3.5" +docutils = ">=0.12" +imagesize = "*" +packaging = "*" +requests = ">=2.5.0" +setuptools = "*" +snowballstemmer = ">=1.1" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = "*" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = "*" + +[[package]] +category = "dev" +description = "" +name = "sphinxcontrib-applehelp" +optional = false +python-versions = "*" +version = "1.0.1" + +[[package]] +category = "dev" +description = "" +name = "sphinxcontrib-devhelp" +optional = false +python-versions = "*" +version = "1.0.1" + +[[package]] +category = "dev" +description = "" +name = "sphinxcontrib-htmlhelp" +optional = false +python-versions = "*" +version = "1.0.2" + +[[package]] +category = "dev" +description = "A sphinx extension which renders display math in HTML via JavaScript" +name = "sphinxcontrib-jsmath" +optional = false +python-versions = ">=3.5" +version = "1.0.1" + +[[package]] +category = "dev" +description = "" +name = "sphinxcontrib-qthelp" +optional = false +python-versions = "*" +version = "1.0.2" + +[[package]] +category = "dev" +description = "" +name = "sphinxcontrib-serializinghtml" +optional = false +python-versions = "*" +version = "1.1.3" + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.0" + +[[package]] +category = "dev" +description = "tox is a generic virtualenv management and test command line tool" +name = "tox" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.13.2" + +[package.dependencies] +filelock = ">=3.0.0,<4" +importlib-metadata = ">=0.12,<1" +packaging = ">=14" +pluggy = ">=0.12.0,<1" +py = ">=1.4.17,<2" +six = ">=1.0.0,<2" +toml = ">=0.9.4" +virtualenv = ">=14.0.0" + +[[package]] +category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" +optional = false +python-versions = "*" +version = "1.4.0" + +[[package]] +category = "dev" +description = "Type Hints for Python" +name = "typing" +optional = false +python-versions = "*" +version = "3.7.4" + +[[package]] +category = "dev" +description = "Backported and Experimental Type Hints for Python 3.5+" +name = "typing-extensions" +optional = false +python-versions = "*" +version = "3.7.4" + +[package.dependencies] +typing = ">=3.7.4" + +[[package]] +category = "dev" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +version = "1.25.3" + +[[package]] +category = "dev" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "16.7.2" + +[[package]] +category = "dev" +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.7" + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +name = "zipp" +optional = false +python-versions = ">=2.7" +version = "0.5.2" + +[metadata] +content-hash = "f64e643bf6d9b4a0e0a72c8c7d4b097f49b0a36ef2832349d7954c750b9cc70c" +python-versions = "^3.5" + +[metadata.hashes] +alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] +atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] +attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] +babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] +certifi = ["046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"] +chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] +colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] +coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"] +coveralls = ["9bc5a1f92682eef59f688a8f280207190d9a6afb84cef8f567fa47631a784060", "fb51cddef4bc458de347274116df15d641a735d3f0a580a9472174e2e62f408c"] +docopt = ["49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"] +docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] +filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"] +idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] +imagesize = ["3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"] +importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"] +jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] +markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] +mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"] +mypy-extensions = ["37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", "b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"] +packaging = ["a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", "c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe"] +pathlib2 = ["2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", "446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8"] +pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"] +py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] +pyasn1 = ["0c444a3482c5f5c7fab93567761324f77045ede002362171e12acdd400ea50e0", "27e919f274d96829d9c78455eacf6a2253c9fd44979e5f880b672b524161366d", "3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", "3f8b11ba9fde9aeb56882589896cf9c7c8f4d5630f5e83abec1d80d1ef37b28b", "40f307cb9e351bf54b5cf956a85e02a42d4f881dac79ce7d0b736acb2adab0e5", "54734028b18e1d625a788d9846479ce088f10015db9ffb1abdd406d82b68b600", "5616c045d1eb934fecc0162bc2b9bd2c8935d4a3c4642c3ccd96fb1528b1f218", "5eb6dbc1191dc8a18da9d3ee4c3133909e3cfd0967d434dee958e737c1ca0bb7", "72f5f934852f4722e769ec9a4dd20d6fa206a78186bab2aadf27753a222892f6", "86ddc0f9a9062f111e70de780c5eb6d5d726f44809fafaa0af7a534ed66fc7c1", "b17f6696f920dc712a4dc5c711b1abd623d80531910e1455c70a6cb85ffb6332", "b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86", "f8dda822e63df09237acd8f88940c68c1964076e5d9a906cbf385d71ec1a4006"] +pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] +pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"] +pytest = ["6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", "a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77"] +pytest-cov = ["2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", "e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"] +pytz = ["26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", "c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"] +requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] +six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] +snowballstemmer = ["9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9"] +sphinx = ["22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", "f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427"] +sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] +sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] +sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"] +sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"] +sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"] +sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"] +toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] +tox = ["dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", "ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd"] +typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] +typing = ["38566c558a0a94d6531012c8e917b1b8518a41e418f7f15f00e129cc80162ad3", "53765ec4f83a2b720214727e319607879fec4acde22c4fbb54fa2604e79e44ce", "84698954b4e6719e912ef9a42a2431407fe3755590831699debda6fba92aac55"] +typing-extensions = ["2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", "b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", "d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"] +urllib3 = ["b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", "dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"] +virtualenv = ["6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d", "909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285"] +wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] +zipp = ["4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", "8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..776c6ab --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[tool.poetry] +name = "python-rsa" +version = "4.1-dev0" +license = "Apache-2.0" +description = "Pure-Python RSA implementation" +authors = ["Sybren A. Stüvel "] +homepage = "https://stuvel.eu/rsa" +documentation = "https://stuvel.eu/python-rsa-doc/" +repository = "https://github.com/sybrenstuvel/python-rsa/" +classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Information Technology', + 'Operating System :: OS Independent', + 'Topic :: Security :: Cryptography', +] + +packages = [ + { include="rsa", from="." }, +] + +[tool.poetry.scripts] +pyrsa-decrypt = "rsa.cli:decrypt" +pyrsa-encrypt = "rsa.cli:encrypt" +pyrsa-keygen = "rsa.cli:keygen" +pyrsa-priv2pub = "rsa.util:private_to_public" +pyrsa-sign = "rsa.cli:sign" +pyrsa-verify = "rsa.cli:verify" + + +[tool.poetry.dependencies] +python = "^3.5" +pyasn1 = ">=0.1.3" + +[tool.poetry.dev-dependencies] +coveralls = "^1.8" +Sphinx = "^2.1" +pathlib2 = {version="^2.3.4", python="~3.5"} +pytest = "^5.0" +pytest-cov = "^2.7" +tox = "^3.13" +mypy = "^0.720" + + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/tox.ini b/tox.ini index c9f1370..3cca8dc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,19 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. envlist = py35,py36,p37 +isolated_build = true [pytest] addopts = -v --cov rsa --cov-report term-missing [testenv] -deps = pipenv -commands= - pipenv install --dev --deploy - pipenv run py.test tests +whitelist_externals = poetry +commands = + poetry install -v + poetry run py.test tests/ -[testenv:py36] +[testenv:py37] +whitelist_externals = poetry commands= - pipenv install --dev --ignore-pipfile - pipenv run py.test --doctest-modules rsa tests + poetry install -v + poetry run py.test --doctest-modules rsa tests/ From 1554453a5be7157c6456dfca2b019ab599c97cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 17:27:42 +0200 Subject: [PATCH 093/142] Updated CHANGELOG --- CHANGELOG.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4b7468b..1fbc18d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,9 +5,13 @@ Version 4.1 - in development ---------------------------------------- - Dropped support for Python 2 and 3.4. -- Move to Python 3.7 in Pipfile (so `pipenv install` creates a Python 3.7 virtualenv). +- Added type annotations to the source code. This will make Python-RSA easier to use in + your IDE, and allows better type checking. +- Added static type checking via [MyPy](http://mypy-lang.org/). - Fix [#129](https://github.com/sybrenstuvel/python-rsa/issues/129) Installing from source gives UnicodeDecodeError. +- Switched to using [Poetry](https://poetry.eustace.io/) for package + management. Version 4.0 - released 2018-09-16 From d3b010a8c0cc4678e743f89597a8a9e109d28736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 17:29:01 +0200 Subject: [PATCH 094/142] Mentioned Poetry in the installation documentation --- doc/installation.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index e8f7170..3ab3ab1 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -42,9 +42,10 @@ the source:: git clone https://github.com/sybrenstuvel/python-rsa.git -Use pipenv_ to install the development requirements in a virtual environment:: +Use Poetry_ to install the development requirements in a virtual environment:: - pipenv install --dev + cd python-rsa + poetry install .. _Git: https://git-scm.com/ -.. _pipenv: https://docs.pipenv.org/ +.. _Poetry: https://poetry.eustace.io/ From fcf5b7457c70426a242b17db20dd4e34e1055f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 17:31:01 +0200 Subject: [PATCH 095/142] Removed setup.py as this is now managed via Poetry Poetry generates a setup.py as part of the building process. It's no longer necessary to keep it in the Git repo. --- doc/installation.rst | 6 ---- setup.py | 67 -------------------------------------------- 2 files changed, 73 deletions(-) delete mode 100755 setup.py diff --git a/doc/installation.rst b/doc/installation.rst index 3ab3ab1..00c46d8 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -9,12 +9,6 @@ Depending on your system you may need to use ``sudo pip`` if you want to install the library system-wide, or use ``pip install --user rsa`` to install the library in your home directory. -Installation from source is also quite easy. Download the source and -then type:: - - python setup.py install - - The sources are tracked in our `Git repository`_ at GitHub. It also hosts the `issue tracker`_. diff --git a/setup.py b/setup.py deleted file mode 100755 index cb07110..0000000 --- a/setup.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# io.open is needed for projects that support Python 2.7. It ensures open() -# defaults to text mode with universal newlines, and accepts an argument to -# specify the text encoding Python 3 only projects can skip this import. -from io import open -from setuptools import setup - -with open('README.md', encoding='utf-8') as f: - long_description = f.read() - -if __name__ == '__main__': - setup(name='rsa', - version='4.1-dev0', - description='Pure-Python RSA implementation', - long_description=long_description, - long_description_content_type='text/markdown', - author='Sybren A. Stuvel', - author_email='sybren@stuvel.eu', - maintainer='Sybren A. Stuvel', - maintainer_email='sybren@stuvel.eu', - url='https://stuvel.eu/rsa', - packages=['rsa'], - license='ASL 2', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Security :: Cryptography', - ], - install_requires=[ - 'pyasn1 >= 0.1.3', - ], - entry_points={'console_scripts': [ - 'pyrsa-priv2pub = rsa.util:private_to_public', - 'pyrsa-keygen = rsa.cli:keygen', - 'pyrsa-encrypt = rsa.cli:encrypt', - 'pyrsa-decrypt = rsa.cli:decrypt', - 'pyrsa-sign = rsa.cli:sign', - 'pyrsa-verify = rsa.cli:verify', - ]}, - - ) From b68f6181e9729afc6cae42cdf12b6a8dba52a80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 17:32:23 +0200 Subject: [PATCH 096/142] Added update_version.sh script This script updates the Python-RSA version number in various places. --- update_version.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 update_version.sh diff --git a/update_version.sh b/update_version.sh new file mode 100755 index 0000000..313b7a5 --- /dev/null +++ b/update_version.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Usage: $0 new-version" >&2 + exit 1 +fi + +DATE=$(date +'%Y-%m-%d') + +sed "s/__date__\s=\s'[^']*'/__date__ = '$DATE'/" -i rsa/__init__.py +sed "s/__version__\s=\s'[^']*'/__version__ = '$1'/" -i rsa/__init__.py + +poetry version $1 + +git diff +echo +echo "Don't forget to commit and tag:" +echo git commit -m \'Bumped version to $1\' pyproject.toml rsa/__init__.py +echo git tag -a version-$1 -m \'Tagged version $1\' From 3c5ee594a2e38b27f086d042d9d2b9d7d0d0269d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 17:43:55 +0200 Subject: [PATCH 097/142] Add support for SHA3 hashing This is based on https://github.com/sybrenstuvel/python-rsa/pull/96, with a few improvements: - The minimum of one use of SHA3 in a unit test, to at least touch it at some point. - Documented the support of SHA3. - Only install the third-party library required by Python 3.5 when we're running on Python 3.5. Newer Python versions support SHA3 natively. --- CHANGELOG.txt | 3 +++ doc/compatibility.rst | 2 +- poetry.lock | 12 +++++++++++- pyproject.toml | 1 + rsa/pkcs1.py | 14 ++++++++++++++ tests/test_pkcs1.py | 4 +++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1fbc18d..921b7bd 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -12,6 +12,9 @@ Version 4.1 - in development gives UnicodeDecodeError. - Switched to using [Poetry](https://poetry.eustace.io/) for package management. +- Added support for SHA3 hashing: SHA3-256, SHA3-384, SHA3-512. This + is natively supported by Python 3.6+ and supported via a third-party + library on Python 3.5. Version 4.0 - released 2018-09-16 diff --git a/doc/compatibility.rst b/doc/compatibility.rst index be4d295..1429553 100644 --- a/doc/compatibility.rst +++ b/doc/compatibility.rst @@ -16,7 +16,7 @@ Encryption: Signatures: PKCS#1 v1.5 using the following hash methods: - MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 + MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512 Private keys: PKCS#1 v1.5 in PEM and DER format, ASN.1 type RSAPrivateKey diff --git a/poetry.lock b/poetry.lock index 4477d26..4255772 100644 --- a/poetry.lock +++ b/poetry.lock @@ -246,6 +246,15 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "2.4.2" +[[package]] +category = "main" +description = "SHA-3 (Keccak) for Python 2.7 - 3.5" +marker = "python_version >= \"3.5\" and python_version < \"3.6\"" +name = "pysha3" +optional = false +python-versions = "*" +version = "1.0.2" + [[package]] category = "dev" description = "pytest: simple powerful testing with Python" @@ -480,7 +489,7 @@ python-versions = ">=2.7" version = "0.5.2" [metadata] -content-hash = "f64e643bf6d9b4a0e0a72c8c7d4b097f49b0a36ef2832349d7954c750b9cc70c" +content-hash = "19b0fa85c2b103b5379097a1d476f450c123dd1a0b770e4c9beca9db5644fd9a" python-versions = "^3.5" [metadata.hashes] @@ -511,6 +520,7 @@ py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639 pyasn1 = ["0c444a3482c5f5c7fab93567761324f77045ede002362171e12acdd400ea50e0", "27e919f274d96829d9c78455eacf6a2253c9fd44979e5f880b672b524161366d", "3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", "3f8b11ba9fde9aeb56882589896cf9c7c8f4d5630f5e83abec1d80d1ef37b28b", "40f307cb9e351bf54b5cf956a85e02a42d4f881dac79ce7d0b736acb2adab0e5", "54734028b18e1d625a788d9846479ce088f10015db9ffb1abdd406d82b68b600", "5616c045d1eb934fecc0162bc2b9bd2c8935d4a3c4642c3ccd96fb1528b1f218", "5eb6dbc1191dc8a18da9d3ee4c3133909e3cfd0967d434dee958e737c1ca0bb7", "72f5f934852f4722e769ec9a4dd20d6fa206a78186bab2aadf27753a222892f6", "86ddc0f9a9062f111e70de780c5eb6d5d726f44809fafaa0af7a534ed66fc7c1", "b17f6696f920dc712a4dc5c711b1abd623d80531910e1455c70a6cb85ffb6332", "b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86", "f8dda822e63df09237acd8f88940c68c1964076e5d9a906cbf385d71ec1a4006"] pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"] +pysha3 = ["0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", "11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", "386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", "41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", "4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", "571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", "59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", "5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", "684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", "68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", "6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", "827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", "93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", "9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", "9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", "c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", "c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", "cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", "f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", "fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", "fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e"] pytest = ["6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", "a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77"] pytest-cov = ["2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", "e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"] pytz = ["26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", "c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"] diff --git a/pyproject.toml b/pyproject.toml index 776c6ab..df54336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ pyrsa-verify = "rsa.cli:verify" [tool.poetry.dependencies] python = "^3.5" pyasn1 = ">=0.1.3" +pysha3 = {version="^1.0", python="~3.5"} [tool.poetry.dev-dependencies] coveralls = "^1.8" diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 39ebc49..f810771 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -30,8 +30,16 @@ import hashlib import os +import sys import typing +if sys.version_info < (3, 6): + # Python 3.6 and newer have SHA-3 support. For Python 3.5 we need a third party library. + # This library monkey-patches the hashlib module so that it looks like Python actually + # supports SHA-3 natively. + import sha3 + + from . import common, transform, core, key # ASN.1 codes that describe the hash algorithm used. @@ -42,6 +50,9 @@ 'SHA-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20', 'SHA-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30', 'SHA-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40', + '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 = { @@ -51,6 +62,9 @@ 'SHA-256': hashlib.sha256, 'SHA-384': hashlib.sha384, 'SHA-512': hashlib.sha512, + 'SHA3-256': hashlib.sha3_256, + 'SHA3-384': hashlib.sha3_384, + 'SHA3-512': hashlib.sha3_512, } diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 1704ffd..1f0d305 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -75,9 +75,11 @@ def test_sign_verify(self): message = b'je moeder' signature = pkcs1.sign(message, self.priv, 'SHA-256') - self.assertEqual('SHA-256', pkcs1.verify(message, signature, self.pub)) + signature = pkcs1.sign(message, self.priv, 'SHA3-256') + self.assertEqual('SHA3-256', pkcs1.verify(message, signature, self.pub)) + def test_find_signature_hash(self): """Test happy flow of sign and find_signature_hash""" From f2c3b4fd619a08a31fa49b11a0814e86169f4088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 18:05:23 +0200 Subject: [PATCH 098/142] Added flake8 as development dependency and fixed reported issues --- poetry.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + rsa/__init__.py | 2 +- rsa/_compat.py | 2 -- rsa/cli.py | 10 ++++----- rsa/key.py | 3 ++- rsa/pkcs1.py | 7 +++---- rsa/transform.py | 2 +- 8 files changed, 65 insertions(+), 15 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4255772..da7eac2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -95,6 +95,14 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "0.15.2" +[[package]] +category = "dev" +description = "Discover and load entry points from installed packages." +name = "entrypoints" +optional = false +python-versions = ">=2.7" +version = "0.3" + [[package]] category = "dev" description = "A platform independent file lock." @@ -103,6 +111,20 @@ optional = false python-versions = "*" version = "3.0.12" +[[package]] +category = "dev" +description = "the modular source code checker: pep8, pyflakes and co" +name = "flake8" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.7.8" + +[package.dependencies] +entrypoints = ">=0.3.0,<0.4.0" +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.5.0,<2.6.0" +pyflakes = ">=2.1.0,<2.2.0" + [[package]] category = "dev" description = "Internationalized Domain Names in Applications (IDNA)" @@ -149,6 +171,14 @@ optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" version = "1.1.1" +[[package]] +category = "dev" +description = "McCabe checker, plugin for flake8" +name = "mccabe" +optional = false +python-versions = "*" +version = "0.6.1" + [[package]] category = "dev" description = "More routines for operating on iterables, beyond itertools" @@ -230,6 +260,22 @@ optional = false python-versions = "*" version = "0.4.6" +[[package]] +category = "dev" +description = "Python style guide checker" +name = "pycodestyle" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.5.0" + +[[package]] +category = "dev" +description = "passive checker of Python programs" +name = "pyflakes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.1.1" + [[package]] category = "dev" description = "Pygments is a syntax highlighting package written in Python." @@ -489,7 +535,7 @@ python-versions = ">=2.7" version = "0.5.2" [metadata] -content-hash = "19b0fa85c2b103b5379097a1d476f450c123dd1a0b770e4c9beca9db5644fd9a" +content-hash = "d9a51bfd7b1f873f2cc17e37a64cf0e03eefcc3410f741dff2a45a01bb4a2b3b" python-versions = "^3.5" [metadata.hashes] @@ -504,12 +550,15 @@ coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", coveralls = ["9bc5a1f92682eef59f688a8f280207190d9a6afb84cef8f567fa47631a784060", "fb51cddef4bc458de347274116df15d641a735d3f0a580a9472174e2e62f408c"] docopt = ["49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"] docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] +entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"] +flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] imagesize = ["3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"] importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"] jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"] mypy-extensions = ["37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", "b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"] @@ -518,6 +567,8 @@ pathlib2 = ["2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"] py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] pyasn1 = ["0c444a3482c5f5c7fab93567761324f77045ede002362171e12acdd400ea50e0", "27e919f274d96829d9c78455eacf6a2253c9fd44979e5f880b672b524161366d", "3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", "3f8b11ba9fde9aeb56882589896cf9c7c8f4d5630f5e83abec1d80d1ef37b28b", "40f307cb9e351bf54b5cf956a85e02a42d4f881dac79ce7d0b736acb2adab0e5", "54734028b18e1d625a788d9846479ce088f10015db9ffb1abdd406d82b68b600", "5616c045d1eb934fecc0162bc2b9bd2c8935d4a3c4642c3ccd96fb1528b1f218", "5eb6dbc1191dc8a18da9d3ee4c3133909e3cfd0967d434dee958e737c1ca0bb7", "72f5f934852f4722e769ec9a4dd20d6fa206a78186bab2aadf27753a222892f6", "86ddc0f9a9062f111e70de780c5eb6d5d726f44809fafaa0af7a534ed66fc7c1", "b17f6696f920dc712a4dc5c711b1abd623d80531910e1455c70a6cb85ffb6332", "b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86", "f8dda822e63df09237acd8f88940c68c1964076e5d9a906cbf385d71ec1a4006"] +pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] +pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"] pysha3 = ["0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", "11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", "386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", "41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", "4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", "571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", "59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", "5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", "684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", "68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", "6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", "827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", "93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", "9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", "9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", "c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", "c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", "cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", "f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", "fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", "fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e"] diff --git a/pyproject.toml b/pyproject.toml index df54336..9ee2c1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ pytest = "^5.0" pytest-cov = "^2.7" tox = "^3.13" mypy = "^0.720" +flake8 = "^3.7" [build-system] diff --git a/rsa/__init__.py b/rsa/__init__.py index 41f1557..3f928d9 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -39,4 +39,4 @@ __all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey', 'PrivateKey', 'DecryptionError', 'VerificationError', - 'compute_hash', 'sign_hash'] + 'find_signature_hash', 'compute_hash', 'sign_hash'] diff --git a/rsa/_compat.py b/rsa/_compat.py index b31331e..46bf6fa 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -16,8 +16,6 @@ """Python compatibility wrappers.""" -import itertools -import sys from struct import pack diff --git a/rsa/cli.py b/rsa/cli.py index 60bc07c..b227a56 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -37,7 +37,7 @@ def keygen() -> None: # Parse the CLI options parser = optparse.OptionParser(usage='usage: %prog [options] keysize', - description='Generates a new RSA keypair of "keysize" bits.') + description='Generates a new RSA keypair of "keysize" bits.') parser.add_option('--pubout', type='string', help='Output filename for the public key. The public key is ' @@ -203,7 +203,7 @@ class EncryptOperation(CryptoOperation): operation_progressive = 'encrypting' def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, - cli_args: Indexable=()): + cli_args: Indexable = ()): """Encrypts files.""" assert isinstance(pub_key, rsa.key.PublicKey) return rsa.encrypt(indata, pub_key) @@ -221,7 +221,7 @@ class DecryptOperation(CryptoOperation): key_class = rsa.PrivateKey def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, - cli_args: Indexable=()): + cli_args: Indexable = ()): """Decrypts files.""" assert isinstance(priv_key, rsa.key.PrivateKey) return rsa.decrypt(indata, priv_key) @@ -244,7 +244,7 @@ class SignOperation(CryptoOperation): 'to stdout if this option is not present.') def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, - cli_args: Indexable): + cli_args: Indexable): """Signs files.""" assert isinstance(priv_key, rsa.key.PrivateKey) @@ -271,7 +271,7 @@ class VerifyOperation(CryptoOperation): has_output = False def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, - cli_args: Indexable): + cli_args: Indexable): """Verifies files.""" assert isinstance(pub_key, rsa.key.PublicKey) diff --git a/rsa/key.py b/rsa/key.py index 05c77ef..b4d902b 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -118,7 +118,8 @@ def load_pkcs1(cls, keyfile: bytes, format='PEM') -> 'AbstractKey': return method(keyfile) @staticmethod - def _assert_format_exists(file_format: str, methods: typing.Mapping[str, typing.Callable]) -> typing.Callable: + def _assert_format_exists(file_format: str, methods: typing.Mapping[str, typing.Callable]) \ + -> typing.Callable: """Checks whether the given file format exists in 'methods'. """ diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index f810771..6378777 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -33,14 +33,13 @@ import sys import typing +from . import common, transform, core, key + if sys.version_info < (3, 6): # Python 3.6 and newer have SHA-3 support. For Python 3.5 we need a third party library. # This library monkey-patches the hashlib module so that it looks like Python actually # supports SHA-3 natively. - import sha3 - - -from . import common, transform, core, key + import sha3 # noqa: F401 # ASN.1 codes that describe the hash algorithm used. HASH_ASN1 = { diff --git a/rsa/transform.py b/rsa/transform.py index bce9f74..4cd99bb 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -36,7 +36,7 @@ def bytes2int(raw_bytes: bytes) -> int: return int.from_bytes(raw_bytes, 'big', signed=False) -def int2bytes(number: int, fill_size: int=0) -> bytes: +def int2bytes(number: int, fill_size: int = 0) -> bytes: """ Convert an unsigned integer to bytes (big-endian):: From 2f3bbea0a5bcb69c4b3202f2bbc4bd4f51b8e476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 18:38:58 +0200 Subject: [PATCH 099/142] Bumped copyright in documentation to 2011-2019 --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 3331a86..17d51c0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,7 +46,7 @@ # General information about the project. project = u'Python-RSA' -copyright = u'2011-2018, Sybren A. Stüvel' +copyright = u'2011-2019, Sybren A. Stüvel' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From d846979e3a2c7c4d68805b9ef5f4a7e1fda61737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 18:43:14 +0200 Subject: [PATCH 100/142] Converted changelog from txt to Markdown --- CHANGELOG.txt => CHANGELOG.md | 61 +++++++++++------------------------ 1 file changed, 19 insertions(+), 42 deletions(-) rename CHANGELOG.txt => CHANGELOG.md (77%) diff --git a/CHANGELOG.txt b/CHANGELOG.md similarity index 77% rename from CHANGELOG.txt rename to CHANGELOG.md index 921b7bd..0f67b84 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ -Python-RSA changelog -======================================== +# Python-RSA changelog -Version 4.1 - in development ----------------------------------------- + +## Version 4.1 - in development - Dropped support for Python 2 and 3.4. - Added type annotations to the source code. This will make Python-RSA easier to use in @@ -17,8 +16,7 @@ Version 4.1 - in development library on Python 3.5. -Version 4.0 - released 2018-09-16 ----------------------------------------- +## Version 4.0 - released 2018-09-16 - Removed deprecated modules: - rsa.varblock @@ -40,21 +38,18 @@ Version 4.0 - released 2018-09-16 - Transitioned from `requirements.txt` to Pipenv for package management. -Version 3.4.2 - released 2016-03-29 ----------------------------------------- +## Version 3.4.2 - released 2016-03-29 - Fixed dates in CHANGELOG.txt -Version 3.4.1 - released 2016-03-26 ----------------------------------------- +## Version 3.4.1 - released 2016-03-26 - Included tests/private.pem in MANIFEST.in - Included README.md and CHANGELOG.txt in MANIFEST.in -Version 3.4 - released 2016-03-17 ----------------------------------------- +## Version 3.4 - released 2016-03-17 - Moved development to GitHub: https://github.com/sybrenstuvel/python-rsa - Solved side-channel vulnerability by implementing blinding, fixes #19 @@ -75,8 +70,7 @@ Version 3.4 - released 2016-03-17 [4] http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf -Version 3.3 - released 2016-01-13 ----------------------------------------- +## Version 3.3 - released 2016-01-13 - Thanks to Filippo Valsorda: Fix BB'06 attack in verify() by switching from parsing to comparison. See [1] for more information. @@ -87,54 +81,46 @@ Version 3.3 - released 2016-01-13 [1] https://blog.filippo.io/bleichenbacher-06-signature-forgery-in-python-rsa/ -Version 3.2.3 - released 2015-11-05 ----------------------------------------- +## Version 3.2.3 - released 2015-11-05 - Added character encoding markers for Python 2.x -Version 3.2.1 - released 2015-11-05 ----------------------------------------- +## Version 3.2.1 - released 2015-11-05 - Added per-file licenses - Added support for wheel packages - Made example code more consistent and up to date with Python 3.4 -Version 3.2 - released 2015-07-29 ----------------------------------------- +## Version 3.2 - released 2015-07-29 - Mentioned support for Python 3 in setup.py -Version 3.1.4 - released 2014-02-22 ----------------------------------------- +## Version 3.1.4 - released 2014-02-22 - Fixed some bugs -Version 3.1.3 - released 2014-02-02 ----------------------------------------- +## Version 3.1.3 - released 2014-02-02 - Dropped support for Python 2.5 -Version 3.1.2 - released 2013-09-15 ----------------------------------------- +## Version 3.1.2 - released 2013-09-15 - Added Python 3.3 to the test environment. - Removed dependency on Distribute - Added support for loading public keys from OpenSSL -Version 3.1.1 - released 2012-06-18 ----------------------------------------- +## Version 3.1.1 - released 2012-06-18 - Fixed doctests for Python 2.7 - Removed obsolete unittest so all tests run fine on Python 3.2 -Version 3.1 - released 2012-06-17 ----------------------------------------- +## Version 3.1 - released 2012-06-17 - Big, big credits to Yesudeep Mangalapilly for all the changes listed below! @@ -147,34 +133,25 @@ Version 3.1 - released 2012-06-17 -Version 3.0.1 - released 2011-08-07 ----------------------------------------- +## Version 3.0.1 - released 2011-08-07 - Removed unused import of abc module -Version 3.0 - released 2011-08-05 ----------------------------------------- +## Version 3.0 - released 2011-08-05 - Changed the meaning of the keysize to mean the size of ``n`` rather than the size of both ``p`` and ``q``. This is the common interpretation of RSA keysize. To get the old behaviour, double the keysize when generating a new key. - - Added a lot of doctests - - Added random-padded encryption and decryption using PKCS#1 version 1.5 - - Added hash-based signatures and verification using PKCS#1v1.5 - - Modeling private and public key as real objects rather than dicts. - - Support for saving and loading keys as PEM and DER files. - - Ability to extract a public key from a private key (PEM+DER) -Version 2.0 ----------------------------------------- +## Version 2.0 - Security improvements by Barry Mead. From d2e786969b9324d08358a96cf85f71d5d6e5c497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 18:44:32 +0200 Subject: [PATCH 101/142] Link changelog from README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c43464..c21ed67 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ generation according to PKCS#1 version 1.5. It can be used as a Python library as well as on the commandline. The code was mostly written by Sybren A. Stüvel. -Documentation can be found at the [Python-RSA homepage](https://stuvel.eu/rsa). +Documentation can be found at the [Python-RSA homepage](https://stuvel.eu/rsa). For all changes, check [the changelog](https://github.com/sybrenstuvel/python-rsa/blob/master/CHANGELOG.md). Download and install using: From a5d1d260852c6d846d7cff1e8d6553825d0a169d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 20:56:49 +0200 Subject: [PATCH 102/142] Configured flask8 to use max_complexity=10 Also reorganised the only function that had a higher complexity. --- rsa/pem.py | 52 +++++++++++++++++++++++++++++----------------------- setup.cfg | 1 + 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/rsa/pem.py b/rsa/pem.py index 02c7691..a50a5e8 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -35,29 +35,11 @@ def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]: b'-----END ' + pem_marker + b'-----') -def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes: - """Loads a PEM file. +def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]: + """Generator over PEM lines between pem_start and pem_end.""" - :param contents: the contents of the file to interpret - :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' - when your file has '-----BEGIN RSA PRIVATE KEY-----' and - '-----END RSA PRIVATE KEY-----' markers. - - :return: the base64-decoded content between the start and end markers. - - @raise ValueError: when the content is invalid, for example when the start - marker cannot be found. - - """ - - # We want bytes, not text. If it's text, it can be converted to ASCII bytes. - if not isinstance(contents, bytes): - contents = contents.encode('ascii') - - (pem_start, pem_end) = _markers(pem_marker) - - pem_lines = [] in_pem_part = False + seen_pem_start = False for line in contents.splitlines(): line = line.strip() @@ -72,6 +54,7 @@ def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes: raise ValueError('Seen start marker "%s" twice' % pem_start) in_pem_part = True + seen_pem_start = True continue # Skip stuff before first marker @@ -87,15 +70,38 @@ def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes: if b':' in line: continue - pem_lines.append(line) + yield line # Do some sanity checks - if not pem_lines: + if not seen_pem_start: raise ValueError('No PEM start marker "%s" found' % pem_start) if in_pem_part: raise ValueError('No PEM end marker "%s" found' % pem_end) + +def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes: + """Loads a PEM file. + + :param contents: the contents of the file to interpret + :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' + when your file has '-----BEGIN RSA PRIVATE KEY-----' and + '-----END RSA PRIVATE KEY-----' markers. + + :return: the base64-decoded content between the start and end markers. + + @raise ValueError: when the content is invalid, for example when the start + marker cannot be found. + + """ + + # We want bytes, not text. If it's text, it can be converted to ASCII bytes. + if not isinstance(contents, bytes): + contents = contents.encode('ascii') + + (pem_start, pem_end) = _markers(pem_marker) + pem_lines = [line for line in _pem_lines(contents, pem_start, pem_end)] + # Base64-decode the contents pem = b''.join(pem_lines) return base64.standard_b64decode(pem) diff --git a/setup.cfg b/setup.cfg index c5601b1..e377bdb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,7 @@ license_file = LICENSE [flake8] max-line-length = 100 +max-complexity = 10 [pep8] max-line-length = 100 From 96e13dd86b6777c86e199e9a98cd7182f1385353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 21:09:42 +0200 Subject: [PATCH 103/142] Configured CodeClimate I've overridden the default configuration in such a way that the code as it is now passes all the code smells checks. Especially the default code complexity threshold is extremely low. --- .codeclimate.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 907c00b..077382c 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -15,3 +15,13 @@ ratings: - "**.py" exclude_paths: - tests/**/* +checks: + argument-count: + config: + threshold: 5 + file-lines: + config: + threshold: 1000 + method-complexity: + config: + threshold: 17 From 1659432af4f67947a9082ed6cc90566c9f5f5f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 4 Aug 2019 21:12:15 +0200 Subject: [PATCH 104/142] Updated Code Climate badge in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c21ed67..ea24210 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Pure Python RSA implementation [![PyPI](https://img.shields.io/pypi/v/rsa.svg)](https://pypi.org/project/rsa/) [![Build Status](https://travis-ci.org/sybrenstuvel/python-rsa.svg?branch=master)](https://travis-ci.org/sybrenstuvel/python-rsa) [![Coverage Status](https://coveralls.io/repos/github/sybrenstuvel/python-rsa/badge.svg?branch=master)](https://coveralls.io/github/sybrenstuvel/python-rsa?branch=master) -[![Code Climate](https://img.shields.io/codeclimate/github/sybrenstuvel/python-rsa.svg)](https://codeclimate.com/github/sybrenstuvel/python-rsa) +[![Code Climate](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability)](https://codeclimate.com/github/codeclimate/codeclimate/maintainability) [Python-RSA](https://stuvel.eu/rsa) is a pure-Python RSA implementation. It supports encryption and decryption, signing and verifying signatures, and key From 8ed507176f09b9c162cf4f060dab8e219c6b0d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 14 Apr 2020 18:55:37 +0200 Subject: [PATCH 105/142] Choose blinding factor relatively prime to N This is a requirement for RSA blinding, but wasn't implemented yet. --- CHANGELOG.md | 1 + rsa/key.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f67b84..1aae86d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added support for SHA3 hashing: SHA3-256, SHA3-384, SHA3-512. This is natively supported by Python 3.6+ and supported via a third-party library on Python 3.5. +- Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out. ## Version 4.0 - released 2018-09-16 diff --git a/rsa/key.py b/rsa/key.py index b4d902b..7da0535 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -416,6 +416,13 @@ def __ne__(self, other: typing.Any) -> bool: def __hash__(self) -> int: return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef)) + def _get_blinding_factor(self) -> int: + for _ in range(1000): + blind_r = rsa.randnum.randint(self.n - 1) + if rsa.prime.are_relatively_prime(self.n, blind_r): + return blind_r + raise RuntimeError('unable to find blinding factor') + def blinded_decrypt(self, encrypted: int) -> int: """Decrypts the message using blinding to prevent side-channel attacks. @@ -426,7 +433,7 @@ def blinded_decrypt(self, encrypted: int) -> int: :rtype: int """ - blind_r = rsa.randnum.randint(self.n - 1) + blind_r = self._get_blinding_factor() blinded = self.blind(encrypted, blind_r) # blind before decrypting decrypted = rsa.core.decrypt_int(blinded, self.d, self.n) @@ -442,7 +449,7 @@ def blinded_encrypt(self, message: int) -> int: :rtype: int """ - blind_r = rsa.randnum.randint(self.n - 1) + blind_r = self._get_blinding_factor() blinded = self.blind(message, blind_r) # blind before encrypting encrypted = rsa.core.encrypt_int(blinded, self.d, self.n) return self.unblind(encrypted, blind_r) From 1473cb8599c44cffad56cecbe32c467d64f00247 Mon Sep 17 00:00:00 2001 From: Andrey Semakin Date: Thu, 7 Nov 2019 11:29:53 +0500 Subject: [PATCH 106/142] Drop character encoding markers for Python 2.x --- create_timing_table.py | 2 -- doc/conf.py | 2 -- rsa/__init__.py | 2 -- rsa/_compat.py | 2 -- rsa/asn1.py | 2 -- rsa/cli.py | 2 -- rsa/common.py | 2 -- rsa/core.py | 2 -- rsa/key.py | 2 -- rsa/parallel.py | 2 -- rsa/pem.py | 2 -- rsa/pkcs1.py | 2 -- rsa/pkcs1_v2.py | 2 -- rsa/prime.py | 2 -- rsa/randnum.py | 2 -- rsa/transform.py | 2 -- rsa/util.py | 2 -- tests/test_common.py | 2 -- tests/test_compat.py | 2 -- tests/test_integers.py | 2 -- tests/test_load_save_keys.py | 2 -- tests/test_pem.py | 2 -- tests/test_pkcs1.py | 2 -- tests/test_pkcs1_v2.py | 2 -- tests/test_prime.py | 2 -- tests/test_strings.py | 2 -- tests/test_transform.py | 2 -- 27 files changed, 54 deletions(-) diff --git a/create_timing_table.py b/create_timing_table.py index 6163916..498e6a0 100755 --- a/create_timing_table.py +++ b/create_timing_table.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/doc/conf.py b/doc/conf.py index 17d51c0..60201b1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Python-RSA documentation build configuration file, created by # sphinx-quickstart on Sat Jul 30 23:11:07 2011. # diff --git a/rsa/__init__.py b/rsa/__init__.py index 3f928d9..3350de7 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/_compat.py b/rsa/_compat.py index 46bf6fa..d9cd8d8 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/asn1.py b/rsa/asn1.py index b724b8f..32b1eb4 100644 --- a/rsa/asn1.py +++ b/rsa/asn1.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/cli.py b/rsa/cli.py index b227a56..2bba47f 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/common.py b/rsa/common.py index b983b98..c5b647d 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/core.py b/rsa/core.py index 42f7bac..d6e146f 100644 --- a/rsa/core.py +++ b/rsa/core.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/key.py b/rsa/key.py index 7da0535..94a14b1 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/parallel.py b/rsa/parallel.py index e81bcad..1c98442 100644 --- a/rsa/parallel.py +++ b/rsa/parallel.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/pem.py b/rsa/pem.py index a50a5e8..24edd90 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 6378777..10ee50b 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py index b751399..db94f87 100644 --- a/rsa/pkcs1_v2.py +++ b/rsa/pkcs1_v2.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/prime.py b/rsa/prime.py index dcd60dd..853aca5 100644 --- a/rsa/prime.py +++ b/rsa/prime.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/randnum.py b/rsa/randnum.py index e9bfc87..a5bb850 100644 --- a/rsa/randnum.py +++ b/rsa/randnum.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/transform.py b/rsa/transform.py index 4cd99bb..03c4a77 100644 --- a/rsa/transform.py +++ b/rsa/transform.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/rsa/util.py b/rsa/util.py index e0c7134..cb31c46 100644 --- a/rsa/util.py +++ b/rsa/util.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_common.py b/tests/test_common.py index af13695..71b81d0 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_compat.py b/tests/test_compat.py index a047402..e74f046 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_integers.py b/tests/test_integers.py index fb29ba4..2ca0a9a 100644 --- a/tests/test_integers.py +++ b/tests/test_integers.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_load_save_keys.py b/tests/test_load_save_keys.py index c0ee06c..7892fb3 100644 --- a/tests/test_load_save_keys.py +++ b/tests/test_load_save_keys.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_pem.py b/tests/test_pem.py index b9bd93c..dd03cca 100644 --- a/tests/test_pem.py +++ b/tests/test_pem.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 1f0d305..9fba2d8 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_pkcs1_v2.py b/tests/test_pkcs1_v2.py index 1d8f001..bba525e 100644 --- a/tests/test_pkcs1_v2.py +++ b/tests/test_pkcs1_v2.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_prime.py b/tests/test_prime.py index 75b80b3..5577f67 100644 --- a/tests/test_prime.py +++ b/tests/test_prime.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_strings.py b/tests/test_strings.py index 26404ae..1090a8e 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_transform.py b/tests/test_transform.py index 83c3934..7b9335e 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Copyright 2011 Sybren A. Stüvel # # Licensed under the Apache License, Version 2.0 (the "License"); From ae1a906952557f616706f79c66030fd812e48cdf Mon Sep 17 00:00:00 2001 From: Andrey Semakin Date: Mon, 4 Nov 2019 18:35:23 +0500 Subject: [PATCH 107/142] Add more type hints --- rsa/_compat.py | 2 +- rsa/cli.py | 10 +++++----- rsa/common.py | 2 +- rsa/core.py | 2 +- rsa/key.py | 28 ++++++++++++++++------------ rsa/parallel.py | 3 ++- rsa/pem.py | 6 +++--- rsa/pkcs1.py | 2 +- rsa/pkcs1_v2.py | 2 +- 9 files changed, 31 insertions(+), 26 deletions(-) diff --git a/rsa/_compat.py b/rsa/_compat.py index d9cd8d8..050e81b 100644 --- a/rsa/_compat.py +++ b/rsa/_compat.py @@ -17,7 +17,7 @@ from struct import pack -def byte(num: int): +def byte(num: int) -> bytes: """ Converts a number between 0 and 255 (both inclusive) to a base-256 (byte) representation. diff --git a/rsa/cli.py b/rsa/cli.py index 2bba47f..3166150 100644 --- a/rsa/cli.py +++ b/rsa/cli.py @@ -110,7 +110,7 @@ def __init__(self) -> None: @abc.abstractmethod def perform_operation(self, indata: bytes, key: rsa.key.AbstractKey, - cli_args: Indexable): + cli_args: Indexable) -> typing.Any: """Performs the program's operation. Implement in a subclass. @@ -201,7 +201,7 @@ class EncryptOperation(CryptoOperation): operation_progressive = 'encrypting' def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, - cli_args: Indexable = ()): + cli_args: Indexable = ()) -> bytes: """Encrypts files.""" assert isinstance(pub_key, rsa.key.PublicKey) return rsa.encrypt(indata, pub_key) @@ -219,7 +219,7 @@ class DecryptOperation(CryptoOperation): key_class = rsa.PrivateKey def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, - cli_args: Indexable = ()): + cli_args: Indexable = ()) -> bytes: """Decrypts files.""" assert isinstance(priv_key, rsa.key.PrivateKey) return rsa.decrypt(indata, priv_key) @@ -242,7 +242,7 @@ class SignOperation(CryptoOperation): 'to stdout if this option is not present.') def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, - cli_args: Indexable): + cli_args: Indexable) -> bytes: """Signs files.""" assert isinstance(priv_key, rsa.key.PrivateKey) @@ -269,7 +269,7 @@ class VerifyOperation(CryptoOperation): has_output = False def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, - cli_args: Indexable): + cli_args: Indexable) -> None: """Verifies files.""" assert isinstance(pub_key, rsa.key.PublicKey) diff --git a/rsa/common.py b/rsa/common.py index c5b647d..e7df21d 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -18,7 +18,7 @@ class NotRelativePrimeError(ValueError): - def __init__(self, a, b, d, msg=''): + def __init__(self, a: int, b: int, d: int, msg: str = '') -> None: super().__init__(msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d)) self.a = a self.b = b diff --git a/rsa/core.py b/rsa/core.py index d6e146f..23032e3 100644 --- a/rsa/core.py +++ b/rsa/core.py @@ -19,7 +19,7 @@ """ -def assert_int(var: int, name: str): +def assert_int(var: int, name: str) -> None: if isinstance(var, int): return diff --git a/rsa/key.py b/rsa/key.py index 94a14b1..b1e2030 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -94,7 +94,7 @@ def _save_pkcs1_der(self) -> bytes: """ @classmethod - def load_pkcs1(cls, keyfile: bytes, format='PEM') -> 'AbstractKey': + def load_pkcs1(cls, keyfile: bytes, format: str = 'PEM') -> 'AbstractKey': """Loads a key in PKCS#1 DER or PEM format. :param keyfile: contents of a DER- or PEM-encoded file that contains @@ -128,7 +128,7 @@ def _assert_format_exists(file_format: str, methods: typing.Mapping[str, typing. raise ValueError('Unsupported format: %r, try one of %s' % (file_format, formats)) - def save_pkcs1(self, format='PEM') -> bytes: + def save_pkcs1(self, format: str = 'PEM') -> bytes: """Saves the key in PKCS#1 DER or PEM format. :param format: the format to save; 'PEM' or 'DER' @@ -203,7 +203,7 @@ class PublicKey(AbstractKey): __slots__ = ('n', 'e') - def __getitem__(self, key): + def __getitem__(self, key: str) -> int: return getattr(self, key) def __repr__(self) -> str: @@ -378,7 +378,7 @@ def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None: self.exp2 = int(d % (q - 1)) self.coef = rsa.common.inverse(q, p) - def __getitem__(self, key): + def __getitem__(self, key: str) -> int: return getattr(self, key) def __repr__(self) -> str: @@ -388,7 +388,7 @@ def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]: """Returns the key as tuple for pickling.""" return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef - def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]): + def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]) -> None: """Sets the key from tuple.""" self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state @@ -574,7 +574,9 @@ def _save_pkcs1_pem(self) -> bytes: return rsa.pem.save_pem(der, b'RSA PRIVATE KEY') -def find_p_q(nbits: int, getprime_func=rsa.prime.getprime, accurate=True) -> typing.Tuple[int, int]: +def find_p_q(nbits: int, + getprime_func: typing.Callable[[int], int] = rsa.prime.getprime, + accurate: bool = True) -> typing.Tuple[int, int]: """Returns a tuple of two different primes of nbits bits each. The resulting p * q has exacty 2 * nbits bits, and the returned p and q @@ -619,7 +621,7 @@ def find_p_q(nbits: int, getprime_func=rsa.prime.getprime, accurate=True) -> typ log.debug('find_p_q(%i): Finding q', nbits) q = getprime_func(qbits) - def is_acceptable(p, q): + def is_acceptable(p: int, q: int) -> bool: """Returns True iff p and q are acceptable: - p and q differ @@ -697,8 +699,8 @@ def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]: def gen_keys(nbits: int, getprime_func: typing.Callable[[int], int], - accurate=True, - exponent=DEFAULT_EXPONENT) -> typing.Tuple[int, int, int, int]: + accurate: bool = True, + exponent: int = DEFAULT_EXPONENT) -> typing.Tuple[int, int, int, int]: """Generate RSA keys of nbits bits. Returns (p, q, e, d). Note: this can take a long time, depending on the key size. @@ -726,8 +728,10 @@ def gen_keys(nbits: int, return p, q, e, d -def newkeys(nbits: int, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT) \ - -> typing.Tuple[PublicKey, PrivateKey]: +def newkeys(nbits: int, + accurate: bool = True, + poolsize: int = 1, + exponent: int = DEFAULT_EXPONENT) -> typing.Tuple[PublicKey, PrivateKey]: """Generates public and private keys, and returns them as (pub, priv). The public key is also known as the 'encryption key', and is a @@ -763,7 +767,7 @@ def newkeys(nbits: int, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT) \ if poolsize > 1: from rsa import parallel - def getprime_func(nbits): + def getprime_func(nbits: int) -> int: return parallel.getprime(nbits, poolsize=poolsize) else: getprime_func = rsa.prime.getprime diff --git a/rsa/parallel.py b/rsa/parallel.py index 1c98442..f9afedb 100644 --- a/rsa/parallel.py +++ b/rsa/parallel.py @@ -23,12 +23,13 @@ """ import multiprocessing as mp +from multiprocessing.connection import Connection import rsa.prime import rsa.randnum -def _find_prime(nbits: int, pipe) -> None: +def _find_prime(nbits: int, pipe: Connection) -> None: while True: integer = rsa.randnum.read_random_odd_int(nbits) diff --git a/rsa/pem.py b/rsa/pem.py index 24edd90..1ffb446 100644 --- a/rsa/pem.py +++ b/rsa/pem.py @@ -49,7 +49,7 @@ def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iter # Handle start marker if line == pem_start: if in_pem_part: - raise ValueError('Seen start marker "%s" twice' % pem_start) + raise ValueError('Seen start marker "%r" twice' % pem_start) in_pem_part = True seen_pem_start = True @@ -72,10 +72,10 @@ def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iter # Do some sanity checks if not seen_pem_start: - raise ValueError('No PEM start marker "%s" found' % pem_start) + raise ValueError('No PEM start marker "%r" found' % pem_start) if in_pem_part: - raise ValueError('No PEM end marker "%s" found' % pem_end) + raise ValueError('No PEM end marker "%r" found' % pem_end) def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes: diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 10ee50b..8d77a97 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -157,7 +157,7 @@ def _pad_for_signing(message: bytes, target_length: int) -> bytes: message]) -def encrypt(message: bytes, pub_key: key.PublicKey): +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 diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py index db94f87..f780aff 100644 --- a/rsa/pkcs1_v2.py +++ b/rsa/pkcs1_v2.py @@ -25,7 +25,7 @@ ) -def mgf1(seed: bytes, length: int, hasher='SHA-1') -> bytes: +def mgf1(seed: bytes, length: int, hasher: str = 'SHA-1') -> bytes: """ MGF1 is a Mask Generation Function based on a hash function. From 93af6f2f89a9bf28361e67716c4240e691520f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 3 Jun 2020 14:39:23 +0200 Subject: [PATCH 108/142] Fix CVE-2020-13757: detect cyphertext modifications by prepending zero bytes Reject cyphertexts that have been modified by prepending zero bytes, by checking the cyphertext length against the expected size (given the decryption key). This resolves CVE-2020-13757. The same approach is used when verifying a signature. Thanks Carnil for pointing this out on https://github.com/sybrenstuvel/python-rsa/issues/146 --- CHANGELOG.md | 3 +++ rsa/pkcs1.py | 9 +++++++++ tests/test_pkcs1.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aae86d..8acc44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ is natively supported by Python 3.6+ and supported via a third-party library on Python 3.5. - Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out. +- Reject cyphertexts (when decrypting) and signatures (when verifying) that have + been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks + Carnil for pointing this out. ## Version 4.0 - released 2018-09-16 diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 8d77a97..408bc5b 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -245,6 +245,12 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: 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') @@ -341,6 +347,9 @@ def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str: 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') diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 9fba2d8..702ce2d 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -64,6 +64,32 @@ def test_randomness(self): self.assertNotEqual(encrypted1, encrypted2) +class ExtraZeroesTest(unittest.TestCase): + def setUp(self): + # Key, cyphertext, and plaintext taken from https://github.com/sybrenstuvel/python-rsa/issues/146 + self.private_key = rsa.PrivateKey.load_pkcs1( + "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAs1EKK81M5kTFtZSuUFnhKy8FS2WNXaWVmi/fGHG4CLw98+Yo\n0nkuUarVwSS0O9pFPcpc3kvPKOe9Tv+6DLS3Qru21aATy2PRqjqJ4CYn71OYtSwM\n/ZfSCKvrjXybzgu+sBmobdtYm+sppbdL+GEHXGd8gdQw8DDCZSR6+dPJFAzLZTCd\nB+Ctwe/RXPF+ewVdfaOGjkZIzDoYDw7n+OHnsYCYozkbTOcWHpjVevipR+IBpGPi\n1rvKgFnlcG6d/tj0hWRl/6cS7RqhjoiNEtxqoJzpXs/Kg8xbCxXbCchkf11STA8u\ndiCjQWuWI8rcDwl69XMmHJjIQAqhKvOOQ8rYTQIDAQABAoIBABpQLQ7qbHtp4h1Y\nORAfcFRW7Q74UvtH/iEHH1TF8zyM6wZsYtcn4y0mxYE3Mp+J0xlTJbeVJkwZXYVH\nL3UH29CWHSlR+TWiazTwrCTRVJDhEoqbcTiRW8fb+o/jljVxMcVDrpyYUHNo2c6w\njBxhmKPtp66hhaDpds1Cwi0A8APZ8Z2W6kya/L/hRBzMgCz7Bon1nYBMak5PQEwV\nF0dF7Wy4vIjvCzO6DSqA415DvJDzUAUucgFudbANNXo4HJwNRnBpymYIh8mHdmNJ\n/MQ0YLSqUWvOB57dh7oWQwe3UsJ37ZUorTugvxh3NJ7Tt5ZqbCQBEECb9ND63gxo\n/a3YR/0CgYEA7BJc834xCi/0YmO5suBinWOQAF7IiRPU+3G9TdhWEkSYquupg9e6\nK9lC5k0iP+t6I69NYF7+6mvXDTmv6Z01o6oV50oXaHeAk74O3UqNCbLe9tybZ/+F\ndkYlwuGSNttMQBzjCiVy0+y0+Wm3rRnFIsAtd0RlZ24aN3bFTWJINIsCgYEAwnQq\nvNmJe9SwtnH5c/yCqPhKv1cF/4jdQZSGI6/p3KYNxlQzkHZ/6uvrU5V27ov6YbX8\nvKlKfO91oJFQxUD6lpTdgAStI3GMiJBJIZNpyZ9EWNSvwUj28H34cySpbZz3s4Xd\nhiJBShgy+fKURvBQwtWmQHZJ3EGrcOI7PcwiyYcCgYEAlql5jSUCY0ALtidzQogW\nJ+B87N+RGHsBuJ/0cxQYinwg+ySAAVbSyF1WZujfbO/5+YBN362A/1dn3lbswCnH\nK/bHF9+fZNqvwprPnceQj5oK1n4g6JSZNsy6GNAhosT+uwQ0misgR8SQE4W25dDG\nkdEYsz+BgCsyrCcu8J5C+tUCgYAFVPQbC4f2ikVyKzvgz0qx4WUDTBqRACq48p6e\n+eLatv7nskVbr7QgN+nS9+Uz80ihR0Ev1yCAvnwmM/XYAskcOea87OPmdeWZlQM8\nVXNwINrZ6LMNBLgorfuTBK1UoRo1pPUHCYdqxbEYI2unak18mikd2WB7Fp3h0YI4\nVpGZnwKBgBxkAYnZv+jGI4MyEKdsQgxvROXXYOJZkWzsKuKxVkVpYP2V4nR2YMOJ\nViJQ8FUEnPq35cMDlUk4SnoqrrHIJNOvcJSCqM+bWHAioAsfByLbUPM8sm3CDdIk\nXVJl32HuKYPJOMIWfc7hIfxLRHnCN+coz2M6tgqMDs0E/OfjuqVZ\n-----END RSA PRIVATE KEY-----", + format='PEM') + self.cyphertext = bytes.fromhex( + "4501b4d669e01b9ef2dc800aa1b06d49196f5a09fe8fbcd037323c60eaf027bfb98432be4e4a26c567ffec718bcbea977dd26812fa071c33808b4d5ebb742d9879806094b6fbeea63d25ea3141733b60e31c6912106e1b758a7fe0014f075193faa8b4622bfd5d3013f0a32190a95de61a3604711bc62945f95a6522bd4dfed0a994ef185b28c281f7b5e4c8ed41176d12d9fc1b837e6a0111d0132d08a6d6f0580de0c9eed8ed105531799482d1e466c68c23b0c222af7fc12ac279bc4ff57e7b4586d209371b38c4c1035edd418dc5f960441cb21ea2bedbfea86de0d7861e81021b650a1de51002c315f1e7c12debe4dcebf790caaa54a2f26b149cf9e77d" + ) + self.plaintext = bytes.fromhex("54657374") + + def test_unmodified(self): + message = rsa.decrypt(self.cyphertext, self.private_key) + self.assertEqual(message, self.plaintext) + + def test_prepend_zeroes(self): + cyphertext = bytes.fromhex("0000") + self.cyphertext + with self.assertRaises(rsa.DecryptionError): + rsa.decrypt(cyphertext, self.private_key) + + def test_append_zeroes(self): + cyphertext = self.cyphertext + bytes.fromhex("0000") + with self.assertRaises(rsa.DecryptionError): + rsa.decrypt(cyphertext, self.private_key) + + class SignatureTest(unittest.TestCase): def setUp(self): (self.pub, self.priv) = rsa.newkeys(512) @@ -132,3 +158,21 @@ def test_hash_sign_verify(self): signature = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-224') self.assertTrue(pkcs1.verify(message, signature, self.pub)) + + def test_prepend_zeroes(self): + """Prepending the signature with zeroes should be detected.""" + + message = b'je moeder' + signature = pkcs1.sign(message, self.priv, 'SHA-256') + signature = bytes.fromhex('0000') + signature + with self.assertRaises(rsa.VerificationError): + pkcs1.verify(message, signature, self.pub) + + def test_apppend_zeroes(self): + """Apppending the signature with zeroes should be detected.""" + + message = b'je moeder' + signature = pkcs1.sign(message, self.priv, 'SHA-256') + signature = signature + bytes.fromhex('0000') + with self.assertRaises(rsa.VerificationError): + pkcs1.verify(message, signature, self.pub) From 9ecf3401108610fdce618046f055311509be03ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 3 Jun 2020 21:48:38 +0200 Subject: [PATCH 109/142] Fixed credit for report --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8acc44d..7bde63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out. - Reject cyphertexts (when decrypting) and signatures (when verifying) that have been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks - Carnil for pointing this out. + Adelapie for pointing this out. ## Version 4.0 - released 2018-09-16 From 65ab5b59f548d615d49c96f504a5279b303747c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 10 Jun 2020 10:10:14 +0200 Subject: [PATCH 110/142] Add support for Python 3.8 Supporting Python 3.8 not only required configuring Tox and Travis to run the tests on that version, but also required updating the dependencies. Without that update, `pluggy` wouldn't work properly. --- .travis.yml | 1 + CHANGELOG.md | 1 + poetry.lock | 736 +++++++++++++++++++++++++++++++++++++++------------ tox.ini | 2 +- 4 files changed, 577 insertions(+), 163 deletions(-) diff --git a/.travis.yml b/.travis.yml index d0bd7cf..42d4d16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ cache: pip python: - "3.6" - "3.7" + - "3.8" matrix: include: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bde63b..38f02e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Version 4.1 - in development +- Added support for Python 3.8. - Dropped support for Python 2 and 3.4. - Added type annotations to the source code. This will make Python-RSA easier to use in your IDE, and allows better type checking. diff --git a/poetry.lock b/poetry.lock index da7eac2..173c007 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,13 +6,22 @@ optional = false python-versions = "*" version = "0.7.12" +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.4" + [[package]] category = "dev" description = "Atomic file writes." +marker = "sys_platform == \"win32\"" name = "atomicwrites" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" +version = "1.4.0" [[package]] category = "dev" @@ -20,7 +29,13 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.1.0" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] category = "dev" @@ -28,7 +43,7 @@ description = "Internationalization utilities" name = "babel" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.7.0" +version = "2.8.0" [package.dependencies] pytz = ">=2015.7" @@ -39,7 +54,7 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2019.6.16" +version = "2020.4.5.2" [[package]] category = "dev" @@ -52,19 +67,22 @@ version = "3.0.4" [[package]] category = "dev" description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" name = "colorama" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.4.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" [[package]] category = "dev" description = "Code coverage measurement for Python" name = "coverage" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" -version = "4.5.4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "5.1" + +[package.extras] +toml = ["toml"] [[package]] category = "dev" @@ -72,13 +90,24 @@ description = "Show coverage stats online via coveralls.io" name = "coveralls" optional = false python-versions = "*" -version = "1.8.2" +version = "1.11.1" [package.dependencies] -coverage = ">=3.6,<5.0" +coverage = ">=3.6,<6.0" docopt = ">=0.6.1" requests = ">=1.0.0" +[package.extras] +yaml = ["PyYAML (>=3.10,<5.3)"] + +[[package]] +category = "dev" +description = "Distribution utilities" +name = "distlib" +optional = false +python-versions = "*" +version = "0.3.0" + [[package]] category = "dev" description = "Pythonic argument parser, that will make you smile" @@ -92,16 +121,8 @@ category = "dev" description = "Docutils -- Python Documentation Utilities" name = "docutils" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.15.2" - -[[package]] -category = "dev" -description = "Discover and load entry points from installed packages." -name = "entrypoints" -optional = false -python-versions = ">=2.7" -version = "0.3" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.16" [[package]] category = "dev" @@ -113,17 +134,20 @@ version = "3.0.12" [[package]] category = "dev" -description = "the modular source code checker: pep8, pyflakes and co" +description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.7.8" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "3.8.3" [package.dependencies] -entrypoints = ">=0.3.0,<0.4.0" mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.5.0,<2.6.0" -pyflakes = ">=2.1.0,<2.2.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" [[package]] category = "dev" @@ -131,7 +155,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8" +version = "2.9" [[package]] category = "dev" @@ -139,30 +163,59 @@ description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" +version = "1.2.0" [[package]] category = "dev" description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false -python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" -version = "0.19" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.6.1" [package.dependencies] zipp = ">=0.5" +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] + +[[package]] +category = "dev" +description = "Read resources from Python packages" +marker = "python_version < \"3.7\"" +name = "importlib-resources" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.5.0" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + +[package.dependencies.zipp] +python = "<3.8" +version = ">=0.4" + +[package.extras] +docs = ["sphinx", "rst.linker", "jaraco.packaging"] + [[package]] category = "dev" -description = "A small but fast and easy to use stand-alone template engine written in pure python." +description = "A very fast and expressive template engine." name = "jinja2" optional = false -python-versions = "*" -version = "2.10.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" +[package.extras] +i18n = ["Babel (>=0.8)"] + [[package]] category = "dev" description = "Safely add untrusted strings to HTML/XML markup." @@ -184,8 +237,8 @@ category = "dev" description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false -python-versions = ">=3.4" -version = "7.2.0" +python-versions = ">=3.5" +version = "8.3.0" [[package]] category = "dev" @@ -200,13 +253,16 @@ mypy-extensions = ">=0.4.0,<0.5.0" typed-ast = ">=1.4.0,<1.5.0" typing-extensions = ">=3.7.4" +[package.extras] +dmypy = ["psutil (>=4.0)"] + [[package]] category = "dev" description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" optional = false python-versions = "*" -version = "0.4.1" +version = "0.4.3" [[package]] category = "dev" @@ -214,10 +270,9 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.1" +version = "20.4" [package.dependencies] -attrs = "*" pyparsing = ">=2.0.2" six = "*" @@ -228,7 +283,7 @@ marker = "python_version >= \"3.5\" and python_version < \"3.6\" or python_versi name = "pathlib2" optional = false python-versions = "*" -version = "2.3.4" +version = "2.3.5" [package.dependencies] six = "*" @@ -239,10 +294,15 @@ description = "plugin and hook calling mechanisms for python" name = "pluggy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.12.0" +version = "0.13.1" [package.dependencies] -importlib-metadata = ">=0.12" +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +dev = ["pre-commit", "tox"] [[package]] category = "dev" @@ -250,7 +310,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili name = "py" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.0" +version = "1.8.1" [[package]] category = "main" @@ -258,7 +318,7 @@ description = "ASN.1 types and codecs" name = "pyasn1" optional = false python-versions = "*" -version = "0.4.6" +version = "0.4.8" [[package]] category = "dev" @@ -266,7 +326,7 @@ description = "Python style guide checker" name = "pycodestyle" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.5.0" +version = "2.6.0" [[package]] category = "dev" @@ -274,15 +334,15 @@ description = "passive checker of Python programs" name = "pyflakes" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.1.1" +version = "2.2.0" [[package]] category = "dev" description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.4.2" +python-versions = ">=3.5" +version = "2.6.1" [[package]] category = "dev" @@ -290,7 +350,7 @@ description = "Python parsing module" name = "pyparsing" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.2" +version = "2.4.7" [[package]] category = "main" @@ -307,42 +367,52 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.0.1" +version = "5.4.3" [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = "*" -importlib-metadata = ">=0.12" more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + [package.dependencies.pathlib2] python = "<3.6" version = ">=2.2.0" +[package.extras] +checkqa-mypy = ["mypy (v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + [[package]] category = "dev" description = "Pytest plugin for measuring coverage." name = "pytest-cov" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.7.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.9.0" [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" +[package.extras] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] + [[package]] category = "dev" description = "World timezone definitions, modern and historical" name = "pytz" optional = false python-versions = "*" -version = "2019.2" +version = "2020.1" [[package]] category = "dev" @@ -350,29 +420,33 @@ description = "Python HTTP for Humans." name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.22.0" +version = "2.23.0" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<3.1.0" -idna = ">=2.5,<2.9" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + [[package]] category = "dev" description = "Python 2 and 3 compatibility utilities" name = "six" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.12.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" [[package]] category = "dev" -description = "This package provides 23 stemmers for 22 languages generated from Snowball algorithms." +description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." name = "snowballstemmer" optional = false python-versions = "*" -version = "1.9.0" +version = "2.0.0" [[package]] category = "dev" @@ -380,7 +454,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.1.2" +version = "2.4.4" [package.dependencies] Jinja2 = ">=2.3" @@ -401,29 +475,45 @@ sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = "*" +[package.extras] +docs = ["sphinxcontrib-websupport"] +test = ["pytest (<5.3.3)", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] + [[package]] category = "dev" -description = "" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" name = "sphinxcontrib-applehelp" optional = false -python-versions = "*" -version = "1.0.1" +python-versions = ">=3.5" +version = "1.0.2" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." name = "sphinxcontrib-devhelp" optional = false -python-versions = "*" -version = "1.0.1" +python-versions = ">=3.5" +version = "1.0.2" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" name = "sphinxcontrib-htmlhelp" optional = false -python-versions = "*" -version = "1.0.2" +python-versions = ">=3.5" +version = "1.0.3" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] [[package]] category = "dev" @@ -433,21 +523,32 @@ optional = false python-versions = ">=3.5" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" -description = "" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." name = "sphinxcontrib-qthelp" optional = false -python-versions = "*" -version = "1.0.2" +python-versions = ">=3.5" +version = "1.0.3" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." name = "sphinxcontrib-serializinghtml" optional = false -python-versions = "*" -version = "1.1.3" +python-versions = ">=3.5" +version = "1.1.4" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" @@ -455,25 +556,33 @@ description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" optional = false python-versions = "*" -version = "0.10.0" +version = "0.10.1" [[package]] category = "dev" description = "tox is a generic virtualenv management and test command line tool" name = "tox" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.13.2" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "3.15.2" [package.dependencies] -filelock = ">=3.0.0,<4" -importlib-metadata = ">=0.12,<1" +colorama = ">=0.4.1" +filelock = ">=3.0.0" packaging = ">=14" -pluggy = ">=0.12.0,<1" -py = ">=1.4.17,<2" -six = ">=1.0.0,<2" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" toml = ">=0.9.4" -virtualenv = ">=14.0.0" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12,<2" + +[package.extras] +docs = ["sphinx (>=2.0.0)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] +testing = ["freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-xdist (>=1.22.2)", "pytest-randomly (>=1.0.0)", "flaky (>=3.4.0)", "psutil (>=5.6.1)"] [[package]] category = "dev" @@ -481,15 +590,7 @@ description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" optional = false python-versions = "*" -version = "1.4.0" - -[[package]] -category = "dev" -description = "Type Hints for Python" -name = "typing" -optional = false -python-versions = "*" -version = "3.7.4" +version = "1.4.1" [[package]] category = "dev" @@ -497,100 +598,411 @@ description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" optional = false python-versions = "*" -version = "3.7.4" - -[package.dependencies] -typing = ">=3.7.4" +version = "3.7.4.2" [[package]] category = "dev" description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.3" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.9" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] category = "dev" description = "Virtual Python Environment builder" name = "virtualenv" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "16.7.2" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.21" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.0,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12,<2" + +[package.dependencies.importlib-resources] +python = "<3.7" +version = ">=1.0,<2" + +[package.extras] +docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] +testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" +description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" optional = false python-versions = "*" -version = "0.1.7" +version = "0.2.4" [[package]] category = "dev" description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=2.7" -version = "0.5.2" +version = "1.2.0" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] content-hash = "d9a51bfd7b1f873f2cc17e37a64cf0e03eefcc3410f741dff2a45a01bb4a2b3b" python-versions = "^3.5" -[metadata.hashes] -alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] -atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] -attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] -babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] -certifi = ["046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"] -chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] -colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] -coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"] -coveralls = ["9bc5a1f92682eef59f688a8f280207190d9a6afb84cef8f567fa47631a784060", "fb51cddef4bc458de347274116df15d641a735d3f0a580a9472174e2e62f408c"] -docopt = ["49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"] -docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] -entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] -filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"] -flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] -idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] -imagesize = ["3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"] -importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"] -jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] -markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] -mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] -mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"] -mypy-extensions = ["37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", "b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"] -packaging = ["a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", "c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe"] -pathlib2 = ["2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", "446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8"] -pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"] -py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] -pyasn1 = ["0c444a3482c5f5c7fab93567761324f77045ede002362171e12acdd400ea50e0", "27e919f274d96829d9c78455eacf6a2253c9fd44979e5f880b672b524161366d", "3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", "3f8b11ba9fde9aeb56882589896cf9c7c8f4d5630f5e83abec1d80d1ef37b28b", "40f307cb9e351bf54b5cf956a85e02a42d4f881dac79ce7d0b736acb2adab0e5", "54734028b18e1d625a788d9846479ce088f10015db9ffb1abdd406d82b68b600", "5616c045d1eb934fecc0162bc2b9bd2c8935d4a3c4642c3ccd96fb1528b1f218", "5eb6dbc1191dc8a18da9d3ee4c3133909e3cfd0967d434dee958e737c1ca0bb7", "72f5f934852f4722e769ec9a4dd20d6fa206a78186bab2aadf27753a222892f6", "86ddc0f9a9062f111e70de780c5eb6d5d726f44809fafaa0af7a534ed66fc7c1", "b17f6696f920dc712a4dc5c711b1abd623d80531910e1455c70a6cb85ffb6332", "b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86", "f8dda822e63df09237acd8f88940c68c1964076e5d9a906cbf385d71ec1a4006"] -pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] -pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] -pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] -pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"] -pysha3 = ["0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", "11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", "386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", "41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", "4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", "571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", "59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", "5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", "684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", "68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", "6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", "827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", "93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", "9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", "9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", "c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", "c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", "cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", "f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", "fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", "fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e"] -pytest = ["6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", "a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77"] -pytest-cov = ["2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", "e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"] -pytz = ["26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", "c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"] -requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] -six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] -snowballstemmer = ["9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9"] -sphinx = ["22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", "f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427"] -sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] -sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] -sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"] -sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"] -sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"] -sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"] -toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] -tox = ["dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", "ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd"] -typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] -typing = ["38566c558a0a94d6531012c8e917b1b8518a41e418f7f15f00e129cc80162ad3", "53765ec4f83a2b720214727e319607879fec4acde22c4fbb54fa2604e79e44ce", "84698954b4e6719e912ef9a42a2431407fe3755590831699debda6fba92aac55"] -typing-extensions = ["2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", "b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", "d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"] -urllib3 = ["b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", "dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"] -virtualenv = ["6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d", "909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285"] -wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] -zipp = ["4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", "8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"] +[metadata.files] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +babel = [ + {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, + {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, +] +certifi = [ + {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"}, + {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +coverage = [ + {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, + {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, + {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, + {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, + {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, + {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, + {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, + {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, + {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, + {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, + {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, + {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, + {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, + {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, + {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, + {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, + {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, + {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, + {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, + {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, + {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, + {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, + {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, + {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, + {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, + {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, + {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, + {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, + {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, + {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, + {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, +] +coveralls = [ + {file = "coveralls-1.11.1-py2.py3-none-any.whl", hash = "sha256:4b6bfc2a2a77b890f556bc631e35ba1ac21193c356393b66c84465c06218e135"}, + {file = "coveralls-1.11.1.tar.gz", hash = "sha256:67188c7ec630c5f708c31552f2bcdac4580e172219897c4136504f14b823132f"}, +] +distlib = [ + {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, +] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] +docutils = [ + {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, + {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, + {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, +] +idna = [ + {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, + {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, +] +imagesize = [ + {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, + {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, + {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, +] +importlib-resources = [ + {file = "importlib_resources-1.5.0-py2.py3-none-any.whl", hash = "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49"}, + {file = "importlib_resources-1.5.0.tar.gz", hash = "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca"}, +] +jinja2 = [ + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +more-itertools = [ + {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, + {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, +] +mypy = [ + {file = "mypy-0.720-cp35-cp35m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498"}, + {file = "mypy-0.720-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0"}, + {file = "mypy-0.720-cp35-cp35m-win_amd64.whl", hash = "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a"}, + {file = "mypy-0.720-cp36-cp36m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76"}, + {file = "mypy-0.720-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba"}, + {file = "mypy-0.720-cp36-cp36m-win_amd64.whl", hash = "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4"}, + {file = "mypy-0.720-cp37-cp37m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd"}, + {file = "mypy-0.720-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339"}, + {file = "mypy-0.720-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"}, + {file = "mypy-0.720-py3-none-any.whl", hash = "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae"}, + {file = "mypy-0.720.tar.gz", hash = "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] +pathlib2 = [ + {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, + {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, + {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, +] +pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pygments = [ + {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, + {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pysha3 = [ + {file = "pysha3-1.0.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9"}, + {file = "pysha3-1.0.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf"}, + {file = "pysha3-1.0.2-cp27-cp27m-win32.whl", hash = "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b"}, + {file = "pysha3-1.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d"}, + {file = "pysha3-1.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5"}, + {file = "pysha3-1.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f"}, + {file = "pysha3-1.0.2-cp33-cp33m-win32.whl", hash = "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603"}, + {file = "pysha3-1.0.2-cp33-cp33m-win_amd64.whl", hash = "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24"}, + {file = "pysha3-1.0.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48"}, + {file = "pysha3-1.0.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f"}, + {file = "pysha3-1.0.2-cp34-cp34m-win32.whl", hash = "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608"}, + {file = "pysha3-1.0.2-cp34-cp34m-win_amd64.whl", hash = "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07"}, + {file = "pysha3-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d"}, + {file = "pysha3-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9"}, + {file = "pysha3-1.0.2-cp35-cp35m-win32.whl", hash = "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8"}, + {file = "pysha3-1.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77"}, + {file = "pysha3-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4"}, + {file = "pysha3-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030"}, + {file = "pysha3-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef"}, + {file = "pysha3-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0"}, + {file = "pysha3-1.0.2.tar.gz", hash = "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +pytest-cov = [ + {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, + {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, +] +pytz = [ + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, +] +requests = [ + {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, + {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, + {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, +] +sphinx = [ + {file = "Sphinx-2.4.4-py3-none-any.whl", hash = "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb"}, + {file = "Sphinx-2.4.4.tar.gz", hash = "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, + {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, + {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, +] +toml = [ + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, +] +tox = [ + {file = "tox-3.15.2-py2.py3-none-any.whl", hash = "sha256:50a188b8e17580c1fb931f494a754e6507d4185f54fb18aca5ba3e12d2ffd55e"}, + {file = "tox-3.15.2.tar.gz", hash = "sha256:c696d36cd7c6a28ada2da780400e44851b20ee19ef08cfe73344a1dcebbbe9f3"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, + {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, + {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, +] +urllib3 = [ + {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, + {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, +] +virtualenv = [ + {file = "virtualenv-20.0.21-py2.py3-none-any.whl", hash = "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"}, + {file = "virtualenv-20.0.21.tar.gz", hash = "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf"}, +] +wcwidth = [ + {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, + {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, +] +zipp = [ + {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, + {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, +] diff --git a/tox.ini b/tox.ini index 3cca8dc..0446ef5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py35,py36,p37 +envlist = py35,py36,p37,p38 isolated_build = true [pytest] From 80f0e9d4c05604b70e66a403a1b36c3be434d9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 10 Jun 2020 10:34:06 +0200 Subject: [PATCH 111/142] Marked version 4.1 as released --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f02e4..0b263e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Python-RSA changelog -## Version 4.1 - in development +## Version 4.1 - released 2020-06-10 - Added support for Python 3.8. - Dropped support for Python 2 and 3.4. From c6731b1dda461676b998a18004b23a9879378041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 10 Jun 2020 10:36:02 +0200 Subject: [PATCH 112/142] Bumped version to 4.1 --- pyproject.toml | 2 +- rsa/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9ee2c1d..1f85c06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-rsa" -version = "4.1-dev0" +version = "4.1" license = "Apache-2.0" description = "Pure-Python RSA implementation" authors = ["Sybren A. Stüvel "] diff --git a/rsa/__init__.py b/rsa/__init__.py index 3350de7..a0b482c 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -26,8 +26,8 @@ VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" -__date__ = '2019-08-04' -__version__ = '4.1-dev0' +__date__ = '2020-06-10' +__version__ = '4.1' # Do doctest if we're run directly if __name__ == "__main__": From 34e0b1355850bff3d11f49a46cdeb5f98d1f748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 10 Jun 2020 10:42:27 +0200 Subject: [PATCH 113/142] Bumped version to 4.2-dev0 --- pyproject.toml | 2 +- rsa/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1f85c06..a50d70e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-rsa" -version = "4.1" +version = "4.2-dev0" license = "Apache-2.0" description = "Pure-Python RSA implementation" authors = ["Sybren A. Stüvel "] diff --git a/rsa/__init__.py b/rsa/__init__.py index a0b482c..1619e33 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -27,7 +27,7 @@ __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = '2020-06-10' -__version__ = '4.1' +__version__ = '4.2-dev0' # Do doctest if we're run directly if __name__ == "__main__": From 1808d44c130eb1c2b8a06f862c0da89888e7e17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 10 Jun 2020 12:26:18 +0200 Subject: [PATCH 114/142] Fixed project name in `pyproject.toml` This resolves the issue that the files are uploaded to the wrong project on pypi.org. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1f85c06..ed54e75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "python-rsa" +name = "rsa" version = "4.1" license = "Apache-2.0" description = "Pure-Python RSA implementation" From 5c7696dd8a53b91b81346b1b04af8ef8e550707d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 11 Jun 2020 18:36:20 +0200 Subject: [PATCH 115/142] Compatibility with newer MyPy versions The newer versions always have a message, even on success. --- tests/test_mypy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mypy.py b/tests/test_mypy.py index c2a9745..8258e7e 100644 --- a/tests/test_mypy.py +++ b/tests/test_mypy.py @@ -23,5 +23,5 @@ def test_run_mypy(self): messages.append(stdout) if status: messages.append('Mypy failed with status %d' % status) - if messages: + if messages and not all('Success' in message for message in messages): self.fail('\n'.join(['Mypy errors:'] + messages)) From d15a7f3eaa90f2c48b6c4b893f4ae136a762b48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 11 Jun 2020 18:53:41 +0200 Subject: [PATCH 116/142] Moving back to Pipenv to manage dependencies Poetry breaks no-binary installations of the RSA library, which defeats the purpose of this library. See https://github.com/sybrenstuvel/python-rsa/issues/148 Among other changes, this reverts commit fcf5b7457c70426a242b17db20dd4e34e1055f69. I also added a workaround for an `ImportError` importing `zipp` on Python 3.5. --- .travis.yml | 10 +- CHANGELOG.md | 7 + Pipfile | 21 + Pipfile.lock | 551 +++++++++++++++++++++++ doc/installation.rst | 6 + poetry.lock | 1008 ------------------------------------------ pyproject.toml | 50 --- setup.py | 67 +++ tox.ini | 12 +- update_version.sh | 5 +- 10 files changed, 666 insertions(+), 1071 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 poetry.lock delete mode 100644 pyproject.toml create mode 100755 setup.py diff --git a/.travis.yml b/.travis.yml index 42d4d16..0fed68b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,17 +14,19 @@ matrix: include: - python: 3.5 dist: xenial # Bionic has no Python 3.5 + script: pip install zipp # Disabled, see https://github.com/sybrenstuvel/python-rsa/issues/131 #- python: pypy3.5 # dist: xenial # Bionic has no Python 3.5 install: - - pip install poetry - - poetry install -v + - pip install -U pip setuptools # https://github.com/pypa/virtualenv/issues/1630 + - pip install pipenv + - pipenv install --dev script: - - poetry run py.test tests/ + - pipenv run py.test tests/ after_success: - - poetry run coveralls + - pipenv run coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b263e2..43c540b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Python-RSA changelog +## Version 4.2 - in development + +- Rolled back the switch to Poetry, and reverted back to using Pipenv + setup.py + for dependency management. There apparently is an issue no-binary installs of + packages build with Poetry. This fixes + [#148](https://github.com/sybrenstuvel/python-rsa/issues/148) + ## Version 4.1 - released 2020-06-10 diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..31a1ada --- /dev/null +++ b/Pipfile @@ -0,0 +1,21 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"pyasn1" = ">=0.1.3" +"pysha3" = {version = "~=1.0, >=1.0",markers = "python_version < '3.6'"} + +[dev-packages] +coveralls = "~=1.8, >=1.8" +"Sphinx" = "~=2.1, >=2.1" +"pathlib2" = {version = "~=2.3, >=2.3.4",markers = "python_version < '3.6'"} +"pytest" = "~=5.0, >=5.0" +"pytest-cov" = "~=2.7, >=2.7" +"tox" = "~=3.13, >=3.13" +"mypy" = "~=0.720, >=0.720" +"flake8" = "~=3.7, >=3.7" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..77a2030 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,551 @@ +{ + "_meta": { + "hash": { + "sha256": "9f3ee557d56ac9a111d2af3bd82a9c8cfc3969723398e26b95c565ea868f2735" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "index": "pypi", + "version": "==0.4.8" + }, + "pysha3": { + "hashes": [ + "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", + "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", + "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", + "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", + "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", + "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", + "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", + "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", + "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", + "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", + "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", + "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", + "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", + "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", + "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", + "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", + "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", + "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", + "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", + "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", + "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e" + ], + "markers": "python_version < '3.6'", + "version": "==1.0.2" + } + }, + "develop": { + "alabaster": { + "hashes": [ + "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", + "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" + ], + "version": "==0.7.12" + }, + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "attrs": { + "hashes": [ + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==19.3.0" + }, + "babel": { + "hashes": [ + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.0" + }, + "certifi": { + "hashes": [ + "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1", + "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc" + ], + "version": "==2020.4.5.2" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "coverage": { + "hashes": [ + "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", + "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", + "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", + "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", + "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", + "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", + "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", + "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", + "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", + "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", + "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", + "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", + "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", + "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", + "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", + "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", + "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", + "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", + "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", + "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", + "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", + "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", + "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", + "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", + "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", + "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", + "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", + "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", + "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", + "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", + "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==5.1" + }, + "coveralls": { + "hashes": [ + "sha256:4b6bfc2a2a77b890f556bc631e35ba1ac21193c356393b66c84465c06218e135", + "sha256:67188c7ec630c5f708c31552f2bcdac4580e172219897c4136504f14b823132f" + ], + "index": "pypi", + "version": "==1.11.1" + }, + "distlib": { + "hashes": [ + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + ], + "version": "==0.3.0" + }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "version": "==0.6.2" + }, + "docutils": { + "hashes": [ + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.16" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, + "flake8": { + "hashes": [ + "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", + "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" + ], + "index": "pypi", + "version": "==3.8.3" + }, + "idna": { + "hashes": [ + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.9" + }, + "imagesize": { + "hashes": [ + "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", + "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.2.0" + }, + "importlib-metadata": { + "hashes": [ + "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545", + "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958" + ], + "markers": "python_version < '3.8'", + "version": "==1.6.1" + }, + "jinja2": { + "hashes": [ + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.11.2" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", + "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" + ], + "markers": "python_version >= '3.5'", + "version": "==8.3.0" + }, + "mypy": { + "hashes": [ + "sha256:00cb1964a7476e871d6108341ac9c1a857d6bd20bf5877f4773ac5e9d92cd3cd", + "sha256:127de5a9b817a03a98c5ae8a0c46a20dc44442af6dcfa2ae7f96cb519b312efa", + "sha256:1f3976a945ad7f0a0727aafdc5651c2d3278e3c88dee94e2bf75cd3386b7b2f4", + "sha256:2f8c098f12b402c19b735aec724cc9105cc1a9eea405d08814eb4b14a6fb1a41", + "sha256:4ef13b619a289aa025f2273e05e755f8049bb4eaba6d703a425de37d495d178d", + "sha256:5d142f219bf8c7894dfa79ebfb7d352c4c63a325e75f10dfb4c3db9417dcd135", + "sha256:62eb5dd4ea86bda8ce386f26684f7f26e4bfe6283c9f2b6ca6d17faf704dcfad", + "sha256:64c36eb0936d0bfb7d8da49f92c18e312ad2e3ed46e5548ae4ca997b0d33bd59", + "sha256:75eed74d2faf2759f79c5f56f17388defd2fc994222312ec54ee921e37b31ad4", + "sha256:974bebe3699b9b46278a7f076635d219183da26e1a675c1f8243a69221758273", + "sha256:a5e5bb12b7982b179af513dddb06fca12285f0316d74f3964078acbfcf4c68f2", + "sha256:d31291df31bafb997952dc0a17ebb2737f802c754aed31dd155a8bfe75112c57", + "sha256:d3b4941de44341227ece1caaf5b08b23e42ad4eeb8b603219afb11e9d4cfb437", + "sha256:eadb865126da4e3c4c95bdb47fe1bb087a3e3ea14d39a3b13224b8a4d9f9a102" + ], + "index": "pypi", + "version": "==0.780" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.4" + }, + "pathlib2": { + "hashes": [ + "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db", + "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868" + ], + "markers": "python_version < '3.6'", + "version": "==2.3.5" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.8.1" + }, + "pycodestyle": { + "hashes": [ + "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", + "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.6.0" + }, + "pyflakes": { + "hashes": [ + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.2.0" + }, + "pygments": { + "hashes": [ + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" + ], + "markers": "python_version >= '3.5'", + "version": "==2.6.1" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1", + "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8" + ], + "index": "pypi", + "version": "==5.4.3" + }, + "pytest-cov": { + "hashes": [ + "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322", + "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424" + ], + "index": "pypi", + "version": "==2.9.0" + }, + "pytz": { + "hashes": [ + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" + ], + "version": "==2020.1" + }, + "requests": { + "hashes": [ + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.23.0" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.15.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", + "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + ], + "version": "==2.0.0" + }, + "sphinx": { + "hashes": [ + "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66", + "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb" + ], + "index": "pypi", + "version": "==2.4.4" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", + "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.2" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", + "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.2" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", + "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.3" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", + "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.3" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", + "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" + ], + "markers": "python_version >= '3.5'", + "version": "==1.1.4" + }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "version": "==0.10.1" + }, + "tox": { + "hashes": [ + "sha256:50a188b8e17580c1fb931f494a754e6507d4185f54fb18aca5ba3e12d2ffd55e", + "sha256:c696d36cd7c6a28ada2da780400e44851b20ee19ef08cfe73344a1dcebbbe9f3" + ], + "index": "pypi", + "version": "==3.15.2" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "version": "==1.4.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", + "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", + "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" + ], + "version": "==3.7.4.2" + }, + "urllib3": { + "hashes": [ + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.25.9" + }, + "virtualenv": { + "hashes": [ + "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf", + "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.0.21" + }, + "wcwidth": { + "hashes": [ + "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f", + "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f" + ], + "version": "==0.2.4" + }, + "zipp": { + "hashes": [ + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" + ], + "markers": "python_version >= '3.6'", + "version": "==3.1.0" + } + } +} diff --git a/doc/installation.rst b/doc/installation.rst index 00c46d8..3ab3ab1 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -9,6 +9,12 @@ Depending on your system you may need to use ``sudo pip`` if you want to install the library system-wide, or use ``pip install --user rsa`` to install the library in your home directory. +Installation from source is also quite easy. Download the source and +then type:: + + python setup.py install + + The sources are tracked in our `Git repository`_ at GitHub. It also hosts the `issue tracker`_. diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 173c007..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1008 +0,0 @@ -[[package]] -category = "dev" -description = "A configurable sidebar-enabled Sphinx theme" -name = "alabaster" -optional = false -python-versions = "*" -version = "0.7.12" - -[[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" -optional = false -python-versions = "*" -version = "1.4.4" - -[[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" -name = "atomicwrites" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" - -[[package]] -category = "dev" -description = "Classes Without Boilerplate" -name = "attrs" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" - -[package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] - -[[package]] -category = "dev" -description = "Internationalization utilities" -name = "babel" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8.0" - -[package.dependencies] -pytz = ">=2015.7" - -[[package]] -category = "dev" -description = "Python package for providing Mozilla's CA Bundle." -name = "certifi" -optional = false -python-versions = "*" -version = "2020.4.5.2" - -[[package]] -category = "dev" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" -optional = false -python-versions = "*" -version = "3.0.4" - -[[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" - -[[package]] -category = "dev" -description = "Code coverage measurement for Python" -name = "coverage" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.1" - -[package.extras] -toml = ["toml"] - -[[package]] -category = "dev" -description = "Show coverage stats online via coveralls.io" -name = "coveralls" -optional = false -python-versions = "*" -version = "1.11.1" - -[package.dependencies] -coverage = ">=3.6,<6.0" -docopt = ">=0.6.1" -requests = ">=1.0.0" - -[package.extras] -yaml = ["PyYAML (>=3.10,<5.3)"] - -[[package]] -category = "dev" -description = "Distribution utilities" -name = "distlib" -optional = false -python-versions = "*" -version = "0.3.0" - -[[package]] -category = "dev" -description = "Pythonic argument parser, that will make you smile" -name = "docopt" -optional = false -python-versions = "*" -version = "0.6.2" - -[[package]] -category = "dev" -description = "Docutils -- Python Documentation Utilities" -name = "docutils" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.16" - -[[package]] -category = "dev" -description = "A platform independent file lock." -name = "filelock" -optional = false -python-versions = "*" -version = "3.0.12" - -[[package]] -category = "dev" -description = "the modular source code checker: pep8 pyflakes and co" -name = "flake8" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.3" - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - -[[package]] -category = "dev" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.9" - -[[package]] -category = "dev" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -name = "imagesize" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.2.0" - -[[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" -name = "importlib-metadata" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.6.1" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] - -[[package]] -category = "dev" -description = "Read resources from Python packages" -marker = "python_version < \"3.7\"" -name = "importlib-resources" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.5.0" - -[package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - -[package.dependencies.zipp] -python = "<3.8" -version = ">=0.4" - -[package.extras] -docs = ["sphinx", "rst.linker", "jaraco.packaging"] - -[[package]] -category = "dev" -description = "A very fast and expressive template engine." -name = "jinja2" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.2" - -[package.dependencies] -MarkupSafe = ">=0.23" - -[package.extras] -i18n = ["Babel (>=0.8)"] - -[[package]] -category = "dev" -description = "Safely add untrusted strings to HTML/XML markup." -name = "markupsafe" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" - -[[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" -name = "mccabe" -optional = false -python-versions = "*" -version = "0.6.1" - -[[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" -optional = false -python-versions = ">=3.5" -version = "8.3.0" - -[[package]] -category = "dev" -description = "Optional static typing for Python" -name = "mypy" -optional = false -python-versions = "*" -version = "0.720" - -[package.dependencies] -mypy-extensions = ">=0.4.0,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] - -[[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -name = "mypy-extensions" -optional = false -python-versions = "*" -version = "0.4.3" - -[[package]] -category = "dev" -description = "Core utilities for Python packages" -name = "packaging" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" - -[package.dependencies] -pyparsing = ">=2.0.2" -six = "*" - -[[package]] -category = "dev" -description = "Object-oriented filesystem paths" -marker = "python_version >= \"3.5\" and python_version < \"3.6\" or python_version < \"3.6\"" -name = "pathlib2" -optional = false -python-versions = "*" -version = "2.3.5" - -[package.dependencies] -six = "*" - -[[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" -name = "pluggy" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" - -[package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - -[package.extras] -dev = ["pre-commit", "tox"] - -[[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -name = "py" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.1" - -[[package]] -category = "main" -description = "ASN.1 types and codecs" -name = "pyasn1" -optional = false -python-versions = "*" -version = "0.4.8" - -[[package]] -category = "dev" -description = "Python style guide checker" -name = "pycodestyle" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" - -[[package]] -category = "dev" -description = "passive checker of Python programs" -name = "pyflakes" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.2.0" - -[[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." -name = "pygments" -optional = false -python-versions = ">=3.5" -version = "2.6.1" - -[[package]] -category = "dev" -description = "Python parsing module" -name = "pyparsing" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" - -[[package]] -category = "main" -description = "SHA-3 (Keccak) for Python 2.7 - 3.5" -marker = "python_version >= \"3.5\" and python_version < \"3.6\"" -name = "pysha3" -optional = false -python-versions = "*" -version = "1.0.2" - -[[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" -optional = false -python-versions = ">=3.5" -version = "5.4.3" - -[package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = "*" -more-itertools = ">=4.0.0" -packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - -[package.dependencies.pathlib2] -python = "<3.6" -version = ">=2.2.0" - -[package.extras] -checkqa-mypy = ["mypy (v0.761)"] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -category = "dev" -description = "Pytest plugin for measuring coverage." -name = "pytest-cov" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.9.0" - -[package.dependencies] -coverage = ">=4.4" -pytest = ">=3.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] - -[[package]] -category = "dev" -description = "World timezone definitions, modern and historical" -name = "pytz" -optional = false -python-versions = "*" -version = "2020.1" - -[[package]] -category = "dev" -description = "Python HTTP for Humans." -name = "requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.23.0" - -[package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" - -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - -[[package]] -category = "dev" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" - -[[package]] -category = "dev" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." -name = "snowballstemmer" -optional = false -python-versions = "*" -version = "2.0.0" - -[[package]] -category = "dev" -description = "Python documentation generator" -name = "sphinx" -optional = false -python-versions = ">=3.5" -version = "2.4.4" - -[package.dependencies] -Jinja2 = ">=2.3" -Pygments = ">=2.0" -alabaster = ">=0.7,<0.8" -babel = ">=1.3,<2.0 || >2.0" -colorama = ">=0.3.5" -docutils = ">=0.12" -imagesize = "*" -packaging = "*" -requests = ">=2.5.0" -setuptools = "*" -snowballstemmer = ">=1.1" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -test = ["pytest (<5.3.3)", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] - -[[package]] -category = "dev" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -name = "sphinxcontrib-applehelp" -optional = false -python-versions = ">=3.5" -version = "1.0.2" - -[package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest"] - -[[package]] -category = "dev" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -name = "sphinxcontrib-devhelp" -optional = false -python-versions = ">=3.5" -version = "1.0.2" - -[package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest"] - -[[package]] -category = "dev" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -name = "sphinxcontrib-htmlhelp" -optional = false -python-versions = ">=3.5" -version = "1.0.3" - -[package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest", "html5lib"] - -[[package]] -category = "dev" -description = "A sphinx extension which renders display math in HTML via JavaScript" -name = "sphinxcontrib-jsmath" -optional = false -python-versions = ">=3.5" -version = "1.0.1" - -[package.extras] -test = ["pytest", "flake8", "mypy"] - -[[package]] -category = "dev" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -name = "sphinxcontrib-qthelp" -optional = false -python-versions = ">=3.5" -version = "1.0.3" - -[package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest"] - -[[package]] -category = "dev" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -name = "sphinxcontrib-serializinghtml" -optional = false -python-versions = ">=3.5" -version = "1.1.4" - -[package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest"] - -[[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = "*" -version = "0.10.1" - -[[package]] -category = "dev" -description = "tox is a generic virtualenv management and test command line tool" -name = "tox" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.15.2" - -[package.dependencies] -colorama = ">=0.4.1" -filelock = ">=3.0.0" -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -toml = ">=0.9.4" -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12,<2" - -[package.extras] -docs = ["sphinx (>=2.0.0)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] -testing = ["freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-xdist (>=1.22.2)", "pytest-randomly (>=1.0.0)", "flaky (>=3.4.0)", "psutil (>=5.6.1)"] - -[[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.4.1" - -[[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" -name = "typing-extensions" -optional = false -python-versions = "*" -version = "3.7.4.2" - -[[package]] -category = "dev" -description = "HTTP library with thread-safe connection pooling, file post, and more." -name = "urllib3" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.9" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] - -[[package]] -category = "dev" -description = "Virtual Python Environment builder" -name = "virtualenv" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.21" - -[package.dependencies] -appdirs = ">=1.4.3,<2" -distlib = ">=0.3.0,<1" -filelock = ">=3.0.0,<4" -six = ">=1.9.0,<2" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12,<2" - -[package.dependencies.importlib-resources] -python = "<3.7" -version = ">=1.0,<2" - -[package.extras] -docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] -testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] - -[[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.4" - -[[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" -name = "zipp" -optional = false -python-versions = ">=2.7" -version = "1.2.0" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] - -[metadata] -content-hash = "d9a51bfd7b1f873f2cc17e37a64cf0e03eefcc3410f741dff2a45a01bb4a2b3b" -python-versions = "^3.5" - -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, -] -babel = [ - {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, - {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, -] -certifi = [ - {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"}, - {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -coverage = [ - {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, - {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, - {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, - {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, - {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, - {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, - {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, - {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, - {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, - {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, - {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, - {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, - {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, - {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, - {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, - {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, - {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, - {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, - {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, - {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, - {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, - {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, - {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, - {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, - {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, - {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, - {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, - {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, - {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, - {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, - {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, -] -coveralls = [ - {file = "coveralls-1.11.1-py2.py3-none-any.whl", hash = "sha256:4b6bfc2a2a77b890f556bc631e35ba1ac21193c356393b66c84465c06218e135"}, - {file = "coveralls-1.11.1.tar.gz", hash = "sha256:67188c7ec630c5f708c31552f2bcdac4580e172219897c4136504f14b823132f"}, -] -distlib = [ - {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, -] -docopt = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] -docutils = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, -] -filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, -] -flake8 = [ - {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, - {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, -] -idna = [ - {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, - {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, -] -imagesize = [ - {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, - {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, -] -importlib-metadata = [ - {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, - {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, -] -importlib-resources = [ - {file = "importlib_resources-1.5.0-py2.py3-none-any.whl", hash = "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49"}, - {file = "importlib_resources-1.5.0.tar.gz", hash = "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca"}, -] -jinja2 = [ - {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, - {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, -] -markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -more-itertools = [ - {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, - {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, -] -mypy = [ - {file = "mypy-0.720-cp35-cp35m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498"}, - {file = "mypy-0.720-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0"}, - {file = "mypy-0.720-cp35-cp35m-win_amd64.whl", hash = "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a"}, - {file = "mypy-0.720-cp36-cp36m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76"}, - {file = "mypy-0.720-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba"}, - {file = "mypy-0.720-cp36-cp36m-win_amd64.whl", hash = "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4"}, - {file = "mypy-0.720-cp37-cp37m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd"}, - {file = "mypy-0.720-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339"}, - {file = "mypy-0.720-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"}, - {file = "mypy-0.720-py3-none-any.whl", hash = "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae"}, - {file = "mypy-0.720.tar.gz", hash = "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, -] -pathlib2 = [ - {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, - {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, -] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] -py = [ - {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, - {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, -] -pyasn1 = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, - {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, - {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, -] -pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, -] -pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, -] -pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] -pysha3 = [ - {file = "pysha3-1.0.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9"}, - {file = "pysha3-1.0.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf"}, - {file = "pysha3-1.0.2-cp27-cp27m-win32.whl", hash = "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b"}, - {file = "pysha3-1.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d"}, - {file = "pysha3-1.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5"}, - {file = "pysha3-1.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f"}, - {file = "pysha3-1.0.2-cp33-cp33m-win32.whl", hash = "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603"}, - {file = "pysha3-1.0.2-cp33-cp33m-win_amd64.whl", hash = "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24"}, - {file = "pysha3-1.0.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48"}, - {file = "pysha3-1.0.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f"}, - {file = "pysha3-1.0.2-cp34-cp34m-win32.whl", hash = "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608"}, - {file = "pysha3-1.0.2-cp34-cp34m-win_amd64.whl", hash = "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07"}, - {file = "pysha3-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d"}, - {file = "pysha3-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9"}, - {file = "pysha3-1.0.2-cp35-cp35m-win32.whl", hash = "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8"}, - {file = "pysha3-1.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77"}, - {file = "pysha3-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4"}, - {file = "pysha3-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030"}, - {file = "pysha3-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef"}, - {file = "pysha3-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0"}, - {file = "pysha3-1.0.2.tar.gz", hash = "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e"}, -] -pytest = [ - {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, -] -pytest-cov = [ - {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, - {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, -] -pytz = [ - {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, - {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, -] -requests = [ - {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, - {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, -] -six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, - {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, -] -sphinx = [ - {file = "Sphinx-2.4.4-py3-none-any.whl", hash = "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb"}, - {file = "Sphinx-2.4.4.tar.gz", hash = "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, - {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, - {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, -] -toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, -] -tox = [ - {file = "tox-3.15.2-py2.py3-none-any.whl", hash = "sha256:50a188b8e17580c1fb931f494a754e6507d4185f54fb18aca5ba3e12d2ffd55e"}, - {file = "tox-3.15.2.tar.gz", hash = "sha256:c696d36cd7c6a28ada2da780400e44851b20ee19ef08cfe73344a1dcebbbe9f3"}, -] -typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, -] -typing-extensions = [ - {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, - {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, - {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, -] -urllib3 = [ - {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, - {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, -] -virtualenv = [ - {file = "virtualenv-20.0.21-py2.py3-none-any.whl", hash = "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"}, - {file = "virtualenv-20.0.21.tar.gz", hash = "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf"}, -] -wcwidth = [ - {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, - {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, -] -zipp = [ - {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, - {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, -] diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index ca337c8..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,50 +0,0 @@ -[tool.poetry] -name = "rsa" -version = "4.2-dev0" -license = "Apache-2.0" -description = "Pure-Python RSA implementation" -authors = ["Sybren A. Stüvel "] -homepage = "https://stuvel.eu/rsa" -documentation = "https://stuvel.eu/python-rsa-doc/" -repository = "https://github.com/sybrenstuvel/python-rsa/" -classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Information Technology', - 'Operating System :: OS Independent', - 'Topic :: Security :: Cryptography', -] - -packages = [ - { include="rsa", from="." }, -] - -[tool.poetry.scripts] -pyrsa-decrypt = "rsa.cli:decrypt" -pyrsa-encrypt = "rsa.cli:encrypt" -pyrsa-keygen = "rsa.cli:keygen" -pyrsa-priv2pub = "rsa.util:private_to_public" -pyrsa-sign = "rsa.cli:sign" -pyrsa-verify = "rsa.cli:verify" - - -[tool.poetry.dependencies] -python = "^3.5" -pyasn1 = ">=0.1.3" -pysha3 = {version="^1.0", python="~3.5"} - -[tool.poetry.dev-dependencies] -coveralls = "^1.8" -Sphinx = "^2.1" -pathlib2 = {version="^2.3.4", python="~3.5"} -pytest = "^5.0" -pytest-cov = "^2.7" -tox = "^3.13" -mypy = "^0.720" -flake8 = "^3.7" - - -[build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..7a6b872 --- /dev/null +++ b/setup.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright 2011 Sybren A. Stüvel +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# io.open is needed for projects that support Python 2.7. It ensures open() +# defaults to text mode with universal newlines, and accepts an argument to +# specify the text encoding Python 3 only projects can skip this import. +from io import open +from setuptools import setup + +with open('README.md', encoding='utf-8') as f: + long_description = f.read() + +if __name__ == '__main__': + setup(name='rsa', + version='4.2-dev0', + description='Pure-Python RSA implementation', + long_description=long_description, + long_description_content_type='text/markdown', + author='Sybren A. Stuvel', + author_email='sybren@stuvel.eu', + maintainer='Sybren A. Stuvel', + maintainer_email='sybren@stuvel.eu', + url='https://stuvel.eu/rsa', + packages=['rsa'], + license='ASL 2', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Security :: Cryptography', + ], + install_requires=[ + 'pyasn1 >= 0.1.3', + ], + entry_points={'console_scripts': [ + 'pyrsa-priv2pub = rsa.util:private_to_public', + 'pyrsa-keygen = rsa.cli:keygen', + 'pyrsa-encrypt = rsa.cli:encrypt', + 'pyrsa-decrypt = rsa.cli:decrypt', + 'pyrsa-sign = rsa.cli:sign', + 'pyrsa-verify = rsa.cli:verify', + ]}, + + ) diff --git a/tox.ini b/tox.ini index 0446ef5..7329d2b 100644 --- a/tox.ini +++ b/tox.ini @@ -7,13 +7,13 @@ isolated_build = true addopts = -v --cov rsa --cov-report term-missing [testenv] -whitelist_externals = poetry +deps = pipenv commands = - poetry install -v - poetry run py.test tests/ + pipenv install --dev --deploy + pipenv run py.test tests/ [testenv:py37] -whitelist_externals = poetry +whitelist_externals = pipenv commands= - poetry install -v - poetry run py.test --doctest-modules rsa tests/ + pipenv install -v + pipenv run py.test --doctest-modules rsa tests/ diff --git a/update_version.sh b/update_version.sh index 313b7a5..89c5447 100755 --- a/update_version.sh +++ b/update_version.sh @@ -9,11 +9,10 @@ DATE=$(date +'%Y-%m-%d') sed "s/__date__\s=\s'[^']*'/__date__ = '$DATE'/" -i rsa/__init__.py sed "s/__version__\s=\s'[^']*'/__version__ = '$1'/" -i rsa/__init__.py - -poetry version $1 +sed "s/version='[^']*'/version='$1'/" -i setup.py git diff echo echo "Don't forget to commit and tag:" -echo git commit -m \'Bumped version to $1\' pyproject.toml rsa/__init__.py +echo git commit -m \'Bumped version to $1\' rsa/__init__.py setup.py echo git tag -a version-$1 -m \'Tagged version $1\' From fb8772a34b9086567b4b51da5a2d62e641131828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 11 Jun 2020 19:53:29 +0200 Subject: [PATCH 117/142] Tox: fix after removal of Poetry --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7329d2b..0552a17 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. envlist = py35,py36,p37,p38 -isolated_build = true [pytest] addopts = -v --cov rsa --cov-report term-missing From 9032802c2574bc4538f8f54843fd1996aaf396e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 11 Jun 2020 20:22:01 +0200 Subject: [PATCH 118/142] Limit SHA3 support to Python 3.6+ The third-party library that adds support for this to Python 3.5 is a binary package, and thus breaks the pure-Python nature of Python-RSA. This should fix [#147](https://github.com/sybrenstuvel/python-rsa/issues/147). --- CHANGELOG.md | 4 ++++ Pipfile | 1 - Pipfile.lock | 29 +---------------------------- rsa/pkcs1.py | 27 +++++++++++++++------------ tests/test_pkcs1.py | 7 +++++++ 5 files changed, 27 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c540b..d2c39cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ for dependency management. There apparently is an issue no-binary installs of packages build with Poetry. This fixes [#148](https://github.com/sybrenstuvel/python-rsa/issues/148) +- Limited SHA3 support to those Python versions (3.6+) that support it natively. + The third-party library that adds support for this to Python 3.5 is a binary + package, and thus breaks the pure-Python nature of Python-RSA. + This should fix [#147](https://github.com/sybrenstuvel/python-rsa/issues/147). ## Version 4.1 - released 2020-06-10 diff --git a/Pipfile b/Pipfile index 31a1ada..a6f846c 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,6 @@ name = "pypi" [packages] "pyasn1" = ">=0.1.3" -"pysha3" = {version = "~=1.0, >=1.0",markers = "python_version < '3.6'"} [dev-packages] coveralls = "~=1.8, >=1.8" diff --git a/Pipfile.lock b/Pipfile.lock index 77a2030..702edae 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9f3ee557d56ac9a111d2af3bd82a9c8cfc3969723398e26b95c565ea868f2735" + "sha256": "4df253faa2a1f6d6665fddc4c13f5e278a4127c27d7b76e59607a8154f96b1ab" }, "pipfile-spec": 6, "requires": { @@ -34,33 +34,6 @@ ], "index": "pypi", "version": "==0.4.8" - }, - "pysha3": { - "hashes": [ - "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", - "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", - "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", - "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", - "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", - "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", - "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", - "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", - "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", - "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", - "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", - "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", - "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", - "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", - "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", - "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", - "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", - "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", - "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", - "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", - "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e" - ], - "markers": "python_version < '3.6'", - "version": "==1.0.2" } }, "develop": { diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 408bc5b..57b0276 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -33,12 +33,6 @@ from . import common, transform, core, key -if sys.version_info < (3, 6): - # Python 3.6 and newer have SHA-3 support. For Python 3.5 we need a third party library. - # This library monkey-patches the hashlib module so that it looks like Python actually - # supports SHA-3 natively. - import sha3 # noqa: F401 - # ASN.1 codes that describe the hash algorithm used. HASH_ASN1 = { 'MD5': b'\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10', @@ -47,9 +41,6 @@ 'SHA-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20', 'SHA-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30', 'SHA-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40', - '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 = { @@ -59,12 +50,24 @@ 'SHA-256': hashlib.sha256, 'SHA-384': hashlib.sha384, 'SHA-512': hashlib.sha512, - 'SHA3-256': hashlib.sha3_256, - 'SHA3-384': hashlib.sha3_384, - 'SHA3-512': hashlib.sha3_512, } +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.""" diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index 702ce2d..f7baf7f 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -15,6 +15,7 @@ """Tests string operations.""" import struct +import sys import unittest import rsa @@ -101,6 +102,12 @@ def test_sign_verify(self): signature = pkcs1.sign(message, self.priv, 'SHA-256') self.assertEqual('SHA-256', pkcs1.verify(message, signature, self.pub)) + + @unittest.skipIf(sys.version_info < (3, 6), "SHA3 requires Python 3.6+") + def test_sign_verify_sha3(self): + """Test happy flow of sign and verify with SHA3-256""" + + message = b'je moeder' signature = pkcs1.sign(message, self.priv, 'SHA3-256') self.assertEqual('SHA3-256', pkcs1.verify(message, signature, self.pub)) From c59236793ea4b5b248629de20b3e304be819e49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 11 Jun 2020 20:26:09 +0200 Subject: [PATCH 119/142] Bumped version to 4.2 --- rsa/__init__.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 1619e33..dc5e148 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -26,8 +26,8 @@ VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" -__date__ = '2020-06-10' -__version__ = '4.2-dev0' +__date__ = '2020-06-11' +__version__ = '4.2' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 7a6b872..ac31da6 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.2-dev0', + version='4.2', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From f4bd4d481a29c49cc96f208d0a092e3cd13aa438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 19:41:36 +0200 Subject: [PATCH 120/142] Updated CHANGELOG Note that version 4.3 will not appear on the master branch, but is available in the version-4.3-py27compatible branch only. --- CHANGELOG.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c39cc..aabecb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ # Python-RSA changelog -## Version 4.2 - in development +## Version 4.4 - released 2020-06-12 + +Version 4.3 is almost a re-tagged release of version 4.0. It requires Python +3.5+. To avoid older Python installations from trying to upgrade to RSA 4.4, +this is now made explicit in the `python_requires` argument in `setup.py`. + +No functional changes compared to version 4.2. + + +## Version 4.3 - released 2020-06-12 + +Version 4.3 is almost a re-tagged release of version 4.0. It is the last to +support Python 2.7. This is now made explicit in the `python_requires` argument +in `setup.py`. Python 3.4 is not supported by this release. + +Two security fixes have also been backported, so 4.3 = 4.0 + these two fixes. + +- Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out. +- Reject cyphertexts (when decrypting) and signatures (when verifying) that have + been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks + Carnil for pointing this out. + + +## Version 4.2 - released 2020-06-10 - Rolled back the switch to Poetry, and reverted back to using Pipenv + setup.py for dependency management. There apparently is an issue no-binary installs of From 199544691ff0b34bd42e5c693f2cf0a651424307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 19:41:40 +0200 Subject: [PATCH 121/142] Bumped version to 4.4 --- rsa/__init__.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index dc5e148..0cdbbc1 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -26,8 +26,8 @@ VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" -__date__ = '2020-06-11' -__version__ = '4.2' +__date__ = '2020-06-12' +__version__ = '4.4' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index ac31da6..8693427 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.2', + version='4.4', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From e7c0b2a65f5f5022d4bffbd55a333d94fc5ce18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 19:52:51 +0200 Subject: [PATCH 122/142] Explicitly declare Python 3.8 as supported --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 8693427..a10bfbe 100755 --- a/setup.py +++ b/setup.py @@ -48,10 +48,12 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Security :: Cryptography', ], + python_requires='>=3.5, <4', install_requires=[ 'pyasn1 >= 0.1.3', ], From 82316704c3b5c28a69a42653e0988560d0673dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 19:53:05 +0200 Subject: [PATCH 123/142] Bumped version to 4.4.1 --- rsa/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 0cdbbc1..12523d8 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -27,7 +27,7 @@ __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = '2020-06-12' -__version__ = '4.4' +__version__ = '4.4.1' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index a10bfbe..57db59d 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.4', + version='4.4.1', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From 9a70b0014f2b80328f274213df78ca90d554c28b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 22:20:18 +0200 Subject: [PATCH 124/142] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aabecb9..736f487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Python-RSA changelog + +## Version 4.4.1 - released 2020-06-12 + +This makes Python 3.8 support explicit in `setup.py`. + + ## Version 4.4 - released 2020-06-12 Version 4.3 is almost a re-tagged release of version 4.0. It requires Python From cedefea7b455375e5a87200866809be59d718968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 22:24:49 +0200 Subject: [PATCH 125/142] Retagged 4.4 as 4.6 and added bit of an explanation to CHANGELOG.md --- CHANGELOG.md | 23 +++++++++++------------ rsa/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 736f487..0fa3054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,24 @@ # Python-RSA changelog -## Version 4.4.1 - released 2020-06-12 +## Version 4.4 & 4.6 - released 2020-06-12 -This makes Python 3.8 support explicit in `setup.py`. - - -## Version 4.4 - released 2020-06-12 - -Version 4.3 is almost a re-tagged release of version 4.0. It requires Python -3.5+. To avoid older Python installations from trying to upgrade to RSA 4.4, -this is now made explicit in the `python_requires` argument in `setup.py`. +Version 4.4 and 4.6 are almost a re-tagged release of version 4.2. It requires +Python 3.5+. To avoid older Python installations from trying to upgrade to RSA +4.4, this is now made explicit in the `python_requires` argument in `setup.py`. +There was a mistake releasing 4.4 as "3.5+ only", which made it necessary to +retag 4.4 as 4.6 as well. No functional changes compared to version 4.2. ## Version 4.3 - released 2020-06-12 -Version 4.3 is almost a re-tagged release of version 4.0. It is the last to -support Python 2.7. This is now made explicit in the `python_requires` argument -in `setup.py`. Python 3.4 is not supported by this release. +Version 4.3 and 4.5 are almost a re-tagged release of version 4.0. It is the +last to support Python 2.7. This is now made explicit in the `python_requires` +argument in `setup.py`. Python 3.4 is not supported by this release. There was a +mistake releasing 4.4 as "3.5+ only", which made it necessary to retag 4.3 as +4.5 as well. Two security fixes have also been backported, so 4.3 = 4.0 + these two fixes. diff --git a/rsa/__init__.py b/rsa/__init__.py index 12523d8..1567dc1 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -27,7 +27,7 @@ __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = '2020-06-12' -__version__ = '4.4.1' +__version__ = '4.6' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 57db59d..2d22865 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.4.1', + version='4.6', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From b5e17047bcd72616c28407609d48e7436a05cd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 22:32:28 +0200 Subject: [PATCH 126/142] Updated documentation to use Pipenv instead of Poetry See commit d15a7f3 for the reason why. --- doc/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index 3ab3ab1..73f56e5 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -42,10 +42,10 @@ the source:: git clone https://github.com/sybrenstuvel/python-rsa.git -Use Poetry_ to install the development requirements in a virtual environment:: +Use Pipenv_ to install the development requirements in a virtual environment:: cd python-rsa - poetry install + pipenv install --dev .. _Git: https://git-scm.com/ -.. _Poetry: https://poetry.eustace.io/ +.. _Pipenv: https://pipenv.pypa.io/en/latest/ From ec57ac07131f24156b348fda3c502ccdc9e4735b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jun 2020 22:33:12 +0200 Subject: [PATCH 127/142] Bumped version to 4.7-dev0 --- rsa/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 1567dc1..0a77e35 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -27,7 +27,7 @@ __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" __date__ = '2020-06-12' -__version__ = '4.6' +__version__ = '4.7-dev0' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 2d22865..bdc552a 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.6', + version='4.7-dev0', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown', From a87a564ca20cedb713853fc8e09df0dcad228722 Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Sun, 14 Jun 2020 14:39:28 +0300 Subject: [PATCH 128/142] Fix exception cause in common.py --- rsa/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa/common.py b/rsa/common.py index e7df21d..b5a966a 100644 --- a/rsa/common.py +++ b/rsa/common.py @@ -49,8 +49,8 @@ def bit_size(num: int) -> int: try: return num.bit_length() - except AttributeError: - raise TypeError('bit_size(num) only supports integers, not %r' % type(num)) + except AttributeError as ex: + raise TypeError('bit_size(num) only supports integers, not %r' % type(num)) from ex def byte_size(number: int) -> int: From e8fc5d280ac5c8b339536be63f947273f43f8777 Mon Sep 17 00:00:00 2001 From: Wyatt Anderson Date: Mon, 15 Jun 2020 11:56:37 -0400 Subject: [PATCH 129/142] Don't build universal wheels --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index e377bdb..4c5e567 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [metadata] license_file = LICENSE From 4beb68d597b14807b1dcfcdb18d1b6ffa6487892 Mon Sep 17 00:00:00 2001 From: tvalentyn Date: Mon, 15 Jun 2020 14:16:31 -0700 Subject: [PATCH 130/142] Adds mention of 4.5 version in the headers. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa3054..bc41f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ retag 4.4 as 4.6 as well. No functional changes compared to version 4.2. -## Version 4.3 - released 2020-06-12 +## Version 4.3 & 4.5 - released 2020-06-12 Version 4.3 and 4.5 are almost a re-tagged release of version 4.0. It is the last to support Python 2.7. This is now made explicit in the `python_requires` From da6fc2cb6a663d1e1d3e59ee99a4653f8b6272aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 26 Oct 2020 15:34:01 +0100 Subject: [PATCH 131/142] Added security note to README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ea24210..875c7f6 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ or download it from the [Python Package Index](https://pypi.org/project/rsa/). The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python-rsa/) and is licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +Security +-------- + +Because of how Python internally stores numbers, it is very hard (if not impossible) to make a pure-Python program secure against timing attacks. This library is no exception, so use it with care. + + Major changes in 4.1 -------------------- From 6f59ff07a317409fe68696935daf8549b1555c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Mon, 26 Oct 2020 15:36:20 +0100 Subject: [PATCH 132/142] Add URL with more info to timing security issues --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 875c7f6..2684060 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ licensed under the [Apache License, version 2.0](https://www.apache.org/licenses Security -------- -Because of how Python internally stores numbers, it is very hard (if not impossible) to make a pure-Python program secure against timing attacks. This library is no exception, so use it with care. +Because of how Python internally stores numbers, it is very hard (if not impossible) to make a pure-Python program secure against timing attacks. This library is no exception, so use it with care. See https://securitypitfalls.wordpress.com/2018/08/03/constant-time-compare-in-python/ for more info. Major changes in 4.1 From dae8ce0d85478e16f2368b2341632775313d41ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 15 Nov 2020 15:18:38 +0100 Subject: [PATCH 133/142] Fix #165: CVE-2020-25658 - Bleichenbacher-style timing oracle Use as many constant-time comparisons as practical in the `rsa.pkcs1.decrypt` function. `cleartext.index(b'\x00', 2)` will still be non-constant-time. The alternative would be to iterate over all the data byte by byte in Python, which is several orders of magnitude slower. Given that a perfect constant-time implementation is very hard or even impossible to do in Python [1], I chose the more performant option here. [1]: https://securitypitfalls.wordpress.com/2018/08/03/constant-time-compare-in-python/ --- CHANGELOG.md | 5 +++++ rsa/pkcs1.py | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc41f70..1838377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Python-RSA changelog +## Version 4.7 - in development + +- Fix #165: CVE-2020-25658 - Bleichenbacher-style timing oracle in PKCS#1 v1.5 + decryption code + ## Version 4.4 & 4.6 - released 2020-06-12 diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 57b0276..19e24c7 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -30,6 +30,7 @@ import os import sys import typing +from hmac import compare_digest from . import common, transform, core, key @@ -251,17 +252,20 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: # 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') + crypto_len_bad = len(crypto) > blocksize # If we can't find the cleartext marker, decryption failed. - if cleartext[0:2] != b'\x00\x02': - raise DecryptionError('Decryption failed') + cleartext_marker_bad = not compare_digest(cleartext[:2], b'\x00\x02') # Find the 00 separator between the padding and the message try: sep_idx = cleartext.index(b'\x00', 2) except ValueError: + sep_idx = -1 + sep_idx_bad = sep_idx < 0 + + anything_bad = crypto_len_bad | cleartext_marker_bad | sep_idx_bad + if anything_bad: raise DecryptionError('Decryption failed') return cleartext[sep_idx + 1:] From f878c374086e672e7806fdd18401ec6b71cfa960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 15 Nov 2020 15:48:27 +0100 Subject: [PATCH 134/142] Fix #164: Add padding length check as described by PKCS#1 v1.5 According to PKCS#1 v1.5, the padding should be at least 8 bytes long. See https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3 for more info. --- CHANGELOG.md | 2 ++ rsa/pkcs1.py | 7 ++++++- tests/test_pkcs1.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1838377..77ad5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Fix #165: CVE-2020-25658 - Bleichenbacher-style timing oracle in PKCS#1 v1.5 decryption code +- Add padding length check as described by PKCS#1 v1.5 (Fixes + [#164](https://github.com/sybrenstuvel/python-rsa/issues/164)) ## Version 4.4 & 4.6 - released 2020-06-12 diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 19e24c7..8a85b1c 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -262,7 +262,12 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: sep_idx = cleartext.index(b'\x00', 2) except ValueError: sep_idx = -1 - sep_idx_bad = sep_idx < 0 + # sep_idx indicates the position of the `\x00` separator that separates the + # padding from the actual message. The padding should be at least 8 bytes + # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which + # means the separator should be at least at index 10 (because of the + # `\x00\x02` marker that preceeds it). + sep_idx_bad = sep_idx < 10 anything_bad = crypto_len_bad | cleartext_marker_bad | sep_idx_bad if anything_bad: diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py index f7baf7f..64fb0c5 100644 --- a/tests/test_pkcs1.py +++ b/tests/test_pkcs1.py @@ -183,3 +183,36 @@ def test_apppend_zeroes(self): signature = signature + bytes.fromhex('0000') with self.assertRaises(rsa.VerificationError): pkcs1.verify(message, signature, self.pub) + + +class PaddingSizeTest(unittest.TestCase): + def test_too_little_padding(self): + """Padding less than 8 bytes should be rejected.""" + + # Construct key that will be small enough to need only 7 bytes of padding. + # This key is 168 bit long, and was generated with rsa.newkeys(nbits=168). + self.private_key = rsa.PrivateKey.load_pkcs1(b''' +-----BEGIN RSA PRIVATE KEY----- +MHkCAQACFgCIGbbNSkIRLtprxka9NgOf5UxgxCMCAwEAAQIVQqymO0gHubdEVS68 +CdCiWmOJxVfRAgwBQM+e1JJwMKmxSF0CCmya6CFxO8Evdn8CDACMM3AlVC4FhlN8 +3QIKC9cjoam/swMirwIMAR7Br9tdouoH7jAE +-----END RSA PRIVATE KEY----- + ''') + self.public_key = rsa.PublicKey(n=self.private_key.n, e=self.private_key.e) + + cyphertext = self.encrypt_with_short_padding(b'op je hoofd') + with self.assertRaises(rsa.DecryptionError): + rsa.decrypt(cyphertext, self.private_key) + + def encrypt_with_short_padding(self, message: bytes) -> bytes: + # This is a copy of rsa.pkcs1.encrypt() adjusted to use the wrong padding length. + keylength = rsa.common.byte_size(self.public_key.n) + + # The word 'padding' has 7 letters, so is one byte short of a valid padding length. + padded = b'\x00\x02padding\x00' + message + + payload = rsa.transform.bytes2int(padded) + encrypted_value = rsa.core.encrypt_int(payload, self.public_key.e, self.public_key.n) + cyphertext = rsa.transform.int2bytes(encrypted_value, keylength) + + return cyphertext From 240b0d8910299f970921391ea9737cb64ec09208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 15 Nov 2020 15:49:12 +0100 Subject: [PATCH 135/142] Add link to changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ad5cd..f61b3c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## Version 4.7 - in development -- Fix #165: CVE-2020-25658 - Bleichenbacher-style timing oracle in PKCS#1 v1.5 - decryption code +- Fix [#165](https://github.com/sybrenstuvel/python-rsa/issues/165]: + CVE-2020-25658 - Bleichenbacher-style timing oracle in PKCS#1 v1.5 decryption + code - Add padding length check as described by PKCS#1 v1.5 (Fixes [#164](https://github.com/sybrenstuvel/python-rsa/issues/164)) From f254895b02f0cb106f9ccee6d8dc6af1a27f0bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 15 Nov 2020 16:18:18 +0100 Subject: [PATCH 136/142] Use `bytes.find()` instead of `bytes.index()` Use `bytes.find()` instead of `bytes.index()`, as the former doesn't raise an exception when the to-be-found byte doesn't exist. --- rsa/pkcs1.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 8a85b1c..d0149a1 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -258,10 +258,8 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: cleartext_marker_bad = not compare_digest(cleartext[:2], b'\x00\x02') # Find the 00 separator between the padding and the message - try: - sep_idx = cleartext.index(b'\x00', 2) - except ValueError: - sep_idx = -1 + sep_idx = cleartext.find(b'\x00', 2) + # sep_idx indicates the position of the `\x00` separator that separates the # padding from the actual message. The padding should be at least 8 bytes # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which From 341e5c4f939988bd472530441b6a02b625a30806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 15 Nov 2020 16:23:19 +0100 Subject: [PATCH 137/142] Directly raise `DecryptionError` when crypto length is bad Crypto length and blocksize are public info, so don't need side-channel free comparison. --- rsa/pkcs1.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index d0149a1..07cf85b 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -252,7 +252,9 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: # 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. - crypto_len_bad = len(crypto) > blocksize + if len(crypto) > blocksize: + # This is operating on public information, so doesn't need to be constant-time. + raise DecryptionError('Decryption failed') # If we can't find the cleartext marker, decryption failed. cleartext_marker_bad = not compare_digest(cleartext[:2], b'\x00\x02') @@ -267,7 +269,7 @@ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes: # `\x00\x02` marker that preceeds it). sep_idx_bad = sep_idx < 10 - anything_bad = crypto_len_bad | cleartext_marker_bad | sep_idx_bad + anything_bad = cleartext_marker_bad | sep_idx_bad if anything_bad: raise DecryptionError('Decryption failed') From 06ec1ea1cc7be6034144bd06f07c35eb9d1b4953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 15 Nov 2020 16:25:51 +0100 Subject: [PATCH 138/142] Fix #162: Blinding uses slow algorithm Store blinding factor + its inverse, so that they can be reused & updated on every blinding operation. This avoids expensive computations. The reuse of the previous blinding factor is done via squaring (mod n), as per section 9 of 'A Timing Attack against RSA with the Chinese Remainder Theorem' by Werner Schindler, https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf --- CHANGELOG.md | 2 ++ rsa/key.py | 52 +++++++++++++++++++++++++++++------------------ tests/test_key.py | 17 ++++++++++++---- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f61b3c4..fe1ab28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ code - Add padding length check as described by PKCS#1 v1.5 (Fixes [#164](https://github.com/sybrenstuvel/python-rsa/issues/164)) +- Reuse of blinding factors to speed up blinding operations. + Fixes [#162](https://github.com/sybrenstuvel/python-rsa/issues/162). ## Version 4.4 & 4.6 - released 2020-06-12 diff --git a/rsa/key.py b/rsa/key.py index b1e2030..e0e7b11 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -49,12 +49,15 @@ class AbstractKey: """Abstract superclass for private and public keys.""" - __slots__ = ('n', 'e') + __slots__ = ('n', 'e', 'blindfac', 'blindfac_inverse') def __init__(self, n: int, e: int) -> None: self.n = n self.e = e + # These will be computed properly on the first call to blind(). + self.blindfac = self.blindfac_inverse = -1 + @classmethod def _load_pkcs1_pem(cls, keyfile: bytes) -> 'AbstractKey': """Loads a key in PKCS#1 PEM format, implement in a subclass. @@ -145,7 +148,7 @@ def save_pkcs1(self, format: str = 'PEM') -> bytes: method = self._assert_format_exists(format, methods) return method() - def blind(self, message: int, r: int) -> int: + def blind(self, message: int) -> int: """Performs blinding on the message using random number 'r'. :param message: the message, as integer, to blind. @@ -159,10 +162,10 @@ def blind(self, message: int, r: int) -> int: See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29 """ + self._update_blinding_factor() + return (message * pow(self.blindfac, self.e, self.n)) % self.n - return (message * pow(r, self.e, self.n)) % self.n - - def unblind(self, blinded: int, r: int) -> int: + def unblind(self, blinded: int) -> int: """Performs blinding on the message using random number 'r'. :param blinded: the blinded message, as integer, to unblind. @@ -174,8 +177,27 @@ def unblind(self, blinded: int, r: int) -> int: See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29 """ - return (rsa.common.inverse(r, self.n) * blinded) % self.n + return (self.blindfac_inverse * blinded) % self.n + def _initial_blinding_factor(self) -> int: + for _ in range(1000): + blind_r = rsa.randnum.randint(self.n - 1) + if rsa.prime.are_relatively_prime(self.n, blind_r): + return blind_r + raise RuntimeError('unable to find blinding factor') + + def _update_blinding_factor(self): + if self.blindfac < 0: + # Compute initial blinding factor, which is rather slow to do. + self.blindfac = self._initial_blinding_factor() + self.blindfac_inverse = rsa.common.inverse(self.blindfac, self.n) + else: + # Reuse previous blinding factor as per section 9 of 'A Timing + # Attack against RSA with the Chinese Remainder Theorem' by Werner + # Schindler. + # See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf + self.blindfac = pow(self.blindfac, 2, self.n) + self.blindfac_inverse = pow(self.blindfac_inverse, 2, self.n) class PublicKey(AbstractKey): """Represents a public RSA key. @@ -414,13 +436,6 @@ def __ne__(self, other: typing.Any) -> bool: def __hash__(self) -> int: return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef)) - def _get_blinding_factor(self) -> int: - for _ in range(1000): - blind_r = rsa.randnum.randint(self.n - 1) - if rsa.prime.are_relatively_prime(self.n, blind_r): - return blind_r - raise RuntimeError('unable to find blinding factor') - def blinded_decrypt(self, encrypted: int) -> int: """Decrypts the message using blinding to prevent side-channel attacks. @@ -431,11 +446,9 @@ def blinded_decrypt(self, encrypted: int) -> int: :rtype: int """ - blind_r = self._get_blinding_factor() - blinded = self.blind(encrypted, blind_r) # blind before decrypting + blinded = self.blind(encrypted) # blind before decrypting decrypted = rsa.core.decrypt_int(blinded, self.d, self.n) - - return self.unblind(decrypted, blind_r) + return self.unblind(decrypted) def blinded_encrypt(self, message: int) -> int: """Encrypts the message using blinding to prevent side-channel attacks. @@ -447,10 +460,9 @@ def blinded_encrypt(self, message: int) -> int: :rtype: int """ - blind_r = self._get_blinding_factor() - blinded = self.blind(message, blind_r) # blind before encrypting + blinded = self.blind(message) # blind before encrypting encrypted = rsa.core.encrypt_int(blinded, self.d, self.n) - return self.unblind(encrypted, blind_r) + return self.unblind(encrypted) @classmethod def _load_pkcs1_der(cls, keyfile: bytes) -> 'PrivateKey': diff --git a/tests/test_key.py b/tests/test_key.py index 9db30ce..b00e26d 100644 --- a/tests/test_key.py +++ b/tests/test_key.py @@ -21,11 +21,20 @@ def test_blinding(self): message = 12345 encrypted = rsa.core.encrypt_int(message, pk.e, pk.n) - blinded = pk.blind(encrypted, 4134431) # blind before decrypting - decrypted = rsa.core.decrypt_int(blinded, pk.d, pk.n) - unblinded = pk.unblind(decrypted, 4134431) + blinded_1 = pk.blind(encrypted) # blind before decrypting + decrypted = rsa.core.decrypt_int(blinded_1, pk.d, pk.n) + unblinded_1 = pk.unblind(decrypted) - self.assertEqual(unblinded, message) + self.assertEqual(unblinded_1, message) + + # Re-blinding should use a different blinding factor. + blinded_2 = pk.blind(encrypted) # blind before decrypting + self.assertNotEqual(blinded_1, blinded_2) + + # The unblinding should still work, though. + decrypted = rsa.core.decrypt_int(blinded_2, pk.d, pk.n) + unblinded_2 = pk.unblind(decrypted) + self.assertEqual(unblinded_2, message) class KeyGenTest(unittest.TestCase): From b81e3171e9f870892e6b8a894db6d00ecd93f544 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 19 Nov 2020 23:40:38 +0200 Subject: [PATCH 139/142] Declare support for and test Python 3.9 --- .travis.yml | 1 + CHANGELOG.md | 1 + setup.py | 1 + tox.ini | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0fed68b..ff7329c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "3.6" - "3.7" - "3.8" + - "3.9" matrix: include: diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1ab28..826c3d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ [#164](https://github.com/sybrenstuvel/python-rsa/issues/164)) - Reuse of blinding factors to speed up blinding operations. Fixes [#162](https://github.com/sybrenstuvel/python-rsa/issues/162). +- Declare & test support for Python 3.9 ## Version 4.4 & 4.6 - released 2020-06-12 diff --git a/setup.py b/setup.py index bdc552a..5f15130 100755 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Security :: Cryptography', diff --git a/tox.ini b/tox.ini index 0552a17..1a033ae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Environment changes have to be manually synced with '.travis.yml'. -envlist = py35,py36,p37,p38 +envlist = py35,py36,p37,p38,p39 [pytest] addopts = -v --cov rsa --cov-report term-missing From 539c54aada3922757099ce1911a28c46e8e81a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 10 Jan 2021 11:32:52 +0100 Subject: [PATCH 140/142] Fix #170: mistake in examples of documentation Strings need to be encoded into bytes before the RSA module can operate on them. --- doc/usage.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/usage.rst b/doc/usage.rst index b1244d4..f76765e 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -170,7 +170,7 @@ You can create a detached signature for a message using the :py:func:`rsa.sign` function: >>> (pubkey, privkey) = rsa.newkeys(512) - >>> message = 'Go left at the blue tree' + >>> message = 'Go left at the blue tree'.encode() >>> signature = rsa.sign(message, privkey, 'SHA-1') This hashes the message using SHA-1. Other hash methods are also @@ -182,21 +182,21 @@ It is possible to calculate the hash and signature in separate operations private key on remote server). To hash a message use the :py:func:`rsa.compute_hash` function and then use the :py:func:`rsa.sign_hash` function to sign the hash: - >>> message = 'Go left at the blue tree' + >>> message = 'Go left at the blue tree'.encode() >>> hash = rsa.compute_hash(message, 'SHA-1') >>> signature = rsa.sign_hash(hash, privkey, 'SHA-1') In order to verify the signature, use the :py:func:`rsa.verify` function. This function returns True if the verification is successful: - >>> message = 'Go left at the blue tree' + >>> message = 'Go left at the blue tree'.encode() >>> rsa.verify(message, signature, pubkey) True Modify the message, and the signature is no longer valid and a :py:class:`rsa.pkcs1.VerificationError` is thrown: - >>> message = 'Go right at the blue tree' + >>> message = 'Go right at the blue tree'.encode() >>> rsa.verify(message, signature, pubkey) Traceback (most recent call last): File "", line 1, in From a364e82caa4cb1fc51400a8c628fb48867fb4362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 10 Jan 2021 11:35:18 +0100 Subject: [PATCH 141/142] Marked version 4.7 as released --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826c3d9..3552260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Python-RSA changelog -## Version 4.7 - in development +## Version 4.7 - released 2021-01-10 - Fix [#165](https://github.com/sybrenstuvel/python-rsa/issues/165]: CVE-2020-25658 - Bleichenbacher-style timing oracle in PKCS#1 v1.5 decryption From fa3282a47457254385f2313c2eceaad4b06186a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Sun, 10 Jan 2021 11:35:51 +0100 Subject: [PATCH 142/142] Bumped version to 4.7 --- rsa/__init__.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rsa/__init__.py b/rsa/__init__.py index 0a77e35..26b28ca 100644 --- a/rsa/__init__.py +++ b/rsa/__init__.py @@ -26,8 +26,8 @@ VerificationError, find_signature_hash, sign_hash, compute_hash __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" -__date__ = '2020-06-12' -__version__ = '4.7-dev0' +__date__ = '2021-01-10' +__version__ = '4.7' # Do doctest if we're run directly if __name__ == "__main__": diff --git a/setup.py b/setup.py index 5f15130..b983b1f 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ if __name__ == '__main__': setup(name='rsa', - version='4.7-dev0', + version='4.7', description='Pure-Python RSA implementation', long_description=long_description, long_description_content_type='text/markdown',