diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..21c125c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +.py text eol=lf +.rst text eol=lf +.txt text eol=lf +.yaml text eol=lf +.toml text eol=lf +.license text eol=lf +.md text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6996f9c..ff19dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,42 +1,21 @@ -# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: Unlicense repos: - - repo: https://github.com/python/black - rev: 22.3.0 - hooks: - - id: black - - repo: https://github.com/fsfe/reuse-tool - rev: v0.14.0 - hooks: - - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/pylint - rev: v2.15.5 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string,duplicate-code - exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint - name: pylint (example code) - description: Run pylint rules on "examples/*.py" files - types: [python] - files: "^examples/" - args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint - name: pylint (test code) - description: Run pylint rules on "tests/*.py" files - types: [python] - files: "^tests/" - args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - id: ruff-format + - id: ruff + args: ["--fix"] + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 + hooks: + - id: reuse diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f945e92..0000000 --- a/.pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the ignore-list. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint.extensions.no_self_use - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call -disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=board - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=12 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/README.rst b/README.rst index 7925cf9..f0604ed 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_RSA/actions/ :alt: Build Status -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Code Style: Ruff RSA implementation based on `Sybren A. Stüvel's python-rsa `_ pure-python RSA implementation. diff --git a/adafruit_rsa/__init__.py b/adafruit_rsa/__init__.py index ddd8bf7..ff46b68 100755 --- a/adafruit_rsa/__init__.py +++ b/adafruit_rsa/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -15,17 +14,17 @@ """ -from adafruit_rsa.key import newkeys, PrivateKey, PublicKey +from adafruit_rsa.key import PrivateKey, PublicKey, newkeys from adafruit_rsa.pkcs1 import ( - encrypt, - decrypt, - sign, - verify, DecryptionError, VerificationError, + compute_hash, + decrypt, + encrypt, find_signature_hash, + sign, sign_hash, - compute_hash, + verify, ) __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly" diff --git a/adafruit_rsa/_compat.py b/adafruit_rsa/_compat.py index 4652e38..fb8770a 100755 --- a/adafruit_rsa/_compat.py +++ b/adafruit_rsa/_compat.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 diff --git a/adafruit_rsa/asn1.py b/adafruit_rsa/asn1.py index 5dd8e65..02e9d33 100755 --- a/adafruit_rsa/asn1.py +++ b/adafruit_rsa/asn1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -12,9 +11,8 @@ Not all ASN.1-handling code use these definitions, but when it does, they should be here. """ -# pylint: disable=no-name-in-module, too-few-public-methods try: - from pyasn1.type import univ, namedtype, tag + from pyasn1.type import namedtype, tag, univ except ImportError as err: raise ImportError("Usage of asn1.py requires pyasn1 library") from err @@ -39,9 +37,7 @@ class OpenSSLPubKey(univ.Sequence): # This little hack (the implicit tag) allows us to get a Bit String as Octet String namedtype.NamedType( "key", - univ.OctetString().subtype( - implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3) - ), + univ.OctetString().subtype(implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3)), ), ) diff --git a/adafruit_rsa/common.py b/adafruit_rsa/common.py index a46863e..4255a94 100755 --- a/adafruit_rsa/common.py +++ b/adafruit_rsa/common.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -10,10 +9,8 @@ Common functionality shared by several modules. """ -# pylint: disable=invalid-name - try: - from typing import Optional, Tuple, Sequence + from typing import Optional, Sequence, Tuple except ImportError: pass @@ -39,9 +36,7 @@ class NotRelativePrimeError(ValueError): """Raises if provided a and b not relatively prime.""" def __init__(self, a: int, b: int, d: int, msg: Optional[str] = None): - super().__init__( - msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d) - ) + 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 @@ -72,9 +67,7 @@ def bit_size(num: int) -> int: try: return bit_length(num) except AttributeError as err: - raise TypeError( - "bit_size(num) only supports integers, not %r" % type(num) - ) from err + raise TypeError("bit_size(num) only supports integers, not %r" % type(num)) from err def byte_size(number: int) -> int: @@ -190,7 +183,7 @@ def crt(a_values: Sequence[int], modulo_values: Sequence[int]) -> int: for modulo in modulo_values: m *= modulo - for (m_i, a_i) in zip(modulo_values, a_values): + for m_i, a_i in zip(modulo_values, a_values): M_i = m // m_i inv = inverse(M_i, m_i) diff --git a/adafruit_rsa/core.py b/adafruit_rsa/core.py index 4336b05..23b0e99 100755 --- a/adafruit_rsa/core.py +++ b/adafruit_rsa/core.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -13,7 +12,6 @@ mathematically on integers. """ -# pylint: disable=invalid-name from adafruit_rsa._compat import is_integer try: @@ -50,7 +48,7 @@ def assert_int(var: Any, name: str) -> None: if is_integer(var): return - raise TypeError("%s should be an integer, not %s" % (name, var.__class__)) + raise TypeError(f"{name} should be an integer, not {var.__class__}") def encrypt_int(message: int, ekey: int, n: int) -> int: diff --git a/adafruit_rsa/key.py b/adafruit_rsa/key.py index 3cc96aa..f059003 100755 --- a/adafruit_rsa/key.py +++ b/adafruit_rsa/key.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -28,14 +27,14 @@ import adafruit_logging as logging -import adafruit_rsa.prime -import adafruit_rsa.pem import adafruit_rsa.common -import adafruit_rsa.randnum import adafruit_rsa.core +import adafruit_rsa.pem +import adafruit_rsa.prime +import adafruit_rsa.randnum try: - from typing import Any, Tuple, Dict, Callable + from typing import Any, Callable, Dict, Tuple try: from typing import Literal @@ -47,7 +46,6 @@ __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RSA.git" -# pylint: disable=invalid-name, useless-object-inheritance, redefined-builtin, no-name-in-module, too-few-public-methods log = logging.getLogger(__name__) log.addHandler(logging.StreamHandler()) log.setLevel(logging.INFO) @@ -55,7 +53,7 @@ DEFAULT_EXPONENT = 65537 -class AbstractKey(object): +class AbstractKey: """Abstract superclass for private and public keys.""" def __init__(self, n: int, e: int) -> None: @@ -97,9 +95,7 @@ def _save_pkcs1_der(self) -> bytes: """ @classmethod - def load_pkcs1( - cls, keyfile: bytes, format: Literal["PEM", "DER"] = "PEM" - ) -> "AbstractKey": + def load_pkcs1(cls, keyfile: bytes, format: Literal["PEM", "DER"] = "PEM") -> "AbstractKey": """Loads a key in PKCS#1 DER or PEM format. :param bytes keyfile: contents of a DER- or PEM-encoded file that @@ -108,9 +104,7 @@ def load_pkcs1( :return: the loaded key :rtype: AbstractKey """ - raise NotImplementedError( - "Loading PEM Files not supported by this CircuitPython library." - ) + raise NotImplementedError("Loading PEM Files not supported by this CircuitPython library.") # methods = { # 'PEM': cls._load_pkcs1_pem, @@ -130,9 +124,7 @@ def _assert_format_exists( return methods[file_format] except KeyError as err: formats = ", ".join(sorted(methods.keys())) - raise ValueError( - "Unsupported format: %r, try one of %s" % (file_format, formats) - ) from err + raise ValueError(f"Unsupported format: {file_format!r}, try one of {formats}") from err def save_pkcs1(self, format: Literal["PEM", "DER"] = "PEM") -> bytes: """Saves the key in PKCS#1 DER or PEM format. @@ -181,7 +173,6 @@ def unblind(self, blinded: int, r: int) -> int: return (adafruit_rsa.common.inverse(r, self.n) * blinded) % self.n -# pylint: disable=abstract-method class PublicKey(AbstractKey): """Represents a public RSA key. @@ -257,10 +248,9 @@ def _load_pkcs1_der(cls, keyfile: bytes) -> "PublicKey": PublicKey(2367317549, 65537) """ - # pylint: disable=import-outside-toplevel try: - from adafruit_rsa.tools.pyasn1.codec.der import decoder from adafruit_rsa.asn1 import AsnPubKey + from adafruit_rsa.tools.pyasn1.codec.der import decoder except ImportError as err: raise ImportError("This functionality requires the pyasn1 library") from err @@ -273,7 +263,6 @@ def _save_pkcs1_der(self) -> bytes: :return: the DER-encoded public key. :rtype: bytes """ - # pylint: disable=import-outside-toplevel try: from pyasn1.codec.der import encoder except ImportError as err: @@ -345,11 +334,11 @@ def load_pkcs1_openssl_der(cls, keyfile: bytes) -> "PublicKey": public key, from OpenSSL. :return: a PublicKey object """ - # pylint: disable=import-outside-toplevel try: - from adafruit_rsa.asn1 import OpenSSLPubKey from pyasn1.codec.der import decoder from pyasn1.type import univ + + from adafruit_rsa.asn1 import OpenSSLPubKey except ImportError as err: raise ImportError("This functionality requires the pyasn1 library") from err @@ -387,7 +376,6 @@ class PrivateKey(AbstractKey): __slots__ = ("n", "e", "d", "p", "q", "exp1", "exp2", "coef") - # pylint: disable=too-many-arguments def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None: AbstractKey.__init__(self, n, e) self.d = d @@ -415,9 +403,7 @@ def __getstate__(self) -> 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: Tuple[int, int, int, int, int, int, int, int] - ) -> None: + def __setstate__(self, state: 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 @@ -443,9 +429,7 @@ def __ne__(self, other: Any) -> bool: return not self == other def __hash__(self) -> int: - return hash( - (self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef) - ) + return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef)) def blinded_decrypt(self, encrypted: int) -> int: """Decrypts the message using blinding to prevent side-channel attacks. @@ -497,7 +481,7 @@ def _load_pkcs1_der(cls, keyfile: bytes) -> "PrivateKey": """ try: - from adafruit_rsa.tools.pyasn1.codec.der import ( # pylint: disable=import-outside-toplevel + from adafruit_rsa.tools.pyasn1.codec.der import ( decoder, ) except ImportError as err: @@ -542,10 +526,9 @@ def _save_pkcs1_der(self) -> bytes: :return: the DER-encoded private key. :rtype: bytes """ - # pylint: disable=import-outside-toplevel try: - from pyasn1.type import univ, namedtype from pyasn1.codec.der import encoder + from pyasn1.type import namedtype, univ except ImportError as err: raise ImportError("This functionality requires the pyasn1 library") from err @@ -712,8 +695,7 @@ def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> Tuple[int, if (exponent * d) % phi_n != 1: raise ValueError( - "e (%d) and d (%d) are not mult. inv. modulo " - "phi_n (%d)" % (exponent, d, phi_n) + "e (%d) and d (%d) are not mult. inv. modulo " "phi_n (%d)" % (exponent, d, phi_n) ) return exponent, d diff --git a/adafruit_rsa/machine_size.py b/adafruit_rsa/machine_size.py index cfbfc32..ac9317c 100755 --- a/adafruit_rsa/machine_size.py +++ b/adafruit_rsa/machine_size.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 diff --git a/adafruit_rsa/pem.py b/adafruit_rsa/pem.py index f32c6cb..99d9cf7 100755 --- a/adafruit_rsa/pem.py +++ b/adafruit_rsa/pem.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -12,11 +11,10 @@ from adafruit_binascii import a2b_base64, b2a_base64 -# pylint: disable=redefined-builtin from adafruit_rsa._compat import is_bytes try: - from typing import Union, Tuple + from typing import Tuple, Union except ImportError: pass diff --git a/adafruit_rsa/pkcs1.py b/adafruit_rsa/pkcs1.py index 508bd25..28db849 100755 --- a/adafruit_rsa/pkcs1.py +++ b/adafruit_rsa/pkcs1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -22,12 +21,15 @@ """ import os + import adafruit_hashlib as hashlib -from adafruit_rsa import common, transform, core + +from adafruit_rsa import common, core, transform try: - from typing import Optional, Iterator, Union - from adafruit_rsa.key import PublicKey, PrivateKey + from typing import Iterator, Optional, Union + + from adafruit_rsa.key import PrivateKey, PublicKey try: from typing import Protocol @@ -301,9 +303,7 @@ def sign_hash( return block -def sign( - message: Union[bytes, _FileLikeObject], priv_key: PrivateKey, hash_method: str -) -> bytes: +def sign(message: Union[bytes, _FileLikeObject], priv_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 @@ -326,9 +326,7 @@ def sign( return sign_hash(msg_hash, priv_key, hash_method) -def verify( - message: Union[bytes, _FileLikeObject], signature: bytes, pub_key: PublicKey -) -> str: +def verify(message: Union[bytes, _FileLikeObject], signature: bytes, pub_key: PublicKey) -> str: """Verifies that the signature matches the message. The hash method is detected automatically from the signature. @@ -385,9 +383,7 @@ def find_signature_hash(signature: bytes, pub_key: PublicKey) -> str: return _find_method_hash(clearsig) -def yield_fixedblocks( - infile: _FileLikeObject, blocksize: int -) -> Iterator[Union[bytes, str]]: +def yield_fixedblocks(infile: _FileLikeObject, blocksize: int) -> Iterator[Union[bytes, str]]: """Generator, yields each block of ``blocksize`` bytes in the input file. :param TextIOWrapper infile: file to read and separate in blocks. @@ -408,9 +404,7 @@ def yield_fixedblocks( break -def compute_hash( - message: Union[bytes, str, _FileLikeObject], method_name: str -) -> bytes: +def compute_hash(message: Union[bytes, str, _FileLikeObject], method_name: str) -> bytes: """Returns the message digest. :param message: the signed message. Can be an 8-bit string or a file-like @@ -445,7 +439,7 @@ def _find_method_hash(clearsig: bytes) -> str: :raise VerificationFailed: when the hash method cannot be found """ - for (hashname, asn1code) in HASH_ASN1.items(): + for hashname, asn1code in HASH_ASN1.items(): if asn1code in clearsig: return hashname diff --git a/adafruit_rsa/prime.py b/adafruit_rsa/prime.py index 26a9696..bf8aacf 100755 --- a/adafruit_rsa/prime.py +++ b/adafruit_rsa/prime.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -12,7 +11,7 @@ Implementation based on the book Algorithm Design by Michael T. Goodrich and Roberto Tamassia, 2002. """ -# pylint: disable=invalid-name + import adafruit_rsa.common import adafruit_rsa.randnum @@ -102,7 +101,7 @@ def miller_rabin_primality_testing(n: int, k: int) -> bool: x = adafruit_rsa.core.fast_pow(a, d, n) - if x in (1, n - 1): + if x in {1, n - 1}: continue for _ in range(r - 1): diff --git a/adafruit_rsa/randnum.py b/adafruit_rsa/randnum.py index 4d5beb3..3614c98 100755 --- a/adafruit_rsa/randnum.py +++ b/adafruit_rsa/randnum.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 diff --git a/adafruit_rsa/transform.py b/adafruit_rsa/transform.py index 5bede8f..c655607 100755 --- a/adafruit_rsa/transform.py +++ b/adafruit_rsa/transform.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2011 Sybren A. Stüvel # # SPDX-License-Identifier: Apache-2.0 @@ -15,10 +14,11 @@ # from __future__ import absolute_import from struct import pack + import adafruit_binascii as binascii -from adafruit_rsa._compat import byte, is_integer from adafruit_rsa import common, machine_size +from adafruit_rsa._compat import byte, is_integer try: from typing import Optional @@ -75,9 +75,7 @@ def _int2bytes(number: int, block_size: Optional[int] = None) -> bytes: # Type checking if not is_integer(number): - raise TypeError( - "You must pass an integer for 'number', not %s" % number.__class__ - ) + 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) @@ -94,8 +92,7 @@ def _int2bytes(number: int, block_size: Optional[int] = None) -> bytes: 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) + "Needed %i bytes for number, but block size " "is %i" % (needed_bytes, block_size) ) # Convert the number to bytes. @@ -213,5 +210,5 @@ def int2bytes( remainder = length % chunk_size if remainder: padding_size = chunk_size - remainder - raw_bytes = "% {}s".format(length + padding_size).encode() % raw_bytes + raw_bytes = f"% {length + padding_size}s".encode() % raw_bytes return raw_bytes diff --git a/docs/api.rst b/docs/api.rst index 280d2f1..b6f0821 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,10 +1,11 @@ -Adafruit CircuitPython RSA API -=============================== .. If you created a package, create one automodule per module in the package. .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" +API Reference +############# + .. automodule:: adafruit_rsa :members: diff --git a/docs/conf.py b/docs/conf.py index 547070d..bf106cc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # # SPDX-License-Identifier: MIT +import datetime import os import sys -import datetime sys.path.insert(0, os.path.abspath("..")) @@ -48,9 +46,7 @@ creation_year = "2019" current_year = str(datetime.datetime.now().year) year_duration = ( - current_year - if current_year == creation_year - else creation_year + " - " + current_year + current_year if current_year == creation_year else creation_year + " - " + current_year ) copyright = year_duration + " Brent Rubell" author = "Brent Rubell" diff --git a/examples/rsa_generate_json_keys.py b/examples/rsa_generate_json_keys.py index 588fbe7..0f2c898 100644 --- a/examples/rsa_generate_json_keys.py +++ b/examples/rsa_generate_json_keys.py @@ -5,9 +5,10 @@ You can copy the JSON from serial console and paste it into a new file on the device and then use it with the rsa_json_keys.py example. """ + import json -import adafruit_rsa +import adafruit_rsa # Create a keypair print("Generating keypair...") diff --git a/examples/rsa_json_keys.py b/examples/rsa_json_keys.py index 02e843d..6c2adf8 100644 --- a/examples/rsa_json_keys.py +++ b/examples/rsa_json_keys.py @@ -2,8 +2,9 @@ # SPDX-License-Identifier: MIT import binascii import json + import adafruit_rsa -from adafruit_rsa import PublicKey, PrivateKey +from adafruit_rsa import PrivateKey, PublicKey """ CircuitPython microcontrollers cannot load PEM key files generated by OpenSSL @@ -13,11 +14,11 @@ # load a keypair from JSON files -with open("keys/example512key.json", "r") as f: +with open("keys/example512key.json") as f: priv_key_obj = json.loads(f.read()) -with open("keys/example512key_pub.json", "r") as f: +with open("keys/example512key_pub.json") as f: pub_key_obj = json.loads(f.read()) diff --git a/examples/rsa_sign_verify.py b/examples/rsa_sign_verify.py index b08a493..d9a57a6 100755 --- a/examples/rsa_sign_verify.py +++ b/examples/rsa_sign_verify.py @@ -21,6 +21,4 @@ # Verify Message Signature if adafruit_rsa.verify(message, signature, public_key) != hash_method: - raise ValueError( - "Verification failed - signature does not match secret message sent!" - ) + raise ValueError("Verification failed - signature does not match secret message sent!") diff --git a/examples/rsa_tests.py b/examples/rsa_tests.py index 49345af..7863c8c 100755 --- a/examples/rsa_tests.py +++ b/examples/rsa_tests.py @@ -1,16 +1,17 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -"""Adafruit RSA Tests -""" +"""Adafruit RSA Tests""" + import time + import adafruit_rsa def test_encrypt_decrypt(): # Generate general purpose keys (pub, priv) = adafruit_rsa.newkeys(256, log_level="DEBUG") - msg = "blinka".encode("utf-8") + msg = b"blinka" msg_enc = adafruit_rsa.encrypt(msg, pub) msg_dec = adafruit_rsa.decrypt(msg_enc, priv) assert msg == msg_dec, "Decrypted message does not match original message" @@ -20,7 +21,7 @@ def test_mod_msg(): """Modifies an enecrypted message, asserts failure""" # Generate general purpose keys (pub, priv) = adafruit_rsa.newkeys(256, log_level="DEBUG") - msg = "blinka".encode("utf-8") + msg = b"blinka" msg_enc = adafruit_rsa.encrypt(msg, pub) msg_enc = msg_enc[:-1] + b"X" # change the last byte try: @@ -30,12 +31,11 @@ def test_mod_msg(): pass -# pylint: disable=unused-variable def test_randomness(): """Encrypt msg 2x yields diff. encrypted values.""" # Generate general purpose keys (pub, priv) = adafruit_rsa.newkeys(256, log_level="DEBUG") - msg = "blinka".encode("utf-8") + msg = b"blinka" msg_enc_1 = adafruit_rsa.encrypt(msg, pub) msg_enc_2 = adafruit_rsa.encrypt(msg, pub) assert msg_enc_1 != msg_enc_2, "Messages should yield different values." @@ -95,9 +95,7 @@ def test_sign_verify_fail(): start_time = time.monotonic() for test_name in all_tests: # for i in range(0, len(all_tests)): - print("Testing: {}".format(test_name)) + print(f"Testing: {test_name}") test_name() print("OK!") -print( - "Ran {} tests in {} seconds".format(len(all_tests), time.monotonic() - start_time) -) +print(f"Ran {len(all_tests)} tests in {time.monotonic() - start_time} seconds") diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..a9c2ec9 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +target-version = "py38" +line-length = 100 + +[lint] +preview = true +select = ["I", "PL", "UP"] + +extend-select = [ + "D419", # empty-docstring + "E501", # line-too-long + "W291", # trailing-whitespace + "PLC0414", # useless-import-alias + "PLC2401", # non-ascii-name + "PLC2801", # unnecessary-dunder-call + "PLC3002", # unnecessary-direct-lambda-call + "E999", # syntax-error + "PLE0101", # return-in-init + "F706", # return-outside-function + "F704", # yield-outside-function + "PLE0116", # continue-in-finally + "PLE0117", # nonlocal-without-binding + "PLE0241", # duplicate-bases + "PLE0302", # unexpected-special-method-signature + "PLE0604", # invalid-all-object + "PLE0605", # invalid-all-format + "PLE0643", # potential-index-error + "PLE0704", # misplaced-bare-raise + "PLE1141", # dict-iter-missing-items + "PLE1142", # await-outside-async + "PLE1205", # logging-too-many-args + "PLE1206", # logging-too-few-args + "PLE1307", # bad-string-format-type + "PLE1310", # bad-str-strip-call + "PLE1507", # invalid-envvar-value + "PLE2502", # bidirectional-unicode + "PLE2510", # invalid-character-backspace + "PLE2512", # invalid-character-sub + "PLE2513", # invalid-character-esc + "PLE2514", # invalid-character-nul + "PLE2515", # invalid-character-zero-width-space + "PLR0124", # comparison-with-itself + "PLR0202", # no-classmethod-decorator + "PLR0203", # no-staticmethod-decorator + "UP004", # useless-object-inheritance + "PLR0206", # property-with-parameters + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0916", # too-many-boolean-expressions + "PLR1702", # too-many-nested-blocks + "PLR1704", # redefined-argument-from-local + "PLR1711", # useless-return + "C416", # unnecessary-comprehension + "PLR1733", # unnecessary-dict-index-lookup + "PLR1736", # unnecessary-list-index-lookup + + # ruff reports this rule is unstable + #"PLR6301", # no-self-use + + "PLW0108", # unnecessary-lambda + "PLW0120", # useless-else-on-loop + "PLW0127", # self-assigning-variable + "PLW0129", # assert-on-string-literal + "B033", # duplicate-value + "PLW0131", # named-expr-without-context + "PLW0245", # super-without-brackets + "PLW0406", # import-self + "PLW0602", # global-variable-not-assigned + "PLW0603", # global-statement + "PLW0604", # global-at-module-level + + # fails on the try: import typing used by libraries + #"F401", # unused-import + + "F841", # unused-variable + "E722", # bare-except + "PLW0711", # binary-op-exception + "PLW1501", # bad-open-mode + "PLW1508", # invalid-envvar-default + "PLW1509", # subprocess-popen-preexec-fn + "PLW2101", # useless-with-lock + "PLW3301", # nested-min-max +] + +ignore = [ + "PLR2004", # magic-value-comparison + "UP030", # format literals + "PLW1514", # unspecified-encoding + "PLR0913", # too-many-arguments + "PLR0915", # too-many-statements + "PLR0917", # too-many-positional-arguments + "PLR0904", # too-many-public-methods + "PLR0912", # too-many-branches + "PLR0916", # too-many-boolean-expressions + "PLR6301", # could-be-static no-self-use + "PLC0415", # import outside toplevel + "PLC2701", # private import + "PLW2901", # loop var overwrite + "F841", # var assigned not used + "E722", # bare except +] + +[format] +line-ending = "lf" diff --git a/util/decode_priv_key.py b/util/decode_priv_key.py index 12f2f99..423d186 100644 --- a/util/decode_priv_key.py +++ b/util/decode_priv_key.py @@ -17,7 +17,9 @@ * Author(s): Google Inc., Brent Rubell """ + import subprocess + import rsa # Generate private and public RSA keys @@ -32,7 +34,7 @@ try: with open("rsa_private.pem", "rb") as file: private_key = file.read() -except: # pylint: disable=bare-except +except: print("No file named rsa_private.pem found in directory.") pk = rsa.PrivateKey.load_pkcs1(private_key)