From 82ef6adec6e59f9cc9dc9fa1a13a2b43e534bada Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Sat, 28 Dec 2024 20:13:43 +0100 Subject: Rename *Exception to *Error and do some general linting --- .ruff.toml | 98 ++ README.md | 5 +- src/otp2289/__init__.py | 26 +- src/otp2289/__main__.py | 46 +- src/otp2289/generator.py | 2408 ++++++++++++++++++++++++++++++++++++++++------ src/otp2289/server.py | 88 +- test/test_generator.py | 55 +- test/test_main.py | 3 +- test/test_server.py | 15 +- 9 files changed, 2296 insertions(+), 448 deletions(-) create mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..c5617c1 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,98 @@ +cache-dir = "~/.cache/ruff" +indent-width = 4 +line-length = 79 +target-version = "py312" + +[lint] +select = ["ALL", "D101", "D102", "D103", "D104"] +ignore = [ + "ANN", + "BLE001", + "COM812", + "D", + "EM101", # Exception must not use a string literal, assign to variable first + "EM102", # Exception must not use an f-string literal, assign to variable first + "ERA001", + "FBT001", + "FBT002", + "INP001", + "ISC001", + "N802", + "N806", + "PLR2004", + "PTH111", + "RUF012", + "RUF013", + "S101", + "S324", + "T201", + "TRY003", + "TRY300", + "UP020" +] + +# D101 - Missing docstring in public class +# D102 - Missing docstring in public method +# D200 - One-line docstring should fit on one line +# D203 - 1 blank line required before class docstring +# D205 - 1 blank line required between summary line and description +# D403 - First word of the first line should be capitalized: `str` -> `Str` +# FBT001 - Boolean-typed positional argument in function definition +# FBT002 - Boolean default positional argument in function definition +# INP001 - File `beinc_weechat.py` is part of an implicit namespace package. Add an `__init__.py` +# N802 - Function name `do_GET` should be lowercase +# N806 - Variable `POST_data` in function should be lowercase +# PLR2004 - Magic value used in comparison, consider replacing `200` with a constant variable +# PTH111 - `os.path.expanduser()` should be replaced by `Path.expanduser()` +# PTH113 - `os.path.isfile()` should be replaced by `Path.is_file()` +# PTH123 - `open()` should be replaced by `Path.open()` +# RUF012 - Mutable class attributes should be annotated with `typing.ClassVar` +# RUF013 - PEP 484 prohibits implicit `Optional` +# S101 - Use of `assert` detected +# S324 - Probable use of insecure hash functions in `hashlib`: `md5` +# T201 - `print` found +# TRY003 - Avoid specifying long messages outside the exception class +# TRY300 - Consider moving this statement to an `else` block +# UP020 - Use builtin `open` + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# custom settings +[lint.per-file-ignores] +"src/otp2289/__main__.py" = ["PTH113", "PTH123"] # "Readability counts" + + +[format] +# Like Black, use double quotes for strings. +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = true + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + +[lint.flake8-quotes] +inline-quotes = "single" + +[lint.isort] +split-on-trailing-comma = false diff --git a/README.md b/README.md index ecf8a22..7ef9344 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ I hope that somebody will find it useful. pkg install security/py-pyotp2289 ``` -– or: +or: ```bash cd /usr/ports/security/py-pyotp2289 @@ -46,9 +46,6 @@ I hope that somebody will find it useful. # add sgs' custom repository using app-eselect/eselect-repository eselect repository add sgs - # ... or using app-portage/layman (obsolete) - layman -a sgs - emerge dev-python/pyotp2289 ``` diff --git a/src/otp2289/__init__.py b/src/otp2289/__init__.py index 5e51498..c9e3c74 100644 --- a/src/otp2289/__init__.py +++ b/src/otp2289/__init__.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023 Simeon Simeonov +# Copyright (c) 2020-2025 Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -24,23 +23,24 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """A pure Python implementation of RFC-2289""" + from .generator import ( OTP_ALGO_MD5, OTP_ALGO_SHA1, - OTPChallengeException, + OTPChallengeError, OTPGenerator, - OTPGeneratorException, + OTPGeneratorError, ) from .server import ( - OTPInvalidResponse, + OTPInvalidResponseError, OTPState, - OTPStateException, + OTPStateError, OTPStore, - OTPStoreException, + OTPStoreError, ) __author__ = 'Simeon Simeonov' -__version__ = '1.2.1' +__version__ = '1.2.2' __license__ = 'BSD 2-Clause' @@ -57,12 +57,12 @@ VERSION = tuple(map(int_or_str, __version__.split('.'))) __all__ = [ 'OTP_ALGO_MD5', 'OTP_ALGO_SHA1', - 'OTPChallengeException', + 'OTPChallengeError', 'OTPGenerator', - 'OTPGeneratorException', - 'OTPInvalidResponse', + 'OTPGeneratorError', + 'OTPInvalidResponseError', 'OTPState', - 'OTPStateException', + 'OTPStateError', 'OTPStore', - 'OTPStoreException', + 'OTPStoreError', ] diff --git a/src/otp2289/__main__.py b/src/otp2289/__main__.py index d267b1b..9f1aab8 100644 --- a/src/otp2289/__main__.py +++ b/src/otp2289/__main__.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023 Simeon Simeonov +# Copyright (c) 2020-2025 Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,10 +31,10 @@ python -m otp2289 --initiate-new-sequence -s TesT python -m otp2289 --generate-otp-response -c "otp-md5 499 TesT " -f token python -m otp2289 --generate-otp-response -s TesT -i 499 -f token """ + import argparse import errno import getpass -import io import os import secrets import string @@ -56,17 +55,15 @@ def generate_otp_response(args: argparse.Namespace) -> str: :param args: The arguments assigned from argparse :type args: argparse.Namespace - :raises otp2289.OTPChallengeException: If the challenge is invalid + :raises otp2289.OTPChallengeError: If the challenge is invalid - :raises otp2289.OTPGeneratorException: If generator parameters are wrong + :raises otp2289.OTPGeneratorError: If generator parameters are wrong :return: The response string :rtype: str """ generator = otp2289.generator.OTPGenerator( - args.password.encode(), - args.seed, - args.hash_algo, + args.password.encode(), args.seed, args.hash_algo ) if args.challenge_string: if args.output_format == 'token': @@ -95,27 +92,26 @@ def generate_otp_range(args: argparse.Namespace) -> str: :param args: The arguments assigned from argparse :type args: argparse.Namespace - :raises otp2289.OTPChallengeException: If the challenge is invalid + :raises otp2289.OTPChallengeError: If the challenge is invalid - :raises otp2289.OTPGeneratorException: If generator parameters are wrong + :raises otp2289.OTPGeneratorError: If generator parameters are wrong :return: The responses string :rtype: str """ generator = otp2289.generator.OTPGenerator( - args.password.encode(), - args.seed, - args.hash_algo, + args.password.encode(), args.seed, args.hash_algo ) if args.output_format == 'token': method = generator.generate_otp_words else: method = generator.generate_otp_hexdigest + # handle most cases explicitly if args.range == 1: return f'{args.step}: ' + method(args.step) - if args.range > args.step + 1: - args.range = args.step + 1 + args.range = min(args.range, args.step + 1) + # any need for quiet? header = '' if not args.quiet: @@ -126,11 +122,7 @@ def generate_otp_range(args: argparse.Namespace) -> str: return header + os.linesep.join( [ f'{step}: ' + method(step) - for step in range( - args.step, - args.step - args.range, - -1, - ) + for step in range(args.step, args.step - args.range, -1) ] ) @@ -171,7 +163,7 @@ def get_password(args: argparse.Namespace) -> str: return password if os.path.isfile(args.password): - with io.open(args.password, 'r', encoding='utf-8') as fp: + with open(args.password, encoding='utf-8') as fp: return fp.readline().strip() return args.password @@ -196,9 +188,9 @@ def initiate_new_sequence(args: argparse.Namespace) -> str: :param args: The arguments assigned from argparse :type args: argparse.Namespace - :raises otp2289.OTPChallengeException: If the challenge is invalid + :raises otp2289.OTPChallengeError: If the challenge is invalid - :raises otp2289.OTPGeneratorException: If generator parameters are wrong + :raises otp2289.OTPGeneratorError: If generator parameters are wrong :return: The response string :rtype: str @@ -212,9 +204,7 @@ def initiate_new_sequence(args: argparse.Namespace) -> str: f'Hash: {args.hash_algo}{os.linesep}' ) generator = otp2289.generator.OTPGenerator( - args.password.encode(), - args.seed, - args.hash_algo, + args.password.encode(), args.seed, args.hash_algo ) if args.challenge_string: return header + generator.generate_otp_hexdigest_from_challenge( @@ -372,9 +362,9 @@ def main(args=None): if args.generate_otp_response: print(generate_otp_response(args)) sys.exit(0) - except otp2289.generator.OTPGeneratorException as exp: + except otp2289.generator.OTPGeneratorError as exp: eprint(f'GeneratorException: {exp}') - except otp2289.generator.OTPChallengeException as exp: + except otp2289.generator.OTPChallengeError as exp: eprint(f'ChallengeException: {exp}') except Exception as exp: eprint(f'Unknown error: {exp}') diff --git a/src/otp2289/generator.py b/src/otp2289/generator.py index 73ca18b..c03d289 100644 --- a/src/otp2289/generator.py +++ b/src/otp2289/generator.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023 Simeon Simeonov +# Copyright (c) 2020-2025 Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -24,6 +23,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """A pure Python implementation of the RFC-2289 OTP generator""" + import binascii import hashlib import string @@ -33,283 +33,2072 @@ OTP_ALGO_SHA1 = 2 # the tokens are defined in https://tools.ietf.org/html/rfc2289 # RFC1760_TOKENS = [ - 'A', 'ABE', 'ACE', 'ACT', 'AD', 'ADA', 'ADD', - 'AGO', 'AID', 'AIM', 'AIR', 'ALL', 'ALP', 'AM', 'AMY', - 'AN', 'ANA', 'AND', 'ANN', 'ANT', 'ANY', 'APE', 'APS', - 'APT', 'ARC', 'ARE', 'ARK', 'ARM', 'ART', 'AS', 'ASH', - 'ASK', 'AT', 'ATE', 'AUG', 'AUK', 'AVE', 'AWE', 'AWK', - 'AWL', 'AWN', 'AX', 'AYE', 'BAD', 'BAG', 'BAH', 'BAM', - 'BAN', 'BAR', 'BAT', 'BAY', 'BE', 'BED', 'BEE', 'BEG', - 'BEN', 'BET', 'BEY', 'BIB', 'BID', 'BIG', 'BIN', 'BIT', - 'BOB', 'BOG', 'BON', 'BOO', 'BOP', 'BOW', 'BOY', 'BUB', - 'BUD', 'BUG', 'BUM', 'BUN', 'BUS', 'BUT', 'BUY', 'BY', - 'BYE', 'CAB', 'CAL', 'CAM', 'CAN', 'CAP', 'CAR', 'CAT', - 'CAW', 'COD', 'COG', 'COL', 'CON', 'COO', 'COP', 'COT', - 'COW', 'COY', 'CRY', 'CUB', 'CUE', 'CUP', 'CUR', 'CUT', - 'DAB', 'DAD', 'DAM', 'DAN', 'DAR', 'DAY', 'DEE', 'DEL', - 'DEN', 'DES', 'DEW', 'DID', 'DIE', 'DIG', 'DIN', 'DIP', - 'DO', 'DOE', 'DOG', 'DON', 'DOT', 'DOW', 'DRY', 'DUB', - 'DUD', 'DUE', 'DUG', 'DUN', 'EAR', 'EAT', 'ED', 'EEL', - 'EGG', 'EGO', 'ELI', 'ELK', 'ELM', 'ELY', 'EM', 'END', - 'EST', 'ETC', 'EVA', 'EVE', 'EWE', 'EYE', 'FAD', 'FAN', - 'FAR', 'FAT', 'FAY', 'FED', 'FEE', 'FEW', 'FIB', 'FIG', - 'FIN', 'FIR', 'FIT', 'FLO', 'FLY', 'FOE', 'FOG', 'FOR', - 'FRY', 'FUM', 'FUN', 'FUR', 'GAB', 'GAD', 'GAG', 'GAL', - 'GAM', 'GAP', 'GAS', 'GAY', 'GEE', 'GEL', 'GEM', 'GET', - 'GIG', 'GIL', 'GIN', 'GO', 'GOT', 'GUM', 'GUN', 'GUS', - 'GUT', 'GUY', 'GYM', 'GYP', 'HA', 'HAD', 'HAL', 'HAM', - 'HAN', 'HAP', 'HAS', 'HAT', 'HAW', 'HAY', 'HE', 'HEM', - 'HEN', 'HER', 'HEW', 'HEY', 'HI', 'HID', 'HIM', 'HIP', - 'HIS', 'HIT', 'HO', 'HOB', 'HOC', 'HOE', 'HOG', 'HOP', - 'HOT', 'HOW', 'HUB', 'HUE', 'HUG', 'HUH', 'HUM', 'HUT', - 'I', 'ICY', 'IDA', 'IF', 'IKE', 'ILL', 'INK', 'INN', - 'IO', 'ION', 'IQ', 'IRA', 'IRE', 'IRK', 'IS', 'IT', - 'ITS', 'IVY', 'JAB', 'JAG', 'JAM', 'JAN', 'JAR', 'JAW', - 'JAY', 'JET', 'JIG', 'JIM', 'JO', 'JOB', 'JOE', 'JOG', - 'JOT', 'JOY', 'JUG', 'JUT', 'KAY', 'KEG', 'KEN', 'KEY', - 'KID', 'KIM', 'KIN', 'KIT', 'LA', 'LAB', 'LAC', 'LAD', - 'LAG', 'LAM', 'LAP', 'LAW', 'LAY', 'LEA', 'LED', 'LEE', - 'LEG', 'LEN', 'LEO', 'LET', 'LEW', 'LID', 'LIE', 'LIN', - 'LIP', 'LIT', 'LO', 'LOB', 'LOG', 'LOP', 'LOS', 'LOT', - 'LOU', 'LOW', 'LOY', 'LUG', 'LYE', 'MA', 'MAC', 'MAD', - 'MAE', 'MAN', 'MAO', 'MAP', 'MAT', 'MAW', 'MAY', 'ME', - 'MEG', 'MEL', 'MEN', 'MET', 'MEW', 'MID', 'MIN', 'MIT', - 'MOB', 'MOD', 'MOE', 'MOO', 'MOP', 'MOS', 'MOT', 'MOW', - 'MUD', 'MUG', 'MUM', 'MY', 'NAB', 'NAG', 'NAN', 'NAP', - 'NAT', 'NAY', 'NE', 'NED', 'NEE', 'NET', 'NEW', 'NIB', - 'NIL', 'NIP', 'NIT', 'NO', 'NOB', 'NOD', 'NON', 'NOR', - 'NOT', 'NOV', 'NOW', 'NU', 'NUN', 'NUT', 'O', 'OAF', - 'OAK', 'OAR', 'OAT', 'ODD', 'ODE', 'OF', 'OFF', 'OFT', - 'OH', 'OIL', 'OK', 'OLD', 'ON', 'ONE', 'OR', 'ORB', - 'ORE', 'ORR', 'OS', 'OTT', 'OUR', 'OUT', 'OVA', 'OW', - 'OWE', 'OWL', 'OWN', 'OX', 'PA', 'PAD', 'PAL', 'PAM', - 'PAN', 'PAP', 'PAR', 'PAT', 'PAW', 'PAY', 'PEA', 'PEG', - 'PEN', 'PEP', 'PER', 'PET', 'PEW', 'PHI', 'PI', 'PIE', - 'PIN', 'PIT', 'PLY', 'PO', 'POD', 'POE', 'POP', 'POT', - 'POW', 'PRO', 'PRY', 'PUB', 'PUG', 'PUN', 'PUP', 'PUT', - 'QUO', 'RAG', 'RAM', 'RAN', 'RAP', 'RAT', 'RAW', 'RAY', - 'REB', 'RED', 'REP', 'RET', 'RIB', 'RID', 'RIG', 'RIM', - 'RIO', 'RIP', 'ROB', 'ROD', 'ROE', 'RON', 'ROT', 'ROW', - 'ROY', 'RUB', 'RUE', 'RUG', 'RUM', 'RUN', 'RYE', 'SAC', - 'SAD', 'SAG', 'SAL', 'SAM', 'SAN', 'SAP', 'SAT', 'SAW', - 'SAY', 'SEA', 'SEC', 'SEE', 'SEN', 'SET', 'SEW', 'SHE', - 'SHY', 'SIN', 'SIP', 'SIR', 'SIS', 'SIT', 'SKI', 'SKY', - 'SLY', 'SO', 'SOB', 'SOD', 'SON', 'SOP', 'SOW', 'SOY', - 'SPA', 'SPY', 'SUB', 'SUD', 'SUE', 'SUM', 'SUN', 'SUP', - 'TAB', 'TAD', 'TAG', 'TAN', 'TAP', 'TAR', 'TEA', 'TED', - 'TEE', 'TEN', 'THE', 'THY', 'TIC', 'TIE', 'TIM', 'TIN', - 'TIP', 'TO', 'TOE', 'TOG', 'TOM', 'TON', 'TOO', 'TOP', - 'TOW', 'TOY', 'TRY', 'TUB', 'TUG', 'TUM', 'TUN', 'TWO', - 'UN', 'UP', 'US', 'USE', 'VAN', 'VAT', 'VET', 'VIE', - 'WAD', 'WAG', 'WAR', 'WAS', 'WAY', 'WE', 'WEB', 'WED', - 'WEE', 'WET', 'WHO', 'WHY', 'WIN', 'WIT', 'WOK', 'WON', - 'WOO', 'WOW', 'WRY', 'WU', 'YAM', 'YAP', 'YAW', 'YE', - 'YEA', 'YES', 'YET', 'YOU', 'ABED', 'ABEL', 'ABET', 'ABLE', - 'ABUT', 'ACHE', 'ACID', 'ACME', 'ACRE', 'ACTA', 'ACTS', 'ADAM', - 'ADDS', 'ADEN', 'AFAR', 'AFRO', 'AGEE', 'AHEM', 'AHOY', 'AIDA', - 'AIDE', 'AIDS', 'AIRY', 'AJAR', 'AKIN', 'ALAN', 'ALEC', 'ALGA', - 'ALIA', 'ALLY', 'ALMA', 'ALOE', 'ALSO', 'ALTO', 'ALUM', 'ALVA', - 'AMEN', 'AMES', 'AMID', 'AMMO', 'AMOK', 'AMOS', 'AMRA', 'ANDY', - 'ANEW', 'ANNA', 'ANNE', 'ANTE', 'ANTI', 'AQUA', 'ARAB', 'ARCH', - 'AREA', 'ARGO', 'ARID', 'ARMY', 'ARTS', 'ARTY', 'ASIA', 'ASKS', - 'ATOM', 'AUNT', 'AURA', 'AUTO', 'AVER', 'AVID', 'AVIS', 'AVON', - 'AVOW', 'AWAY', 'AWRY', 'BABE', 'BABY', 'BACH', 'BACK', 'BADE', - 'BAIL', 'BAIT', 'BAKE', 'BALD', 'BALE', 'BALI', 'BALK', 'BALL', - 'BALM', 'BAND', 'BANE', 'BANG', 'BANK', 'BARB', 'BARD', 'BARE', - 'BARK', 'BARN', 'BARR', 'BASE', 'BASH', 'BASK', 'BASS', 'BATE', - 'BATH', 'BAWD', 'BAWL', 'BEAD', 'BEAK', 'BEAM', 'BEAN', 'BEAR', - 'BEAT', 'BEAU', 'BECK', 'BEEF', 'BEEN', 'BEER', 'BEET', 'BELA', - 'BELL', 'BELT', 'BEND', 'BENT', 'BERG', 'BERN', 'BERT', 'BESS', - 'BEST', 'BETA', 'BETH', 'BHOY', 'BIAS', 'BIDE', 'BIEN', 'BILE', - 'BILK', 'BILL', 'BIND', 'BING', 'BIRD', 'BITE', 'BITS', 'BLAB', - 'BLAT', 'BLED', 'BLEW', 'BLOB', 'BLOC', 'BLOT', 'BLOW', 'BLUE', - 'BLUM', 'BLUR', 'BOAR', 'BOAT', 'BOCA', 'BOCK', 'BODE', 'BODY', - 'BOGY', 'BOHR', 'BOIL', 'BOLD', 'BOLO', 'BOLT', 'BOMB', 'BONA', - 'BOND', 'BONE', 'BONG', 'BONN', 'BONY', 'BOOK', 'BOOM', 'BOON', - 'BOOT', 'BORE', 'BORG', 'BORN', 'BOSE', 'BOSS', 'BOTH', 'BOUT', - 'BOWL', 'BOYD', 'BRAD', 'BRAE', 'BRAG', 'BRAN', 'BRAY', 'BRED', - 'BREW', 'BRIG', 'BRIM', 'BROW', 'BUCK', 'BUDD', 'BUFF', 'BULB', - 'BULK', 'BULL', 'BUNK', 'BUNT', 'BUOY', 'BURG', 'BURL', 'BURN', - 'BURR', 'BURT', 'BURY', 'BUSH', 'BUSS', 'BUST', 'BUSY', 'BYTE', - 'CADY', 'CAFE', 'CAGE', 'CAIN', 'CAKE', 'CALF', 'CALL', 'CALM', - 'CAME', 'CANE', 'CANT', 'CARD', 'CARE', 'CARL', 'CARR', 'CART', - 'CASE', 'CASH', 'CASK', 'CAST', 'CAVE', 'CEIL', 'CELL', 'CENT', - 'CERN', 'CHAD', 'CHAR', 'CHAT', 'CHAW', 'CHEF', 'CHEN', 'CHEW', - 'CHIC', 'CHIN', 'CHOU', 'CHOW', 'CHUB', 'CHUG', 'CHUM', 'CITE', - 'CITY', 'CLAD', 'CLAM', 'CLAN', 'CLAW', 'CLAY', 'CLOD', 'CLOG', - 'CLOT', 'CLUB', 'CLUE', 'COAL', 'COAT', 'COCA', 'COCK', 'COCO', - 'CODA', 'CODE', 'CODY', 'COED', 'COIL', 'COIN', 'COKE', 'COLA', - 'COLD', 'COLT', 'COMA', 'COMB', 'COME', 'COOK', 'COOL', 'COON', - 'COOT', 'CORD', 'CORE', 'CORK', 'CORN', 'COST', 'COVE', 'COWL', - 'CRAB', 'CRAG', 'CRAM', 'CRAY', 'CREW', 'CRIB', 'CROW', 'CRUD', - 'CUBA', 'CUBE', 'CUFF', 'CULL', 'CULT', 'CUNY', 'CURB', 'CURD', - 'CURE', 'CURL', 'CURT', 'CUTS', 'DADE', 'DALE', 'DAME', 'DANA', - 'DANE', 'DANG', 'DANK', 'DARE', 'DARK', 'DARN', 'DART', 'DASH', - 'DATA', 'DATE', 'DAVE', 'DAVY', 'DAWN', 'DAYS', 'DEAD', 'DEAF', - 'DEAL', 'DEAN', 'DEAR', 'DEBT', 'DECK', 'DEED', 'DEEM', 'DEER', - 'DEFT', 'DEFY', 'DELL', 'DENT', 'DENY', 'DESK', 'DIAL', 'DICE', - 'DIED', 'DIET', 'DIME', 'DINE', 'DING', 'DINT', 'DIRE', 'DIRT', - 'DISC', 'DISH', 'DISK', 'DIVE', 'DOCK', 'DOES', 'DOLE', 'DOLL', - 'DOLT', 'DOME', 'DONE', 'DOOM', 'DOOR', 'DORA', 'DOSE', 'DOTE', - 'DOUG', 'DOUR', 'DOVE', 'DOWN', 'DRAB', 'DRAG', 'DRAM', 'DRAW', - 'DREW', 'DRUB', 'DRUG', 'DRUM', 'DUAL', 'DUCK', 'DUCT', 'DUEL', - 'DUET', 'DUKE', 'DULL', 'DUMB', 'DUNE', 'DUNK', 'DUSK', 'DUST', - 'DUTY', 'EACH', 'EARL', 'EARN', 'EASE', 'EAST', 'EASY', 'EBEN', - 'ECHO', 'EDDY', 'EDEN', 'EDGE', 'EDGY', 'EDIT', 'EDNA', 'EGAN', - 'ELAN', 'ELBA', 'ELLA', 'ELSE', 'EMIL', 'EMIT', 'EMMA', 'ENDS', - 'ERIC', 'EROS', 'EVEN', 'EVER', 'EVIL', 'EYED', 'FACE', 'FACT', - 'FADE', 'FAIL', 'FAIN', 'FAIR', 'FAKE', 'FALL', 'FAME', 'FANG', - 'FARM', 'FAST', 'FATE', 'FAWN', 'FEAR', 'FEAT', 'FEED', 'FEEL', - 'FEET', 'FELL', 'FELT', 'FEND', 'FERN', 'FEST', 'FEUD', 'FIEF', - 'FIGS', 'FILE', 'FILL', 'FILM', 'FIND', 'FINE', 'FINK', 'FIRE', - 'FIRM', 'FISH', 'FISK', 'FIST', 'FITS', 'FIVE', 'FLAG', 'FLAK', - 'FLAM', 'FLAT', 'FLAW', 'FLEA', 'FLED', 'FLEW', 'FLIT', 'FLOC', - 'FLOG', 'FLOW', 'FLUB', 'FLUE', 'FOAL', 'FOAM', 'FOGY', 'FOIL', - 'FOLD', 'FOLK', 'FOND', 'FONT', 'FOOD', 'FOOL', 'FOOT', 'FORD', - 'FORE', 'FORK', 'FORM', 'FORT', 'FOSS', 'FOUL', 'FOUR', 'FOWL', - 'FRAU', 'FRAY', 'FRED', 'FREE', 'FRET', 'FREY', 'FROG', 'FROM', - 'FUEL', 'FULL', 'FUME', 'FUND', 'FUNK', 'FURY', 'FUSE', 'FUSS', - 'GAFF', 'GAGE', 'GAIL', 'GAIN', 'GAIT', 'GALA', 'GALE', 'GALL', - 'GALT', 'GAME', 'GANG', 'GARB', 'GARY', 'GASH', 'GATE', 'GAUL', - 'GAUR', 'GAVE', 'GAWK', 'GEAR', 'GELD', 'GENE', 'GENT', 'GERM', - 'GETS', 'GIBE', 'GIFT', 'GILD', 'GILL', 'GILT', 'GINA', 'GIRD', - 'GIRL', 'GIST', 'GIVE', 'GLAD', 'GLEE', 'GLEN', 'GLIB', 'GLOB', - 'GLOM', 'GLOW', 'GLUE', 'GLUM', 'GLUT', 'GOAD', 'GOAL', 'GOAT', - 'GOER', 'GOES', 'GOLD', 'GOLF', 'GONE', 'GONG', 'GOOD', 'GOOF', - 'GORE', 'GORY', 'GOSH', 'GOUT', 'GOWN', 'GRAB', 'GRAD', 'GRAY', - 'GREG', 'GREW', 'GREY', 'GRID', 'GRIM', 'GRIN', 'GRIT', 'GROW', - 'GRUB', 'GULF', 'GULL', 'GUNK', 'GURU', 'GUSH', 'GUST', 'GWEN', - 'GWYN', 'HAAG', 'HAAS', 'HACK', 'HAIL', 'HAIR', 'HALE', 'HALF', - 'HALL', 'HALO', 'HALT', 'HAND', 'HANG', 'HANK', 'HANS', 'HARD', - 'HARK', 'HARM', 'HART', 'HASH', 'HAST', 'HATE', 'HATH', 'HAUL', - 'HAVE', 'HAWK', 'HAYS', 'HEAD', 'HEAL', 'HEAR', 'HEAT', 'HEBE', - 'HECK', 'HEED', 'HEEL', 'HEFT', 'HELD', 'HELL', 'HELM', 'HERB', - 'HERD', 'HERE', 'HERO', 'HERS', 'HESS', 'HEWN', 'HICK', 'HIDE', - 'HIGH', 'HIKE', 'HILL', 'HILT', 'HIND', 'HINT', 'HIRE', 'HISS', - 'HIVE', 'HOBO', 'HOCK', 'HOFF', 'HOLD', 'HOLE', 'HOLM', 'HOLT', - 'HOME', 'HONE', 'HONK', 'HOOD', 'HOOF', 'HOOK', 'HOOT', 'HORN', - 'HOSE', 'HOST', 'HOUR', 'HOVE', 'HOWE', 'HOWL', 'HOYT', 'HUCK', - 'HUED', 'HUFF', 'HUGE', 'HUGH', 'HUGO', 'HULK', 'HULL', 'HUNK', - 'HUNT', 'HURD', 'HURL', 'HURT', 'HUSH', 'HYDE', 'HYMN', 'IBIS', - 'ICON', 'IDEA', 'IDLE', 'IFFY', 'INCA', 'INCH', 'INTO', 'IONS', - 'IOTA', 'IOWA', 'IRIS', 'IRMA', 'IRON', 'ISLE', 'ITCH', 'ITEM', - 'IVAN', 'JACK', 'JADE', 'JAIL', 'JAKE', 'JANE', 'JAVA', 'JEAN', - 'JEFF', 'JERK', 'JESS', 'JEST', 'JIBE', 'JILL', 'JILT', 'JIVE', - 'JOAN', 'JOBS', 'JOCK', 'JOEL', 'JOEY', 'JOHN', 'JOIN', 'JOKE', - 'JOLT', 'JOVE', 'JUDD', 'JUDE', 'JUDO', 'JUDY', 'JUJU', 'JUKE', - 'JULY', 'JUNE', 'JUNK', 'JUNO', 'JURY', 'JUST', 'JUTE', 'KAHN', - 'KALE', 'KANE', 'KANT', 'KARL', 'KATE', 'KEEL', 'KEEN', 'KENO', - 'KENT', 'KERN', 'KERR', 'KEYS', 'KICK', 'KILL', 'KIND', 'KING', - 'KIRK', 'KISS', 'KITE', 'KLAN', 'KNEE', 'KNEW', 'KNIT', 'KNOB', - 'KNOT', 'KNOW', 'KOCH', 'KONG', 'KUDO', 'KURD', 'KURT', 'KYLE', - 'LACE', 'LACK', 'LACY', 'LADY', 'LAID', 'LAIN', 'LAIR', 'LAKE', - 'LAMB', 'LAME', 'LAND', 'LANE', 'LANG', 'LARD', 'LARK', 'LASS', - 'LAST', 'LATE', 'LAUD', 'LAVA', 'LAWN', 'LAWS', 'LAYS', 'LEAD', - 'LEAF', 'LEAK', 'LEAN', 'LEAR', 'LEEK', 'LEER', 'LEFT', 'LEND', - 'LENS', 'LENT', 'LEON', 'LESK', 'LESS', 'LEST', 'LETS', 'LIAR', - 'LICE', 'LICK', 'LIED', 'LIEN', 'LIES', 'LIEU', 'LIFE', 'LIFT', - 'LIKE', 'LILA', 'LILT', 'LILY', 'LIMA', 'LIMB', 'LIME', 'LIND', - 'LINE', 'LINK', 'LINT', 'LION', 'LISA', 'LIST', 'LIVE', 'LOAD', - 'LOAF', 'LOAM', 'LOAN', 'LOCK', 'LOFT', 'LOGE', 'LOIS', 'LOLA', - 'LONE', 'LONG', 'LOOK', 'LOON', 'LOOT', 'LORD', 'LORE', 'LOSE', - 'LOSS', 'LOST', 'LOUD', 'LOVE', 'LOWE', 'LUCK', 'LUCY', 'LUGE', - 'LUKE', 'LULU', 'LUND', 'LUNG', 'LURA', 'LURE', 'LURK', 'LUSH', - 'LUST', 'LYLE', 'LYNN', 'LYON', 'LYRA', 'MACE', 'MADE', 'MAGI', - 'MAID', 'MAIL', 'MAIN', 'MAKE', 'MALE', 'MALI', 'MALL', 'MALT', - 'MANA', 'MANN', 'MANY', 'MARC', 'MARE', 'MARK', 'MARS', 'MART', - 'MARY', 'MASH', 'MASK', 'MASS', 'MAST', 'MATE', 'MATH', 'MAUL', - 'MAYO', 'MEAD', 'MEAL', 'MEAN', 'MEAT', 'MEEK', 'MEET', 'MELD', - 'MELT', 'MEMO', 'MEND', 'MENU', 'MERT', 'MESH', 'MESS', 'MICE', - 'MIKE', 'MILD', 'MILE', 'MILK', 'MILL', 'MILT', 'MIMI', 'MIND', - 'MINE', 'MINI', 'MINK', 'MINT', 'MIRE', 'MISS', 'MIST', 'MITE', - 'MITT', 'MOAN', 'MOAT', 'MOCK', 'MODE', 'MOLD', 'MOLE', 'MOLL', - 'MOLT', 'MONA', 'MONK', 'MONT', 'MOOD', 'MOON', 'MOOR', 'MOOT', - 'MORE', 'MORN', 'MORT', 'MOSS', 'MOST', 'MOTH', 'MOVE', 'MUCH', - 'MUCK', 'MUDD', 'MUFF', 'MULE', 'MULL', 'MURK', 'MUSH', 'MUST', - 'MUTE', 'MUTT', 'MYRA', 'MYTH', 'NAGY', 'NAIL', 'NAIR', 'NAME', - 'NARY', 'NASH', 'NAVE', 'NAVY', 'NEAL', 'NEAR', 'NEAT', 'NECK', - 'NEED', 'NEIL', 'NELL', 'NEON', 'NERO', 'NESS', 'NEST', 'NEWS', - 'NEWT', 'NIBS', 'NICE', 'NICK', 'NILE', 'NINA', 'NINE', 'NOAH', - 'NODE', 'NOEL', 'NOLL', 'NONE', 'NOOK', 'NOON', 'NORM', 'NOSE', - 'NOTE', 'NOUN', 'NOVA', 'NUDE', 'NULL', 'NUMB', 'OATH', 'OBEY', - 'OBOE', 'ODIN', 'OHIO', 'OILY', 'OINT', 'OKAY', 'OLAF', 'OLDY', - 'OLGA', 'OLIN', 'OMAN', 'OMEN', 'OMIT', 'ONCE', 'ONES', 'ONLY', - 'ONTO', 'ONUS', 'ORAL', 'ORGY', 'OSLO', 'OTIS', 'OTTO', 'OUCH', - 'OUST', 'OUTS', 'OVAL', 'OVEN', 'OVER', 'OWLY', 'OWNS', 'QUAD', - 'QUIT', 'QUOD', 'RACE', 'RACK', 'RACY', 'RAFT', 'RAGE', 'RAID', - 'RAIL', 'RAIN', 'RAKE', 'RANK', 'RANT', 'RARE', 'RASH', 'RATE', - 'RAVE', 'RAYS', 'READ', 'REAL', 'REAM', 'REAR', 'RECK', 'REED', - 'REEF', 'REEK', 'REEL', 'REID', 'REIN', 'RENA', 'REND', 'RENT', - 'REST', 'RICE', 'RICH', 'RICK', 'RIDE', 'RIFT', 'RILL', 'RIME', - 'RING', 'RINK', 'RISE', 'RISK', 'RITE', 'ROAD', 'ROAM', 'ROAR', - 'ROBE', 'ROCK', 'RODE', 'ROIL', 'ROLL', 'ROME', 'ROOD', 'ROOF', - 'ROOK', 'ROOM', 'ROOT', 'ROSA', 'ROSE', 'ROSS', 'ROSY', 'ROTH', - 'ROUT', 'ROVE', 'ROWE', 'ROWS', 'RUBE', 'RUBY', 'RUDE', 'RUDY', - 'RUIN', 'RULE', 'RUNG', 'RUNS', 'RUNT', 'RUSE', 'RUSH', 'RUSK', - 'RUSS', 'RUST', 'RUTH', 'SACK', 'SAFE', 'SAGE', 'SAID', 'SAIL', - 'SALE', 'SALK', 'SALT', 'SAME', 'SAND', 'SANE', 'SANG', 'SANK', - 'SARA', 'SAUL', 'SAVE', 'SAYS', 'SCAN', 'SCAR', 'SCAT', 'SCOT', - 'SEAL', 'SEAM', 'SEAR', 'SEAT', 'SEED', 'SEEK', 'SEEM', 'SEEN', - 'SEES', 'SELF', 'SELL', 'SEND', 'SENT', 'SETS', 'SEWN', 'SHAG', - 'SHAM', 'SHAW', 'SHAY', 'SHED', 'SHIM', 'SHIN', 'SHOD', 'SHOE', - 'SHOT', 'SHOW', 'SHUN', 'SHUT', 'SICK', 'SIDE', 'SIFT', 'SIGH', - 'SIGN', 'SILK', 'SILL', 'SILO', 'SILT', 'SINE', 'SING', 'SINK', - 'SIRE', 'SITE', 'SITS', 'SITU', 'SKAT', 'SKEW', 'SKID', 'SKIM', - 'SKIN', 'SKIT', 'SLAB', 'SLAM', 'SLAT', 'SLAY', 'SLED', 'SLEW', - 'SLID', 'SLIM', 'SLIT', 'SLOB', 'SLOG', 'SLOT', 'SLOW', 'SLUG', - 'SLUM', 'SLUR', 'SMOG', 'SMUG', 'SNAG', 'SNOB', 'SNOW', 'SNUB', - 'SNUG', 'SOAK', 'SOAR', 'SOCK', 'SODA', 'SOFA', 'SOFT', 'SOIL', - 'SOLD', 'SOME', 'SONG', 'SOON', 'SOOT', 'SORE', 'SORT', 'SOUL', - 'SOUR', 'SOWN', 'STAB', 'STAG', 'STAN', 'STAR', 'STAY', 'STEM', - 'STEW', 'STIR', 'STOW', 'STUB', 'STUN', 'SUCH', 'SUDS', 'SUIT', - 'SULK', 'SUMS', 'SUNG', 'SUNK', 'SURE', 'SURF', 'SWAB', 'SWAG', - 'SWAM', 'SWAN', 'SWAT', 'SWAY', 'SWIM', 'SWUM', 'TACK', 'TACT', - 'TAIL', 'TAKE', 'TALE', 'TALK', 'TALL', 'TANK', 'TASK', 'TATE', - 'TAUT', 'TEAL', 'TEAM', 'TEAR', 'TECH', 'TEEM', 'TEEN', 'TEET', - 'TELL', 'TEND', 'TENT', 'TERM', 'TERN', 'TESS', 'TEST', 'THAN', - 'THAT', 'THEE', 'THEM', 'THEN', 'THEY', 'THIN', 'THIS', 'THUD', - 'THUG', 'TICK', 'TIDE', 'TIDY', 'TIED', 'TIER', 'TILE', 'TILL', - 'TILT', 'TIME', 'TINA', 'TINE', 'TINT', 'TINY', 'TIRE', 'TOAD', - 'TOGO', 'TOIL', 'TOLD', 'TOLL', 'TONE', 'TONG', 'TONY', 'TOOK', - 'TOOL', 'TOOT', 'TORE', 'TORN', 'TOTE', 'TOUR', 'TOUT', 'TOWN', - 'TRAG', 'TRAM', 'TRAY', 'TREE', 'TREK', 'TRIG', 'TRIM', 'TRIO', - 'TROD', 'TROT', 'TROY', 'TRUE', 'TUBA', 'TUBE', 'TUCK', 'TUFT', - 'TUNA', 'TUNE', 'TUNG', 'TURF', 'TURN', 'TUSK', 'TWIG', 'TWIN', - 'TWIT', 'ULAN', 'UNIT', 'URGE', 'USED', 'USER', 'USES', 'UTAH', - 'VAIL', 'VAIN', 'VALE', 'VARY', 'VASE', 'VAST', 'VEAL', 'VEDA', - 'VEIL', 'VEIN', 'VEND', 'VENT', 'VERB', 'VERY', 'VETO', 'VICE', - 'VIEW', 'VINE', 'VISE', 'VOID', 'VOLT', 'VOTE', 'WACK', 'WADE', - 'WAGE', 'WAIL', 'WAIT', 'WAKE', 'WALE', 'WALK', 'WALL', 'WALT', - 'WAND', 'WANE', 'WANG', 'WANT', 'WARD', 'WARM', 'WARN', 'WART', - 'WASH', 'WAST', 'WATS', 'WATT', 'WAVE', 'WAVY', 'WAYS', 'WEAK', - 'WEAL', 'WEAN', 'WEAR', 'WEED', 'WEEK', 'WEIR', 'WELD', 'WELL', - 'WELT', 'WENT', 'WERE', 'WERT', 'WEST', 'WHAM', 'WHAT', 'WHEE', - 'WHEN', 'WHET', 'WHOA', 'WHOM', 'WICK', 'WIFE', 'WILD', 'WILL', - 'WIND', 'WINE', 'WING', 'WINK', 'WINO', 'WIRE', 'WISE', 'WISH', - 'WITH', 'WOLF', 'WONT', 'WOOD', 'WOOL', 'WORD', 'WORE', 'WORK', - 'WORM', 'WORN', 'WOVE', 'WRIT', 'WYNN', 'YALE', 'YANG', 'YANK', - 'YARD', 'YARN', 'YAWL', 'YAWN', 'YEAH', 'YEAR', 'YELL', 'YOGA', - 'YOKE'] + 'A', + 'ABE', + 'ACE', + 'ACT', + 'AD', + 'ADA', + 'ADD', + 'AGO', + 'AID', + 'AIM', + 'AIR', + 'ALL', + 'ALP', + 'AM', + 'AMY', + 'AN', + 'ANA', + 'AND', + 'ANN', + 'ANT', + 'ANY', + 'APE', + 'APS', + 'APT', + 'ARC', + 'ARE', + 'ARK', + 'ARM', + 'ART', + 'AS', + 'ASH', + 'ASK', + 'AT', + 'ATE', + 'AUG', + 'AUK', + 'AVE', + 'AWE', + 'AWK', + 'AWL', + 'AWN', + 'AX', + 'AYE', + 'BAD', + 'BAG', + 'BAH', + 'BAM', + 'BAN', + 'BAR', + 'BAT', + 'BAY', + 'BE', + 'BED', + 'BEE', + 'BEG', + 'BEN', + 'BET', + 'BEY', + 'BIB', + 'BID', + 'BIG', + 'BIN', + 'BIT', + 'BOB', + 'BOG', + 'BON', + 'BOO', + 'BOP', + 'BOW', + 'BOY', + 'BUB', + 'BUD', + 'BUG', + 'BUM', + 'BUN', + 'BUS', + 'BUT', + 'BUY', + 'BY', + 'BYE', + 'CAB', + 'CAL', + 'CAM', + 'CAN', + 'CAP', + 'CAR', + 'CAT', + 'CAW', + 'COD', + 'COG', + 'COL', + 'CON', + 'COO', + 'COP', + 'COT', + 'COW', + 'COY', + 'CRY', + 'CUB', + 'CUE', + 'CUP', + 'CUR', + 'CUT', + 'DAB', + 'DAD', + 'DAM', + 'DAN', + 'DAR', + 'DAY', + 'DEE', + 'DEL', + 'DEN', + 'DES', + 'DEW', + 'DID', + 'DIE', + 'DIG', + 'DIN', + 'DIP', + 'DO', + 'DOE', + 'DOG', + 'DON', + 'DOT', + 'DOW', + 'DRY', + 'DUB', + 'DUD', + 'DUE', + 'DUG', + 'DUN', + 'EAR', + 'EAT', + 'ED', + 'EEL', + 'EGG', + 'EGO', + 'ELI', + 'ELK', + 'ELM', + 'ELY', + 'EM', + 'END', + 'EST', + 'ETC', + 'EVA', + 'EVE', + 'EWE', + 'EYE', + 'FAD', + 'FAN', + 'FAR', + 'FAT', + 'FAY', + 'FED', + 'FEE', + 'FEW', + 'FIB', + 'FIG', + 'FIN', + 'FIR', + 'FIT', + 'FLO', + 'FLY', + 'FOE', + 'FOG', + 'FOR', + 'FRY', + 'FUM', + 'FUN', + 'FUR', + 'GAB', + 'GAD', + 'GAG', + 'GAL', + 'GAM', + 'GAP', + 'GAS', + 'GAY', + 'GEE', + 'GEL', + 'GEM', + 'GET', + 'GIG', + 'GIL', + 'GIN', + 'GO', + 'GOT', + 'GUM', + 'GUN', + 'GUS', + 'GUT', + 'GUY', + 'GYM', + 'GYP', + 'HA', + 'HAD', + 'HAL', + 'HAM', + 'HAN', + 'HAP', + 'HAS', + 'HAT', + 'HAW', + 'HAY', + 'HE', + 'HEM', + 'HEN', + 'HER', + 'HEW', + 'HEY', + 'HI', + 'HID', + 'HIM', + 'HIP', + 'HIS', + 'HIT', + 'HO', + 'HOB', + 'HOC', + 'HOE', + 'HOG', + 'HOP', + 'HOT', + 'HOW', + 'HUB', + 'HUE', + 'HUG', + 'HUH', + 'HUM', + 'HUT', + 'I', + 'ICY', + 'IDA', + 'IF', + 'IKE', + 'ILL', + 'INK', + 'INN', + 'IO', + 'ION', + 'IQ', + 'IRA', + 'IRE', + 'IRK', + 'IS', + 'IT', + 'ITS', + 'IVY', + 'JAB', + 'JAG', + 'JAM', + 'JAN', + 'JAR', + 'JAW', + 'JAY', + 'JET', + 'JIG', + 'JIM', + 'JO', + 'JOB', + 'JOE', + 'JOG', + 'JOT', + 'JOY', + 'JUG', + 'JUT', + 'KAY', + 'KEG', + 'KEN', + 'KEY', + 'KID', + 'KIM', + 'KIN', + 'KIT', + 'LA', + 'LAB', + 'LAC', + 'LAD', + 'LAG', + 'LAM', + 'LAP', + 'LAW', + 'LAY', + 'LEA', + 'LED', + 'LEE', + 'LEG', + 'LEN', + 'LEO', + 'LET', + 'LEW', + 'LID', + 'LIE', + 'LIN', + 'LIP', + 'LIT', + 'LO', + 'LOB', + 'LOG', + 'LOP', + 'LOS', + 'LOT', + 'LOU', + 'LOW', + 'LOY', + 'LUG', + 'LYE', + 'MA', + 'MAC', + 'MAD', + 'MAE', + 'MAN', + 'MAO', + 'MAP', + 'MAT', + 'MAW', + 'MAY', + 'ME', + 'MEG', + 'MEL', + 'MEN', + 'MET', + 'MEW', + 'MID', + 'MIN', + 'MIT', + 'MOB', + 'MOD', + 'MOE', + 'MOO', + 'MOP', + 'MOS', + 'MOT', + 'MOW', + 'MUD', + 'MUG', + 'MUM', + 'MY', + 'NAB', + 'NAG', + 'NAN', + 'NAP', + 'NAT', + 'NAY', + 'NE', + 'NED', + 'NEE', + 'NET', + 'NEW', + 'NIB', + 'NIL', + 'NIP', + 'NIT', + 'NO', + 'NOB', + 'NOD', + 'NON', + 'NOR', + 'NOT', + 'NOV', + 'NOW', + 'NU', + 'NUN', + 'NUT', + 'O', + 'OAF', + 'OAK', + 'OAR', + 'OAT', + 'ODD', + 'ODE', + 'OF', + 'OFF', + 'OFT', + 'OH', + 'OIL', + 'OK', + 'OLD', + 'ON', + 'ONE', + 'OR', + 'ORB', + 'ORE', + 'ORR', + 'OS', + 'OTT', + 'OUR', + 'OUT', + 'OVA', + 'OW', + 'OWE', + 'OWL', + 'OWN', + 'OX', + 'PA', + 'PAD', + 'PAL', + 'PAM', + 'PAN', + 'PAP', + 'PAR', + 'PAT', + 'PAW', + 'PAY', + 'PEA', + 'PEG', + 'PEN', + 'PEP', + 'PER', + 'PET', + 'PEW', + 'PHI', + 'PI', + 'PIE', + 'PIN', + 'PIT', + 'PLY', + 'PO', + 'POD', + 'POE', + 'POP', + 'POT', + 'POW', + 'PRO', + 'PRY', + 'PUB', + 'PUG', + 'PUN', + 'PUP', + 'PUT', + 'QUO', + 'RAG', + 'RAM', + 'RAN', + 'RAP', + 'RAT', + 'RAW', + 'RAY', + 'REB', + 'RED', + 'REP', + 'RET', + 'RIB', + 'RID', + 'RIG', + 'RIM', + 'RIO', + 'RIP', + 'ROB', + 'ROD', + 'ROE', + 'RON', + 'ROT', + 'ROW', + 'ROY', + 'RUB', + 'RUE', + 'RUG', + 'RUM', + 'RUN', + 'RYE', + 'SAC', + 'SAD', + 'SAG', + 'SAL', + 'SAM', + 'SAN', + 'SAP', + 'SAT', + 'SAW', + 'SAY', + 'SEA', + 'SEC', + 'SEE', + 'SEN', + 'SET', + 'SEW', + 'SHE', + 'SHY', + 'SIN', + 'SIP', + 'SIR', + 'SIS', + 'SIT', + 'SKI', + 'SKY', + 'SLY', + 'SO', + 'SOB', + 'SOD', + 'SON', + 'SOP', + 'SOW', + 'SOY', + 'SPA', + 'SPY', + 'SUB', + 'SUD', + 'SUE', + 'SUM', + 'SUN', + 'SUP', + 'TAB', + 'TAD', + 'TAG', + 'TAN', + 'TAP', + 'TAR', + 'TEA', + 'TED', + 'TEE', + 'TEN', + 'THE', + 'THY', + 'TIC', + 'TIE', + 'TIM', + 'TIN', + 'TIP', + 'TO', + 'TOE', + 'TOG', + 'TOM', + 'TON', + 'TOO', + 'TOP', + 'TOW', + 'TOY', + 'TRY', + 'TUB', + 'TUG', + 'TUM', + 'TUN', + 'TWO', + 'UN', + 'UP', + 'US', + 'USE', + 'VAN', + 'VAT', + 'VET', + 'VIE', + 'WAD', + 'WAG', + 'WAR', + 'WAS', + 'WAY', + 'WE', + 'WEB', + 'WED', + 'WEE', + 'WET', + 'WHO', + 'WHY', + 'WIN', + 'WIT', + 'WOK', + 'WON', + 'WOO', + 'WOW', + 'WRY', + 'WU', + 'YAM', + 'YAP', + 'YAW', + 'YE', + 'YEA', + 'YES', + 'YET', + 'YOU', + 'ABED', + 'ABEL', + 'ABET', + 'ABLE', + 'ABUT', + 'ACHE', + 'ACID', + 'ACME', + 'ACRE', + 'ACTA', + 'ACTS', + 'ADAM', + 'ADDS', + 'ADEN', + 'AFAR', + 'AFRO', + 'AGEE', + 'AHEM', + 'AHOY', + 'AIDA', + 'AIDE', + 'AIDS', + 'AIRY', + 'AJAR', + 'AKIN', + 'ALAN', + 'ALEC', + 'ALGA', + 'ALIA', + 'ALLY', + 'ALMA', + 'ALOE', + 'ALSO', + 'ALTO', + 'ALUM', + 'ALVA', + 'AMEN', + 'AMES', + 'AMID', + 'AMMO', + 'AMOK', + 'AMOS', + 'AMRA', + 'ANDY', + 'ANEW', + 'ANNA', + 'ANNE', + 'ANTE', + 'ANTI', + 'AQUA', + 'ARAB', + 'ARCH', + 'AREA', + 'ARGO', + 'ARID', + 'ARMY', + 'ARTS', + 'ARTY', + 'ASIA', + 'ASKS', + 'ATOM', + 'AUNT', + 'AURA', + 'AUTO', + 'AVER', + 'AVID', + 'AVIS', + 'AVON', + 'AVOW', + 'AWAY', + 'AWRY', + 'BABE', + 'BABY', + 'BACH', + 'BACK', + 'BADE', + 'BAIL', + 'BAIT', + 'BAKE', + 'BALD', + 'BALE', + 'BALI', + 'BALK', + 'BALL', + 'BALM', + 'BAND', + 'BANE', + 'BANG', + 'BANK', + 'BARB', + 'BARD', + 'BARE', + 'BARK', + 'BARN', + 'BARR', + 'BASE', + 'BASH', + 'BASK', + 'BASS', + 'BATE', + 'BATH', + 'BAWD', + 'BAWL', + 'BEAD', + 'BEAK', + 'BEAM', + 'BEAN', + 'BEAR', + 'BEAT', + 'BEAU', + 'BECK', + 'BEEF', + 'BEEN', + 'BEER', + 'BEET', + 'BELA', + 'BELL', + 'BELT', + 'BEND', + 'BENT', + 'BERG', + 'BERN', + 'BERT', + 'BESS', + 'BEST', + 'BETA', + 'BETH', + 'BHOY', + 'BIAS', + 'BIDE', + 'BIEN', + 'BILE', + 'BILK', + 'BILL', + 'BIND', + 'BING', + 'BIRD', + 'BITE', + 'BITS', + 'BLAB', + 'BLAT', + 'BLED', + 'BLEW', + 'BLOB', + 'BLOC', + 'BLOT', + 'BLOW', + 'BLUE', + 'BLUM', + 'BLUR', + 'BOAR', + 'BOAT', + 'BOCA', + 'BOCK', + 'BODE', + 'BODY', + 'BOGY', + 'BOHR', + 'BOIL', + 'BOLD', + 'BOLO', + 'BOLT', + 'BOMB', + 'BONA', + 'BOND', + 'BONE', + 'BONG', + 'BONN', + 'BONY', + 'BOOK', + 'BOOM', + 'BOON', + 'BOOT', + 'BORE', + 'BORG', + 'BORN', + 'BOSE', + 'BOSS', + 'BOTH', + 'BOUT', + 'BOWL', + 'BOYD', + 'BRAD', + 'BRAE', + 'BRAG', + 'BRAN', + 'BRAY', + 'BRED', + 'BREW', + 'BRIG', + 'BRIM', + 'BROW', + 'BUCK', + 'BUDD', + 'BUFF', + 'BULB', + 'BULK', + 'BULL', + 'BUNK', + 'BUNT', + 'BUOY', + 'BURG', + 'BURL', + 'BURN', + 'BURR', + 'BURT', + 'BURY', + 'BUSH', + 'BUSS', + 'BUST', + 'BUSY', + 'BYTE', + 'CADY', + 'CAFE', + 'CAGE', + 'CAIN', + 'CAKE', + 'CALF', + 'CALL', + 'CALM', + 'CAME', + 'CANE', + 'CANT', + 'CARD', + 'CARE', + 'CARL', + 'CARR', + 'CART', + 'CASE', + 'CASH', + 'CASK', + 'CAST', + 'CAVE', + 'CEIL', + 'CELL', + 'CENT', + 'CERN', + 'CHAD', + 'CHAR', + 'CHAT', + 'CHAW', + 'CHEF', + 'CHEN', + 'CHEW', + 'CHIC', + 'CHIN', + 'CHOU', + 'CHOW', + 'CHUB', + 'CHUG', + 'CHUM', + 'CITE', + 'CITY', + 'CLAD', + 'CLAM', + 'CLAN', + 'CLAW', + 'CLAY', + 'CLOD', + 'CLOG', + 'CLOT', + 'CLUB', + 'CLUE', + 'COAL', + 'COAT', + 'COCA', + 'COCK', + 'COCO', + 'CODA', + 'CODE', + 'CODY', + 'COED', + 'COIL', + 'COIN', + 'COKE', + 'COLA', + 'COLD', + 'COLT', + 'COMA', + 'COMB', + 'COME', + 'COOK', + 'COOL', + 'COON', + 'COOT', + 'CORD', + 'CORE', + 'CORK', + 'CORN', + 'COST', + 'COVE', + 'COWL', + 'CRAB', + 'CRAG', + 'CRAM', + 'CRAY', + 'CREW', + 'CRIB', + 'CROW', + 'CRUD', + 'CUBA', + 'CUBE', + 'CUFF', + 'CULL', + 'CULT', + 'CUNY', + 'CURB', + 'CURD', + 'CURE', + 'CURL', + 'CURT', + 'CUTS', + 'DADE', + 'DALE', + 'DAME', + 'DANA', + 'DANE', + 'DANG', + 'DANK', + 'DARE', + 'DARK', + 'DARN', + 'DART', + 'DASH', + 'DATA', + 'DATE', + 'DAVE', + 'DAVY', + 'DAWN', + 'DAYS', + 'DEAD', + 'DEAF', + 'DEAL', + 'DEAN', + 'DEAR', + 'DEBT', + 'DECK', + 'DEED', + 'DEEM', + 'DEER', + 'DEFT', + 'DEFY', + 'DELL', + 'DENT', + 'DENY', + 'DESK', + 'DIAL', + 'DICE', + 'DIED', + 'DIET', + 'DIME', + 'DINE', + 'DING', + 'DINT', + 'DIRE', + 'DIRT', + 'DISC', + 'DISH', + 'DISK', + 'DIVE', + 'DOCK', + 'DOES', + 'DOLE', + 'DOLL', + 'DOLT', + 'DOME', + 'DONE', + 'DOOM', + 'DOOR', + 'DORA', + 'DOSE', + 'DOTE', + 'DOUG', + 'DOUR', + 'DOVE', + 'DOWN', + 'DRAB', + 'DRAG', + 'DRAM', + 'DRAW', + 'DREW', + 'DRUB', + 'DRUG', + 'DRUM', + 'DUAL', + 'DUCK', + 'DUCT', + 'DUEL', + 'DUET', + 'DUKE', + 'DULL', + 'DUMB', + 'DUNE', + 'DUNK', + 'DUSK', + 'DUST', + 'DUTY', + 'EACH', + 'EARL', + 'EARN', + 'EASE', + 'EAST', + 'EASY', + 'EBEN', + 'ECHO', + 'EDDY', + 'EDEN', + 'EDGE', + 'EDGY', + 'EDIT', + 'EDNA', + 'EGAN', + 'ELAN', + 'ELBA', + 'ELLA', + 'ELSE', + 'EMIL', + 'EMIT', + 'EMMA', + 'ENDS', + 'ERIC', + 'EROS', + 'EVEN', + 'EVER', + 'EVIL', + 'EYED', + 'FACE', + 'FACT', + 'FADE', + 'FAIL', + 'FAIN', + 'FAIR', + 'FAKE', + 'FALL', + 'FAME', + 'FANG', + 'FARM', + 'FAST', + 'FATE', + 'FAWN', + 'FEAR', + 'FEAT', + 'FEED', + 'FEEL', + 'FEET', + 'FELL', + 'FELT', + 'FEND', + 'FERN', + 'FEST', + 'FEUD', + 'FIEF', + 'FIGS', + 'FILE', + 'FILL', + 'FILM', + 'FIND', + 'FINE', + 'FINK', + 'FIRE', + 'FIRM', + 'FISH', + 'FISK', + 'FIST', + 'FITS', + 'FIVE', + 'FLAG', + 'FLAK', + 'FLAM', + 'FLAT', + 'FLAW', + 'FLEA', + 'FLED', + 'FLEW', + 'FLIT', + 'FLOC', + 'FLOG', + 'FLOW', + 'FLUB', + 'FLUE', + 'FOAL', + 'FOAM', + 'FOGY', + 'FOIL', + 'FOLD', + 'FOLK', + 'FOND', + 'FONT', + 'FOOD', + 'FOOL', + 'FOOT', + 'FORD', + 'FORE', + 'FORK', + 'FORM', + 'FORT', + 'FOSS', + 'FOUL', + 'FOUR', + 'FOWL', + 'FRAU', + 'FRAY', + 'FRED', + 'FREE', + 'FRET', + 'FREY', + 'FROG', + 'FROM', + 'FUEL', + 'FULL', + 'FUME', + 'FUND', + 'FUNK', + 'FURY', + 'FUSE', + 'FUSS', + 'GAFF', + 'GAGE', + 'GAIL', + 'GAIN', + 'GAIT', + 'GALA', + 'GALE', + 'GALL', + 'GALT', + 'GAME', + 'GANG', + 'GARB', + 'GARY', + 'GASH', + 'GATE', + 'GAUL', + 'GAUR', + 'GAVE', + 'GAWK', + 'GEAR', + 'GELD', + 'GENE', + 'GENT', + 'GERM', + 'GETS', + 'GIBE', + 'GIFT', + 'GILD', + 'GILL', + 'GILT', + 'GINA', + 'GIRD', + 'GIRL', + 'GIST', + 'GIVE', + 'GLAD', + 'GLEE', + 'GLEN', + 'GLIB', + 'GLOB', + 'GLOM', + 'GLOW', + 'GLUE', + 'GLUM', + 'GLUT', + 'GOAD', + 'GOAL', + 'GOAT', + 'GOER', + 'GOES', + 'GOLD', + 'GOLF', + 'GONE', + 'GONG', + 'GOOD', + 'GOOF', + 'GORE', + 'GORY', + 'GOSH', + 'GOUT', + 'GOWN', + 'GRAB', + 'GRAD', + 'GRAY', + 'GREG', + 'GREW', + 'GREY', + 'GRID', + 'GRIM', + 'GRIN', + 'GRIT', + 'GROW', + 'GRUB', + 'GULF', + 'GULL', + 'GUNK', + 'GURU', + 'GUSH', + 'GUST', + 'GWEN', + 'GWYN', + 'HAAG', + 'HAAS', + 'HACK', + 'HAIL', + 'HAIR', + 'HALE', + 'HALF', + 'HALL', + 'HALO', + 'HALT', + 'HAND', + 'HANG', + 'HANK', + 'HANS', + 'HARD', + 'HARK', + 'HARM', + 'HART', + 'HASH', + 'HAST', + 'HATE', + 'HATH', + 'HAUL', + 'HAVE', + 'HAWK', + 'HAYS', + 'HEAD', + 'HEAL', + 'HEAR', + 'HEAT', + 'HEBE', + 'HECK', + 'HEED', + 'HEEL', + 'HEFT', + 'HELD', + 'HELL', + 'HELM', + 'HERB', + 'HERD', + 'HERE', + 'HERO', + 'HERS', + 'HESS', + 'HEWN', + 'HICK', + 'HIDE', + 'HIGH', + 'HIKE', + 'HILL', + 'HILT', + 'HIND', + 'HINT', + 'HIRE', + 'HISS', + 'HIVE', + 'HOBO', + 'HOCK', + 'HOFF', + 'HOLD', + 'HOLE', + 'HOLM', + 'HOLT', + 'HOME', + 'HONE', + 'HONK', + 'HOOD', + 'HOOF', + 'HOOK', + 'HOOT', + 'HORN', + 'HOSE', + 'HOST', + 'HOUR', + 'HOVE', + 'HOWE', + 'HOWL', + 'HOYT', + 'HUCK', + 'HUED', + 'HUFF', + 'HUGE', + 'HUGH', + 'HUGO', + 'HULK', + 'HULL', + 'HUNK', + 'HUNT', + 'HURD', + 'HURL', + 'HURT', + 'HUSH', + 'HYDE', + 'HYMN', + 'IBIS', + 'ICON', + 'IDEA', + 'IDLE', + 'IFFY', + 'INCA', + 'INCH', + 'INTO', + 'IONS', + 'IOTA', + 'IOWA', + 'IRIS', + 'IRMA', + 'IRON', + 'ISLE', + 'ITCH', + 'ITEM', + 'IVAN', + 'JACK', + 'JADE', + 'JAIL', + 'JAKE', + 'JANE', + 'JAVA', + 'JEAN', + 'JEFF', + 'JERK', + 'JESS', + 'JEST', + 'JIBE', + 'JILL', + 'JILT', + 'JIVE', + 'JOAN', + 'JOBS', + 'JOCK', + 'JOEL', + 'JOEY', + 'JOHN', + 'JOIN', + 'JOKE', + 'JOLT', + 'JOVE', + 'JUDD', + 'JUDE', + 'JUDO', + 'JUDY', + 'JUJU', + 'JUKE', + 'JULY', + 'JUNE', + 'JUNK', + 'JUNO', + 'JURY', + 'JUST', + 'JUTE', + 'KAHN', + 'KALE', + 'KANE', + 'KANT', + 'KARL', + 'KATE', + 'KEEL', + 'KEEN', + 'KENO', + 'KENT', + 'KERN', + 'KERR', + 'KEYS', + 'KICK', + 'KILL', + 'KIND', + 'KING', + 'KIRK', + 'KISS', + 'KITE', + 'KLAN', + 'KNEE', + 'KNEW', + 'KNIT', + 'KNOB', + 'KNOT', + 'KNOW', + 'KOCH', + 'KONG', + 'KUDO', + 'KURD', + 'KURT', + 'KYLE', + 'LACE', + 'LACK', + 'LACY', + 'LADY', + 'LAID', + 'LAIN', + 'LAIR', + 'LAKE', + 'LAMB', + 'LAME', + 'LAND', + 'LANE', + 'LANG', + 'LARD', + 'LARK', + 'LASS', + 'LAST', + 'LATE', + 'LAUD', + 'LAVA', + 'LAWN', + 'LAWS', + 'LAYS', + 'LEAD', + 'LEAF', + 'LEAK', + 'LEAN', + 'LEAR', + 'LEEK', + 'LEER', + 'LEFT', + 'LEND', + 'LENS', + 'LENT', + 'LEON', + 'LESK', + 'LESS', + 'LEST', + 'LETS', + 'LIAR', + 'LICE', + 'LICK', + 'LIED', + 'LIEN', + 'LIES', + 'LIEU', + 'LIFE', + 'LIFT', + 'LIKE', + 'LILA', + 'LILT', + 'LILY', + 'LIMA', + 'LIMB', + 'LIME', + 'LIND', + 'LINE', + 'LINK', + 'LINT', + 'LION', + 'LISA', + 'LIST', + 'LIVE', + 'LOAD', + 'LOAF', + 'LOAM', + 'LOAN', + 'LOCK', + 'LOFT', + 'LOGE', + 'LOIS', + 'LOLA', + 'LONE', + 'LONG', + 'LOOK', + 'LOON', + 'LOOT', + 'LORD', + 'LORE', + 'LOSE', + 'LOSS', + 'LOST', + 'LOUD', + 'LOVE', + 'LOWE', + 'LUCK', + 'LUCY', + 'LUGE', + 'LUKE', + 'LULU', + 'LUND', + 'LUNG', + 'LURA', + 'LURE', + 'LURK', + 'LUSH', + 'LUST', + 'LYLE', + 'LYNN', + 'LYON', + 'LYRA', + 'MACE', + 'MADE', + 'MAGI', + 'MAID', + 'MAIL', + 'MAIN', + 'MAKE', + 'MALE', + 'MALI', + 'MALL', + 'MALT', + 'MANA', + 'MANN', + 'MANY', + 'MARC', + 'MARE', + 'MARK', + 'MARS', + 'MART', + 'MARY', + 'MASH', + 'MASK', + 'MASS', + 'MAST', + 'MATE', + 'MATH', + 'MAUL', + 'MAYO', + 'MEAD', + 'MEAL', + 'MEAN', + 'MEAT', + 'MEEK', + 'MEET', + 'MELD', + 'MELT', + 'MEMO', + 'MEND', + 'MENU', + 'MERT', + 'MESH', + 'MESS', + 'MICE', + 'MIKE', + 'MILD', + 'MILE', + 'MILK', + 'MILL', + 'MILT', + 'MIMI', + 'MIND', + 'MINE', + 'MINI', + 'MINK', + 'MINT', + 'MIRE', + 'MISS', + 'MIST', + 'MITE', + 'MITT', + 'MOAN', + 'MOAT', + 'MOCK', + 'MODE', + 'MOLD', + 'MOLE', + 'MOLL', + 'MOLT', + 'MONA', + 'MONK', + 'MONT', + 'MOOD', + 'MOON', + 'MOOR', + 'MOOT', + 'MORE', + 'MORN', + 'MORT', + 'MOSS', + 'MOST', + 'MOTH', + 'MOVE', + 'MUCH', + 'MUCK', + 'MUDD', + 'MUFF', + 'MULE', + 'MULL', + 'MURK', + 'MUSH', + 'MUST', + 'MUTE', + 'MUTT', + 'MYRA', + 'MYTH', + 'NAGY', + 'NAIL', + 'NAIR', + 'NAME', + 'NARY', + 'NASH', + 'NAVE', + 'NAVY', + 'NEAL', + 'NEAR', + 'NEAT', + 'NECK', + 'NEED', + 'NEIL', + 'NELL', + 'NEON', + 'NERO', + 'NESS', + 'NEST', + 'NEWS', + 'NEWT', + 'NIBS', + 'NICE', + 'NICK', + 'NILE', + 'NINA', + 'NINE', + 'NOAH', + 'NODE', + 'NOEL', + 'NOLL', + 'NONE', + 'NOOK', + 'NOON', + 'NORM', + 'NOSE', + 'NOTE', + 'NOUN', + 'NOVA', + 'NUDE', + 'NULL', + 'NUMB', + 'OATH', + 'OBEY', + 'OBOE', + 'ODIN', + 'OHIO', + 'OILY', + 'OINT', + 'OKAY', + 'OLAF', + 'OLDY', + 'OLGA', + 'OLIN', + 'OMAN', + 'OMEN', + 'OMIT', + 'ONCE', + 'ONES', + 'ONLY', + 'ONTO', + 'ONUS', + 'ORAL', + 'ORGY', + 'OSLO', + 'OTIS', + 'OTTO', + 'OUCH', + 'OUST', + 'OUTS', + 'OVAL', + 'OVEN', + 'OVER', + 'OWLY', + 'OWNS', + 'QUAD', + 'QUIT', + 'QUOD', + 'RACE', + 'RACK', + 'RACY', + 'RAFT', + 'RAGE', + 'RAID', + 'RAIL', + 'RAIN', + 'RAKE', + 'RANK', + 'RANT', + 'RARE', + 'RASH', + 'RATE', + 'RAVE', + 'RAYS', + 'READ', + 'REAL', + 'REAM', + 'REAR', + 'RECK', + 'REED', + 'REEF', + 'REEK', + 'REEL', + 'REID', + 'REIN', + 'RENA', + 'REND', + 'RENT', + 'REST', + 'RICE', + 'RICH', + 'RICK', + 'RIDE', + 'RIFT', + 'RILL', + 'RIME', + 'RING', + 'RINK', + 'RISE', + 'RISK', + 'RITE', + 'ROAD', + 'ROAM', + 'ROAR', + 'ROBE', + 'ROCK', + 'RODE', + 'ROIL', + 'ROLL', + 'ROME', + 'ROOD', + 'ROOF', + 'ROOK', + 'ROOM', + 'ROOT', + 'ROSA', + 'ROSE', + 'ROSS', + 'ROSY', + 'ROTH', + 'ROUT', + 'ROVE', + 'ROWE', + 'ROWS', + 'RUBE', + 'RUBY', + 'RUDE', + 'RUDY', + 'RUIN', + 'RULE', + 'RUNG', + 'RUNS', + 'RUNT', + 'RUSE', + 'RUSH', + 'RUSK', + 'RUSS', + 'RUST', + 'RUTH', + 'SACK', + 'SAFE', + 'SAGE', + 'SAID', + 'SAIL', + 'SALE', + 'SALK', + 'SALT', + 'SAME', + 'SAND', + 'SANE', + 'SANG', + 'SANK', + 'SARA', + 'SAUL', + 'SAVE', + 'SAYS', + 'SCAN', + 'SCAR', + 'SCAT', + 'SCOT', + 'SEAL', + 'SEAM', + 'SEAR', + 'SEAT', + 'SEED', + 'SEEK', + 'SEEM', + 'SEEN', + 'SEES', + 'SELF', + 'SELL', + 'SEND', + 'SENT', + 'SETS', + 'SEWN', + 'SHAG', + 'SHAM', + 'SHAW', + 'SHAY', + 'SHED', + 'SHIM', + 'SHIN', + 'SHOD', + 'SHOE', + 'SHOT', + 'SHOW', + 'SHUN', + 'SHUT', + 'SICK', + 'SIDE', + 'SIFT', + 'SIGH', + 'SIGN', + 'SILK', + 'SILL', + 'SILO', + 'SILT', + 'SINE', + 'SING', + 'SINK', + 'SIRE', + 'SITE', + 'SITS', + 'SITU', + 'SKAT', + 'SKEW', + 'SKID', + 'SKIM', + 'SKIN', + 'SKIT', + 'SLAB', + 'SLAM', + 'SLAT', + 'SLAY', + 'SLED', + 'SLEW', + 'SLID', + 'SLIM', + 'SLIT', + 'SLOB', + 'SLOG', + 'SLOT', + 'SLOW', + 'SLUG', + 'SLUM', + 'SLUR', + 'SMOG', + 'SMUG', + 'SNAG', + 'SNOB', + 'SNOW', + 'SNUB', + 'SNUG', + 'SOAK', + 'SOAR', + 'SOCK', + 'SODA', + 'SOFA', + 'SOFT', + 'SOIL', + 'SOLD', + 'SOME', + 'SONG', + 'SOON', + 'SOOT', + 'SORE', + 'SORT', + 'SOUL', + 'SOUR', + 'SOWN', + 'STAB', + 'STAG', + 'STAN', + 'STAR', + 'STAY', + 'STEM', + 'STEW', + 'STIR', + 'STOW', + 'STUB', + 'STUN', + 'SUCH', + 'SUDS', + 'SUIT', + 'SULK', + 'SUMS', + 'SUNG', + 'SUNK', + 'SURE', + 'SURF', + 'SWAB', + 'SWAG', + 'SWAM', + 'SWAN', + 'SWAT', + 'SWAY', + 'SWIM', + 'SWUM', + 'TACK', + 'TACT', + 'TAIL', + 'TAKE', + 'TALE', + 'TALK', + 'TALL', + 'TANK', + 'TASK', + 'TATE', + 'TAUT', + 'TEAL', + 'TEAM', + 'TEAR', + 'TECH', + 'TEEM', + 'TEEN', + 'TEET', + 'TELL', + 'TEND', + 'TENT', + 'TERM', + 'TERN', + 'TESS', + 'TEST', + 'THAN', + 'THAT', + 'THEE', + 'THEM', + 'THEN', + 'THEY', + 'THIN', + 'THIS', + 'THUD', + 'THUG', + 'TICK', + 'TIDE', + 'TIDY', + 'TIED', + 'TIER', + 'TILE', + 'TILL', + 'TILT', + 'TIME', + 'TINA', + 'TINE', + 'TINT', + 'TINY', + 'TIRE', + 'TOAD', + 'TOGO', + 'TOIL', + 'TOLD', + 'TOLL', + 'TONE', + 'TONG', + 'TONY', + 'TOOK', + 'TOOL', + 'TOOT', + 'TORE', + 'TORN', + 'TOTE', + 'TOUR', + 'TOUT', + 'TOWN', + 'TRAG', + 'TRAM', + 'TRAY', + 'TREE', + 'TREK', + 'TRIG', + 'TRIM', + 'TRIO', + 'TROD', + 'TROT', + 'TROY', + 'TRUE', + 'TUBA', + 'TUBE', + 'TUCK', + 'TUFT', + 'TUNA', + 'TUNE', + 'TUNG', + 'TURF', + 'TURN', + 'TUSK', + 'TWIG', + 'TWIN', + 'TWIT', + 'ULAN', + 'UNIT', + 'URGE', + 'USED', + 'USER', + 'USES', + 'UTAH', + 'VAIL', + 'VAIN', + 'VALE', + 'VARY', + 'VASE', + 'VAST', + 'VEAL', + 'VEDA', + 'VEIL', + 'VEIN', + 'VEND', + 'VENT', + 'VERB', + 'VERY', + 'VETO', + 'VICE', + 'VIEW', + 'VINE', + 'VISE', + 'VOID', + 'VOLT', + 'VOTE', + 'WACK', + 'WADE', + 'WAGE', + 'WAIL', + 'WAIT', + 'WAKE', + 'WALE', + 'WALK', + 'WALL', + 'WALT', + 'WAND', + 'WANE', + 'WANG', + 'WANT', + 'WARD', + 'WARM', + 'WARN', + 'WART', + 'WASH', + 'WAST', + 'WATS', + 'WATT', + 'WAVE', + 'WAVY', + 'WAYS', + 'WEAK', + 'WEAL', + 'WEAN', + 'WEAR', + 'WEED', + 'WEEK', + 'WEIR', + 'WELD', + 'WELL', + 'WELT', + 'WENT', + 'WERE', + 'WERT', + 'WEST', + 'WHAM', + 'WHAT', + 'WHEE', + 'WHEN', + 'WHET', + 'WHOA', + 'WHOM', + 'WICK', + 'WIFE', + 'WILD', + 'WILL', + 'WIND', + 'WINE', + 'WING', + 'WINK', + 'WINO', + 'WIRE', + 'WISE', + 'WISH', + 'WITH', + 'WOLF', + 'WONT', + 'WOOD', + 'WOOL', + 'WORD', + 'WORE', + 'WORK', + 'WORM', + 'WORN', + 'WOVE', + 'WRIT', + 'WYNN', + 'YALE', + 'YANG', + 'YANK', + 'YARD', + 'YARN', + 'YAWL', + 'YAWN', + 'YEAH', + 'YEAR', + 'YELL', + 'YOGA', + 'YOKE', +] _ALGO_DICT = {OTP_ALGO_MD5: 'md5', OTP_ALGO_SHA1: 'sha1'} -class OTPGeneratorException(Exception): - """OTPGeneratorException class""" +class OTPGeneratorError(Exception): + """OTPGeneratorError class""" -class OTPChallengeException(Exception): - """OTPChallengeException class""" +class OTPChallengeError(Exception): + """OTPChallengeError class""" class OTPGenerator: """OTPGenerator class""" def __init__( - self, - password: bytes, - seed: str = '', - hash_algo=OTP_ALGO_MD5, + self, password: bytes, seed: str = '', hash_algo=OTP_ALGO_MD5 ): """ Constructs an OTPGenerator object with a given password and seed. @@ -323,7 +2112,7 @@ class OTPGenerator: :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 :type hash_algo: int or str - :raises otp2289.OTPGeneratorException: If the input does not validate + :raises otp2289.OTPGeneratorError: If the input does not validate """ # enforce the rfc2289 constraints self._seed = seed @@ -331,11 +2120,9 @@ class OTPGenerator: self._seed = self.validate_seed(self._seed) self._hash_algo = self.validate_hash_algo(hash_algo) if not isinstance(password, bytes): - raise OTPGeneratorException('Password must be a byte-string') + raise OTPGeneratorError('Password must be a byte-string') if len(password) < 10: - raise OTPGeneratorException( - 'Password must be longer than 10 bytes' - ) + raise OTPGeneratorError('Password must be longer than 10 bytes') self._password = password def __repr__(self): @@ -357,11 +2144,11 @@ class OTPGenerator: :rtype: int """ if not isinstance(bit_stream, str): - raise OTPGeneratorException('bit_stream must be of type str') + raise OTPGeneratorError('bit_stream must be of type str') if len(bit_stream) != 64: - raise OTPGeneratorException('bit_stream must be of size 64') + raise OTPGeneratorError('bit_stream must be of size 64') value = 0 - for pair in zip(bit_stream[::2], bit_stream[1::2]): + for pair in zip(bit_stream[::2], bit_stream[1::2], strict=True): value += int(''.join(pair), 2) return value @@ -386,10 +2173,7 @@ class OTPGenerator: tokens.append(RFC1760_TOKENS[int(bit_stream[44:55], 2)]) tokens.append( RFC1760_TOKENS[ - int( - bit_stream[55:64] + f'{bit_pair_sum:0>8b}'[-2:], - 2, - ) + int(bit_stream[55:64] + f'{bit_pair_sum:0>8b}'[-2:], 2) ] ) return ' '.join(tokens) @@ -404,21 +2188,21 @@ class OTPGenerator: :param challenge: The challenge string described in RFC-2289 :type challenge: str - :raises otp2289.OTPChallengeException: If the challenge is invalid + :raises otp2289.OTPChallengeError: If the challenge is invalid :return: (seed, hash_algo, step) tuple. :rtype: tuple """ if not isinstance(challenge, str): - raise OTPChallengeException('Challenge must be str') + raise OTPChallengeError('Challenge must be str') challenge = challenge.strip() if not challenge.startswith('otp-'): - raise OTPChallengeException('Invalid challenge') + raise OTPChallengeError('Invalid challenge') try: hash_algo, step, seed = challenge[4:].split() return (seed, hash_algo, int(step)) except ValueError: - raise OTPChallengeException('Invalid challenge') from None + raise OTPChallengeError('Invalid challenge') from None @staticmethod def sha1_digest_folding(sha1_digest: bytes) -> bytes: @@ -432,10 +2216,10 @@ class OTPGenerator: :return: The byte-string representing the folded sha1-digest :rtype: bytes """ - if not (isinstance(sha1_digest, bytes)): - raise OTPGeneratorException('sha1_digest must be of type bytes') + if not isinstance(sha1_digest, bytes): + raise OTPGeneratorError('sha1_digest must be of type bytes') if len(sha1_digest) != 20: - raise OTPGeneratorException( + raise OTPGeneratorError( 'sha1_digest must be 160 bits (20 bytes) long' ) digested = list(5 * b'i') # 5 bytes (40 bits) @@ -485,12 +2269,12 @@ class OTPGenerator: :rtype: bytes """ if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)): - raise OTPGeneratorException( + raise OTPGeneratorError( 'byte_str1 and byte_str2 must be of type bytes' ) length = len(byte_str1) if length != len(byte_str2) or length < 1: - raise OTPGeneratorException( + raise OTPGeneratorError( 'byte_str1 and byte_str2 must be of the same length > 0' ) return bytes([byte_str1[i] ^ byte_str2[i] for i in range(length)]) @@ -503,25 +2287,23 @@ class OTPGenerator: :param tokens_str: String representing 6 words tokens :type tokens_str: str - :raises otp2289.OTPGeneratorException: When the tokens_str is invalid + :raises otp2289.OTPGeneratorError: When the tokens_str is invalid :return: 6 words tokens :rtype: bytes """ if not isinstance(tokens_str, str): - raise OTPGeneratorException('tokens must be a str') + raise OTPGeneratorError('tokens must be a str') tokens = tokens_str.split() if len(tokens) != 6: - raise OTPGeneratorException( - 'Tokens-string does not contain 6 tokens' - ) + raise OTPGeneratorError('Tokens-string does not contain 6 tokens') token_ints = [] try: token_ints = [ RFC1760_TOKENS.index(token.upper()) for token in tokens ] except ValueError: - raise OTPGeneratorException( + raise OTPGeneratorError( 'One or more words not present in RFC1760' ) from None # now we build a string of bits @@ -539,7 +2321,7 @@ class OTPGenerator: f'{OTPGenerator.bit_pair_sum(bit_stream[:64]):0>8b}'[-2:] != bit_stream[-2:] ): - raise OTPGeneratorException('Invalid bit checksum') + raise OTPGeneratorError('Invalid bit checksum') return int(bit_stream[:64], 2).to_bytes(8, 'big') @staticmethod @@ -550,21 +2332,21 @@ class OTPGenerator: :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 :type hash_algo: int or str - :raises otp2289.OTPGeneratorException: If hash_algo does not validate + :raises otp2289.OTPGeneratorError: If hash_algo does not validate :return: The validated hash_algo in str-form :rtype: str """ if isinstance(hash_algo, int): if hash_algo not in _ALGO_DICT: - raise OTPGeneratorException( + raise OTPGeneratorError( 'hash_algo is not among the known algorithms' ) hash_algo = _ALGO_DICT.get(hash_algo) if not isinstance(hash_algo, str): - raise OTPGeneratorException('hash_algo must be an int or a str') + raise OTPGeneratorError('hash_algo must be an int or a str') if hash_algo not in hashlib.algorithms_available: - raise OTPGeneratorException( + raise OTPGeneratorError( f'{hash_algo} is not supported by this version of the ' 'hashlib module' ) @@ -578,20 +2360,20 @@ class OTPGenerator: :param seed: The seed received from the challenge, defaults to '' :type seed: str - :raises otp2289.OTPGeneratorException: If seed does not validate + :raises otp2289.OTPGeneratorError: If seed does not validate :return: The validated (and very same) seed :rtype: str """ if not isinstance(seed, str): - raise OTPGeneratorException('Seed must be a string') + raise OTPGeneratorError('Seed must be a string') if not seed or len(seed) > 16: - raise OTPGeneratorException( + raise OTPGeneratorError( 'The seed MUST be of 1 to 16 characters in length' ) for char in seed: if char not in string.ascii_letters + string.digits: - raise OTPGeneratorException( + raise OTPGeneratorError( 'The seed MUST consist of purely alphanumeric characters' ) return seed @@ -604,15 +2386,15 @@ class OTPGenerator: :param seed: The step received from the challenge :type seed: int - :raises otp2289.OTPGeneratorException: If step does not validate + :raises otp2289.OTPGeneratorError: If step does not validate :return: The validated (and very same) step :rtype: int """ if not isinstance(step, int): - raise OTPGeneratorException('Step value MUST be an int') + raise OTPGeneratorError('Step value MUST be an int') if step < 0: - raise OTPGeneratorException('Step value MUST be >= 0') + raise OTPGeneratorError('Step value MUST be >= 0') return step def generate_otp_hexdigest(self, step: int) -> str: @@ -696,11 +2478,9 @@ class OTPGenerator: :rtype: generator """ if not isinstance(start, int) and isinstance(stop, int): - raise OTPGeneratorException('Step value MUST be an int') + raise OTPGeneratorError('Step value MUST be an int') if start < stop: - raise OTPGeneratorException( - 'Start value can not be lower than stop' - ) + raise OTPGeneratorError('Start value can not be lower than stop') for step in range(start, stop - 1, -1): yield self.generate_otp_hexdigest(step) @@ -719,11 +2499,9 @@ class OTPGenerator: :rtype: generator """ if not isinstance(start, int) and isinstance(stop, int): - raise OTPGeneratorException('Step value MUST be an int') + raise OTPGeneratorError('Step value MUST be an int') if start < stop: - raise OTPGeneratorException( - 'Start value can not be lower than stop' - ) + raise OTPGeneratorError('Start value can not be lower than stop') for step in range(start, stop - 1, -1): yield self.generate_otp_words(step) @@ -754,7 +2532,7 @@ class OTPGenerator: # sha1 160bit -> 64bit folding digest = self.sha1_digest_folding(large_digest) else: - raise OTPGeneratorException( + raise OTPGeneratorError( f'{self._hash_algo} is not supported by this module' ) return digest diff --git a/src/otp2289/server.py b/src/otp2289/server.py index 4305f75..e5ee0f2 100644 --- a/src/otp2289/server.py +++ b/src/otp2289/server.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023 Simeon Simeonov +# Copyright (c) 2020-2025 Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -24,22 +23,23 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """A pure Python implementation of the RFC-2289 OTP server""" + import binascii import hashlib -from .generator import OTP_ALGO_MD5, OTPGenerator, OTPGeneratorException +from .generator import OTP_ALGO_MD5, OTPGenerator, OTPGeneratorError -class OTPStateException(Exception): - """OTPStateException class""" +class OTPStateError(Exception): + """OTPStateError class""" -class OTPStoreException(Exception): - """OTPStoreException class""" +class OTPStoreError(Exception): + """OTPStoreError class""" -class OTPInvalidResponse(Exception): - """OTPInvalidResponse class""" +class OTPInvalidResponseError(Exception): + """OTPInvalidResponseError class""" class OTPState: @@ -52,11 +52,7 @@ class OTPState: """ def __init__( - self, - ot_hex: str, - current_step: int, - seed: str, - hash_algo=OTP_ALGO_MD5, + self, ot_hex: str, current_step: int, seed: str, hash_algo=OTP_ALGO_MD5 ): """ Constructs an OTPState object with the given arguments. @@ -75,15 +71,15 @@ class OTPState: :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 :type hash_algo: int or str - :raises otp2289.OTPStateException: If the input does not validate + :raises otp2289.OTPStateError: If the input does not validate """ # enforce the rfc2289 constraints try: self._seed = OTPGenerator.validate_seed(seed) self._hash_algo = OTPGenerator.validate_hash_algo(hash_algo) self._step = OTPGenerator.validate_step(current_step) - except OTPGeneratorException as exp: - raise OTPStateException(exp.args[0]) from None + except OTPGeneratorError as exp: + raise OTPStateError(exp.args[0]) from None self._current_digest = None if ot_hex is not None: self._current_digest = self.validate_hex(ot_hex) @@ -157,28 +153,28 @@ class OTPState: The method first checks if response is a token and tries to convert it to bytes. If that fails, the method assumes that response is a hex. - If neither of those attempts succeeds OTPInvalidResponse is raised. + If neither of those attempts succeeds OTPInvalidResponseError is raised It is up to the caller to run another iteration and compare the result to an existing digest in this state. :param response: The response to this state (its challenge) :type response: str - :raises otp2289.OTPInvalidResponse: If the response is corrupt/illegal, - but not if it simply does not - validate + :raises otp2289.OTPInvalidResponseError: If the response is + corrupt/illegal, but not if it + simply does not validate :return: The bytes representation of response (if any) :rtype: bytes """ try: return OTPGenerator.tokens_to_bytes(response) - except OTPGeneratorException: + except OTPGeneratorError: # now assume hex... try: return OTPState.validate_hex(response) - except OTPStateException: - raise OTPInvalidResponse( + except OTPStateError: + raise OTPInvalidResponseError( 'The response is neither a valid token or hex' ) from None @@ -190,25 +186,25 @@ class OTPState: :param ot_hex: The one-time hex to validate :type ot_hex: str - :raises otp2289.OTPStateException: If hex does not validate + :raises otp2289.OTPStateError: If hex does not validate :return: The validated hex (without leading 0x) converted to bytes :rtype: bytes """ if not isinstance(ot_hex, str): - raise OTPStateException('OT-hex must be a str') + raise OTPStateError('OT-hex must be a str') if ot_hex.startswith('0x'): ot_hex = ot_hex[2:] ot_hex = ot_hex.strip().lower() if len(ot_hex) != 16: - raise OTPStateException( + raise OTPStateError( 'The length of the hex should be 16 ' '(representing 64 bits digest)' ) try: return binascii.unhexlify(ot_hex) except binascii.Error: - raise OTPStateException('Invalid OT-hex') from None + raise OTPStateError('Invalid OT-hex') from None def get_next_state(self): """ @@ -223,16 +219,11 @@ class OTPState: if self._new_digest_hex is None: return None return OTPState( - self._new_digest_hex, - self._step - 1, - self._seed, - self._hash_algo, + self._new_digest_hex, self._step - 1, self._seed, self._hash_algo ) def response_validates( - self, - response: str, - store_valid_response: str = True, + self, response: str, store_valid_response: str = True ) -> bool: """ Validates the incoming response as specified by RFC-2289. @@ -243,14 +234,14 @@ class OTPState: :param store_valid_response: Should a valid response be stored :type store_valid_response: bool - :raises otp2289.OTPInvalidResponse: If the response does not match + :raises otp2289.OTPInvalidResponseError: If the response does not match this state :return: Returns True if response validates, False otherwise :rtype: bool """ - # self.response_to_bytes raises OTPInvalidResponse in case response - # is corrupt or in a wrong format + # self.response_to_bytes raises OTPInvalidResponseError in case + # response is corrupt or in a wrong format response_bytes = self.response_to_bytes(response) if self._hash_algo == 'md5': digest = hashlib.md5(response_bytes).digest() @@ -281,7 +272,7 @@ class OTPState: return True return False # this should not happen since the hash_algo is validated by the caller - raise OTPInvalidResponse(f'Ivalid hash_algo: {self._hash_algo}') + raise OTPInvalidResponseError(f'Ivalid hash_algo: {self._hash_algo}') def to_dict(self) -> dict: """ @@ -367,12 +358,12 @@ class OTPStore: :param state: The OTPState object :type state: otp2289.OTPState - :raises otp2289.OTPStoreException: On failure + :raises otp2289.OTPStoreError: On failure """ if not isinstance(key, str): - raise OTPStoreException('key must be a str') + raise OTPStoreError('key must be a str') if not isinstance(state, OTPState): - raise OTPStoreException('state must be an OTPState-object') + raise OTPStoreError('state must be an OTPState-object') self._data[key] = state self._states[state] = key @@ -393,22 +384,19 @@ class OTPStore: :raises KeyError: If key does not exist - :raises otp2289.OTPStoreException: On failure + :raises otp2289.OTPStoreError: On failure :return: The state corresponding to the key :rtype: otp2289.OTPState """ if not isinstance(key, str): - raise OTPStoreException('key must be a str') + raise OTPStoreError('key must be a str') state = self._data.pop(key) self._states.pop(state) return state def response_validates( - self, - key: str, - response: str, - store_valid_response: bool = True, + self, key: str, response: str, store_valid_response: bool = True ) -> bool: """ A method that wraps around OTPState.response_validates and @@ -429,8 +417,8 @@ class OTPStore: :raises KeyError: If the key is not present - :raises otp2289.OTPInvalidResponse: If the response does not match - this state + :raises otp2289.OTPInvalidResponseError: If the response does not match + this state :return: Returns True if response validates, False otherwise :rtype: bool diff --git a/test/test_generator.py b/test/test_generator.py index b4bb961..4329ae4 100644 --- a/test/test_generator.py +++ b/test/test_generator.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023, Simeon Simeonov +# Copyright (c) 2020-2025, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,25 +35,25 @@ def test_caller_exceptions(): 'TeSt', otp2289.OTP_ALGO_MD5, ) - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: gen.generate_otp_words('3') - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == 'Step value MUST be an int' - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: gen.generate_otp_hexdigest(-1) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == 'Step value MUST be >= 0' - with pytest.raises(otp2289.OTPChallengeException) as exc_info: + with pytest.raises(otp2289.OTPChallengeError) as exc_info: gen.generate_otp_hexdigest_from_challenge(b'md5 fbd TeSt') - assert exc_info.type is otp2289.OTPChallengeException + assert exc_info.type is otp2289.OTPChallengeError assert exc_info.value.args[0] == 'Challenge must be str' - with pytest.raises(otp2289.OTPChallengeException) as exc_info: + with pytest.raises(otp2289.OTPChallengeError) as exc_info: gen.generate_otp_hexdigest_from_challenge('md5 fbd TeSt') - assert exc_info.type is otp2289.OTPChallengeException + assert exc_info.type is otp2289.OTPChallengeError assert exc_info.value.args[0] == 'Invalid challenge' - with pytest.raises(otp2289.generator.OTPChallengeException) as exc_info: + with pytest.raises(otp2289.generator.OTPChallengeError) as exc_info: gen.generate_otp_hexdigest_from_challenge('otp-md5 fbd TeSt') - assert exc_info.type is otp2289.generator.OTPChallengeException + assert exc_info.type is otp2289.generator.OTPChallengeError assert exc_info.value.args[0] == 'Invalid challenge' @@ -63,74 +62,74 @@ def test_constructor_exceptions(): Tests the exceptions when initializing a new object (in the constructor) """ # test the otp2289.OTPGenerator __init__ and validators - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator( 'This is a test.'.encode(), 'TeStø'.encode(), otp2289.OTP_ALGO_MD5, ) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == 'Seed must be a string' - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator( 'This is a test.'.encode(), 'TeStøtEsTteSTteStTest', otp2289.OTP_ALGO_SHA1, ) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == ( 'The seed MUST be of 1 to 16 characters in length' ) - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator( 'This is a test.'.encode(), 'TeStø', otp2289.OTP_ALGO_SHA1, ) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == ( 'The seed MUST consist of purely alphanumeric characters' ) - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator( 'This is a test.'.encode(), 'TeSt', 9, ) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == ( 'hash_algo is not among the known algorithms' ) - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator( 'This is a test.'.encode(), 'TeSt', b'md5', ) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == 'hash_algo must be an int or a str' # test the package structure as well - with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.generator.OTPGeneratorError) as exc_info: otp2289.generator.OTPGenerator( 'This is a test.'.encode(), 'TeSt', 'foo', ) - assert exc_info.type is otp2289.generator.OTPGeneratorException + assert exc_info.type is otp2289.generator.OTPGeneratorError assert exc_info.value.args[0] == ( 'foo is not supported by this version of the hashlib module' ) - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == 'Password must be a byte-string' - with pytest.raises(otp2289.OTPGeneratorException) as exc_info: + with pytest.raises(otp2289.OTPGeneratorError) as exc_info: otp2289.OTPGenerator( '1234567'.encode(), 'TeSt', otp2289.OTP_ALGO_MD5, ) - assert exc_info.type is otp2289.OTPGeneratorException + assert exc_info.type is otp2289.OTPGeneratorError assert exc_info.value.args[0] == 'Password must be longer than 10 bytes' diff --git a/test/test_main.py b/test/test_main.py index 35bd2a2..593bf80 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023, Simeon Simeonov +# Copyright (c) 2020-2025, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/test/test_server.py b/test/test_server.py index 64e7f67..e81532f 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # -# Copyright (c) 2020-2023, Simeon Simeonov +# Copyright (c) 2020-2025, Simeon Simeonov # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -39,9 +38,9 @@ def test_state_caller_exceptions(): 'TeSt', otp2289.OTP_ALGO_MD5, ) - with pytest.raises(otp2289.OTPInvalidResponse) as exc_info: + with pytest.raises(otp2289.OTPInvalidResponseError) as exc_info: state.response_validates('bla') - assert exc_info.type is otp2289.OTPInvalidResponse + assert exc_info.type is otp2289.OTPInvalidResponseError assert exc_info.value.args[0] == ( 'The response is neither a valid token or hex' ) @@ -49,23 +48,23 @@ def test_state_caller_exceptions(): def test_state_constructor_exceptions(): """Tests the exceptions when initializing new OTPState objects""" - with pytest.raises(otp2289.OTPStateException) as exc_info: + with pytest.raises(otp2289.OTPStateError) as exc_info: otp2289.OTPState( '0x7965e05436f5029t', 1, 'TeStø'.encode(), otp2289.OTP_ALGO_MD5, ) - assert exc_info.type is otp2289.OTPStateException + assert exc_info.type is otp2289.OTPStateError assert exc_info.value.args[0] == 'Seed must be a string' - with pytest.raises(otp2289.OTPStateException) as exc_info: + with pytest.raises(otp2289.OTPStateError) as exc_info: otp2289.OTPState( '0x7965e05436f5029t', '1', 'TeSt', otp2289.OTP_ALGO_MD5, ) - assert exc_info.type is otp2289.OTPStateException + assert exc_info.type is otp2289.OTPStateError assert exc_info.value.args[0] == 'Step value MUST be an int' -- cgit v1.3