commit 68a4ebb08b43d0aa060970e595e132f5f1e5c98c Author: Christophe Mehay Date: Sun Apr 7 20:12:57 2019 +0200 Onion v2 are ok diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64d44b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ + +# Created by https://www.gitignore.io/api/python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# End of https://www.gitignore.io/api/python + +# more +key/ +.vscode/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..30d9ca7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +repos: +- repo: git://github.com/pre-commit/pre-commit-hooks + rev: v2.1.0 + hooks: + - id: check-added-large-files + - id: check-docstring-first + - id: check-merge-conflict +# - id: check-yaml + - id: end-of-file-fixer + - id: flake8 + args: + - --exclude=__init__.py + - --exclude=settings* + language_version: python3 + - id: requirements-txt-fixer + - id: trailing-whitespace +- repo: git://github.com/asottile/reorder_python_imports + rev: v1.3.4 + hooks: + - id: reorder-python-imports + language_version: python3 + +- repo: https://github.com/pre-commit/mirrors-autopep8 + rev: 'v1.4.3' + hooks: + - id: autopep8 + args: + - -aaa + - --in-place + - --experimental + +- repo: https://github.com/humitos/mirrors-autoflake.git + rev: 'v1.2' + hooks: + - id: autoflake + args: + - --in-place + - --remove-unused-variables + - --remove-all-unused-imports diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..72266f9 --- /dev/null +++ b/Pipfile @@ -0,0 +1,19 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +tox = "*" +pre-commit = "*" +ptpython = "*" +pylama = "*" +pylint = "*" +pytest = "*" +autopep8 = "*" + +[packages] +pycryptodome = "*" + +[requires] +python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..f0bad31 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,390 @@ +{ + "_meta": { + "hash": { + "sha256": "352e3d3e8d72c1b9ced4c95a6c324e3f2a1255a21d5cd6bc8fdabc37ea85d0fc" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "pycryptodome": { + "hashes": [ + "sha256:02838a7184803c05710ccb4f3c9129fe26b2634f04daf017a3490d8be5f55b62", + "sha256:04b710f55141c06388aa60d6d4a1d704b79f25b03fa9e61b0b1b9fcf0766e2ea", + "sha256:0c54852415567fc53b656b528612d3f8084c127086c38370a2985fbac4892298", + "sha256:0e59d4e06b0cdb57132387144051101eaa9bbde447b62db9660345d247212ff9", + "sha256:134479b4f4743402e7e8740f1bf3d779efd95b467c2535c600332b55d0b34686", + "sha256:1961a6cbd30926a812809efd0f17bd677f54857ac5fd0b0e2638ad4891407dd0", + "sha256:1e8f2d1b97578f0950a46128cca8b2dc44f224c4c08309a66ca53c4a33839fdd", + "sha256:213d360d5e145a2ff4d9d90afab9e2b2d99174114aa4b4c04888937730c7ef57", + "sha256:24dc9a6bc59d4e4fb182197327db1e92e15c598729889132195b420dbcc48468", + "sha256:3c24117d7fc65f5e299550abd7fa5c612813d0891b4db36d8b8cd02442674306", + "sha256:432b19da6106785825cbcaea50a8c3846ec9cd4678702f89e02985d27a73a314", + "sha256:5897e1e336c30274279f23a0f1ea81fdb43c51b514d6ac0f376ff835f420a33e", + "sha256:68ad0ce4a374577a26bb7f458575abe3c2a342818b5280de6e5738870b7761b3", + "sha256:68e50aa3230841dc296004e084ceb74225c92da951cff808122242af8a735501", + "sha256:74bd385d89aca3f8860d56caadead5dcb9e1abdf5774739c4aa9fe3cf56bb34f", + "sha256:783cffd29fb0dbf6498bd225c1b7194f8372258166c7585fb777075226b04b64", + "sha256:933750922193899d5601404c0d0637c4da12593ac2c54a16ba841b8afef44c9b", + "sha256:9871164f10b7bc87b88559b66e632528e766fcd0a84324a803860883b51f4c87", + "sha256:99a2f931e1bf87c95e465bc74ef0276ce735f1def575d52b67e117f712a63243", + "sha256:a53be389125c79728658bf42bf711a277c8ac1b3690d26303bb8b0a9217d8cb9", + "sha256:a68e85d19a870d8f54d7d78fbdefac640bb694aca216223b5987456cdf4d50ff", + "sha256:ae4070cfa271f78e9f74cde4f6b8c3869facfa4749b96e9bc7fbd1959d75f167", + "sha256:b3d3215c5a3392a6c1f537d38c13e014d93f9b33277048f06c7ede4c6926b8bd", + "sha256:df1d5540021b79230cfe8f80bb1f13ee1a626eab10161a26784b78c122c845db", + "sha256:dfdca3079aac696a25edb9a0b59595b9b776426f61587bf5a974596da217834e", + "sha256:e874188bb9c274de2dcc0995e885f15732fd0a43c7ef581eff54eef2b11d83c7", + "sha256:e9ff57287d899d2c4e17e523ac46d1e90902f3b36a81f0e6817238ff0219597f", + "sha256:ecb7718fc0087a67023fb4a78dd8ebb883d66c49f0465ad656b3213bb1841631" + ], + "index": "pypi", + "version": "==3.8.1" + } + }, + "develop": { + "aspy.yaml": { + "hashes": [ + "sha256:ae249074803e8b957c83fdd82a99160d0d6d26dff9ba81ba608b42eebd7d8cd3", + "sha256:c7390d79f58eb9157406966201abf26da0d56c07e0ff0deadc39c8f4dbc13482" + ], + "version": "==1.2.0" + }, + "astroid": { + "hashes": [ + "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", + "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" + ], + "version": "==2.2.5" + }, + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "autopep8": { + "hashes": [ + "sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c" + ], + "index": "pypi", + "version": "==1.4.3" + }, + "cfgv": { + "hashes": [ + "sha256:6e9f2feea5e84bc71e56abd703140d7a2c250fc5ba38b8702fd6a68ed4e3b2ef", + "sha256:e7f186d4a36c099a9e20b04ac3108bd8bb9b9257e692ce18c8c3764d5cb12172" + ], + "version": "==1.6.0" + }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "version": "==0.6.2" + }, + "filelock": { + "hashes": [ + "sha256:b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633", + "sha256:d610c1bb404daf85976d7a82eb2ada120f04671007266b708606565dd03b5be6" + ], + "version": "==3.0.10" + }, + "identify": { + "hashes": [ + "sha256:244e7864ef59f0c7c50c6db73f58564151d91345cd9b76ed793458953578cadd", + "sha256:8ff062f90ad4b09cfe79b5dfb7a12e40f19d2e68a5c9598a49be45f16aba7171" + ], + "version": "==1.4.1" + }, + "importlib-metadata": { + "hashes": [ + "sha256:46fc60c34b6ed7547e2a723fc8de6dc2e3a1173f8423246b3ce497f064e9c3de", + "sha256:bc136180e961875af88b1ab85b4009f4f1278f8396a60526c0009f503a1a96ca" + ], + "version": "==0.9" + }, + "isort": { + "hashes": [ + "sha256:08f8e3f0f0b7249e9fad7e5c41e2113aba44969798a26452ee790c06f155d4ec", + "sha256:4e9e9c4bd1acd66cf6c36973f29b031ec752cbfd991c69695e4e259f9a756927" + ], + "version": "==4.3.16" + }, + "jedi": { + "hashes": [ + "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b", + "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c" + ], + "version": "==0.13.3" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", + "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", + "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", + "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", + "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", + "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", + "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", + "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", + "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", + "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", + "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", + "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", + "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", + "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", + "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", + "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", + "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", + "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", + "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", + "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", + "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", + "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", + "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", + "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", + "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", + "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", + "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", + "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", + "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" + ], + "version": "==1.3.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" + ], + "markers": "python_version > '2.7'", + "version": "==7.0.0" + }, + "nodeenv": { + "hashes": [ + "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a" + ], + "version": "==1.3.3" + }, + "parso": { + "hashes": [ + "sha256:17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33", + "sha256:2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376" + ], + "version": "==0.4.0" + }, + "pluggy": { + "hashes": [ + "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", + "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" + ], + "version": "==0.9.0" + }, + "pre-commit": { + "hashes": [ + "sha256:75a9110eae00d009c913616c0fc8a6a02e7716c4a29a14cac9b313d2c7338ab0", + "sha256:f882c65316eb5b705fe4613e92a7c91055c1800102e4d291cfd18912ec9cf90e" + ], + "index": "pypi", + "version": "==1.15.1" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", + "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", + "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55" + ], + "version": "==2.0.9" + }, + "ptpython": { + "hashes": [ + "sha256:51a74abe931f692360a32d650c2ba1ca329c08f3ed9b1de8abcd1164e0b0a6a7", + "sha256:938ee050e37d61c138dbbeb21383dfef8b9ed4ffb453a5f34041f42025bf5042", + "sha256:ebe9d68ea7532ec8ab306d4bdc7ec393701cd9bbd6eff0aa3067c821f99264d4" + ], + "index": "pypi", + "version": "==2.0.4" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pydocstyle": { + "hashes": [ + "sha256:2258f9b0df68b97bf3a6c29003edc5238ff8879f1efb6f1999988d934e432bd8", + "sha256:5741c85e408f9e0ddf873611085e819b809fca90b619f5fd7f34bd4959da3dd4", + "sha256:ed79d4ec5e92655eccc21eb0c6cf512e69512b4a97d215ace46d17e4990f2039" + ], + "version": "==3.0.0" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "version": "==2.1.1" + }, + "pygments": { + "hashes": [ + "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", + "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" + ], + "version": "==2.3.1" + }, + "pylama": { + "hashes": [ + "sha256:7e0327ee9b2a350ed73fe54c240894e534e2bccfb23a59ed5ce89f5a5689ee94", + "sha256:f81bf3bbd15db802b620903df491e5cd6469dcd542424ce6718425037dcc4d10" + ], + "index": "pypi", + "version": "==7.6.6" + }, + "pylint": { + "hashes": [ + "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", + "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1" + ], + "index": "pypi", + "version": "==2.3.1" + }, + "pytest": { + "hashes": [ + "sha256:13c5e9fb5ec5179995e9357111ab089af350d788cbc944c628f3cde72285809b", + "sha256:f21d2f1fb8200830dcbb5d8ec466a9c9120e20d8b53c7585d180125cce1d297a" + ], + "index": "pypi", + "version": "==4.4.0" + }, + "pyyaml": { + "hashes": [ + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + ], + "version": "==5.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", + "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + ], + "version": "==1.2.1" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, + "tox": { + "hashes": [ + "sha256:69620e19de33a6b7ee8aeda5478791b3618ff58f0b869dbd0319fb71aa903deb", + "sha256:e5cdb1653aa27b3e46b5c390de6b6d51d31afcfdbd9d1222d82d76b82ad03d9b" + ], + "index": "pypi", + "version": "==3.8.6" + }, + "typed-ast": { + "hashes": [ + "sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23", + "sha256:037c35f2741ce3a9ac0d55abfcd119133cbd821fffa4461397718287092d9d15", + "sha256:049feae7e9f180b64efacbdc36b3af64a00393a47be22fa9cb6794e68d4e73d3", + "sha256:19228f7940beafc1ba21a6e8e070e0b0bfd1457902a3a81709762b8b9039b88d", + "sha256:2ea681e91e3550a30c2265d2916f40a5f5d89b59469a20f3bad7d07adee0f7a6", + "sha256:3a6b0a78af298d82323660df5497bcea0f0a4a25a0b003afd0ce5af049bd1f60", + "sha256:5385da8f3b801014504df0852bf83524599df890387a3c2b17b7caa3d78b1773", + "sha256:606d8afa07eef77280c2bf84335e24390055b478392e1975f96286d99d0cb424", + "sha256:69245b5b23bbf7fb242c9f8f08493e9ecd7711f063259aefffaeb90595d62287", + "sha256:6f6d839ab09830d59b7fa8fb6917023d8cb5498ee1f1dbd82d37db78eb76bc99", + "sha256:730888475f5ac0e37c1de4bd05eeb799fdb742697867f524dc8a4cd74bcecc23", + "sha256:9819b5162ffc121b9e334923c685b0d0826154e41dfe70b2ede2ce29034c71d8", + "sha256:9e60ef9426efab601dd9aa120e4ff560f4461cf8442e9c0a2b92548d52800699", + "sha256:af5fbdde0690c7da68e841d7fc2632345d570768ea7406a9434446d7b33b0ee1", + "sha256:b64efdbdf3bbb1377562c179f167f3bf301251411eb5ac77dec6b7d32bcda463", + "sha256:bac5f444c118aeb456fac1b0b5d14c6a71ea2a42069b09c176f75e9bd4c186f6", + "sha256:bda9068aafb73859491e13b99b682bd299c1b5fd50644d697533775828a28ee0", + "sha256:d659517ca116e6750101a1326107d3479028c5191f0ecee3c7203c50f5b915b0", + "sha256:eddd3fb1f3e0f82e5915a899285a39ee34ce18fd25d89582bc89fc9fb16cd2c6" + ], + "markers": "implementation_name == 'cpython'", + "version": "==1.3.1" + }, + "virtualenv": { + "hashes": [ + "sha256:6aebaf4dd2568a0094225ebbca987859e369e3e5c22dc7d52e5406d504890417", + "sha256:984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39" + ], + "version": "==16.4.3" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + }, + "wrapt": { + "hashes": [ + "sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533" + ], + "version": "==1.11.1" + }, + "zipp": { + "hashes": [ + "sha256:55ca87266c38af6658b84db8cfb7343cdb0bf275f93c7afaea0d8e7a209c7478", + "sha256:682b3e1c62b7026afe24eadf6be579fb45fec54c07ea218bded8092af07a68c4" + ], + "version": "==0.3.3" + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pytor/__init__.py b/pytor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytor/__main__.py b/pytor/__main__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytor/onion.py b/pytor/onion.py new file mode 100644 index 0000000..832a560 --- /dev/null +++ b/pytor/onion.py @@ -0,0 +1,199 @@ +import os +import re +from abc import ABC +from abc import abstractmethod +from base64 import b32encode +from hashlib import sha1 +from typing import BinaryIO + +from Crypto.PublicKey import RSA + + +__all__ = [ + 'OnionV2', + 'OnionV3', +] + + +class EmptyDirException(Exception): + pass + + +class Onion(ABC): + ''' + Interface to implement hidden services keys managment + ''' + + _priv = None + _pub = None + _hidden_service_path = None + _version = None + + def __init__(self, + private_key: bytes = None, + hidden_service_path: str = None): + + if hidden_service_path: + try: + self.load_hidden_service(hidden_service_path) + except EmptyDirException: + pass + self._hidden_service_path = hidden_service_path + if private_key: + self.set_private_key(private_key) + if not self._priv: + self.gen_new_private_key() + + @abstractmethod + def gen_new_private_key(self) -> None: + 'Generate new private key' + ... + + @abstractmethod + def set_private_key_from_file(self, file: BinaryIO): + 'Load private key from file' + ... + + @abstractmethod + def set_private_key(self, key: bytes) -> None: + 'Add private key' + ... + + @abstractmethod + def _save_keypair(self, key) -> None: + 'Generate pub key from priv key and save both in instance' + ... + + @abstractmethod + def load_hidden_service(self, path: str) -> None: + 'Load key from hidden service' + ... + + @abstractmethod + def write_hidden_service(self, path: str, force: bool = False) -> None: + 'Write hidden service keys to directory' + ... + + def get_available_private_key_formats(self) -> list: + 'Get private key export availables formats' + r = re.compile(r'_get_private_key_has_([a-z]+)') + formats = [] + for method in dir(self): + m = r.match(method) + if m: + formats.append(m[1]) + return formats + + def get_private_key(self, format: str = 'native'): + 'Get the private key as specified format' + method = '_get_private_key_has_{format}'.format( + format=format + ) + if not hasattr(self, method) and not callable(getattr(self, method)): + raise NotImplementedError('Method {method} if not implemented') + return getattr(self, method)() + + @abstractmethod + def _get_private_key_has_native(self) -> bytes: + 'Get private key like in tor native format' + ... + + @abstractmethod + def get_public_key(self) -> bytes: + 'Compute public key' + if not self._priv: + raise Exception('No private key has been set') + + @abstractmethod + def get_onion_str(self) -> str: + 'Compute onion address string' + ... + + @property + def onion_address(self) -> str: + return "{onion}.onion".format( + onion=self.get_onion_str() + ) + + @property + def version(self) -> str: + return str(self._version) + + +class OnionV2(Onion): + ''' + Tor onion address v2 implement + ''' + + _version = 2 + + def gen_new_private_key(self) -> None: + 'Generate new 1024 bits RSA key for hidden service' + self._save_keypair(RSA.generate(bits=1024)) + + def _save_keypair(self, key: RSA.RsaKey) -> None: + self._priv = key.exportKey("PEM") + self._pub = key.publickey().exportKey("DER") + + def set_private_key(self, key: bytes) -> None: + 'Add private key' + self._save_keypair(RSA.importKey(key.strip())) + + def set_private_key_from_file(self, file: BinaryIO): + 'Load private key from file' + self.set_private_key(file.read()) + + def _get_private_key_has_native(self) -> bytes: + 'Get RSA private key like in PEM' + return self._get_private_key_has_pem() + + def _get_private_key_has_pem(self) -> bytes: + 'Get RSA private key like in PEM' + return RSA.importKey(self._priv).exportKey("PEM") + + def get_public_key(self) -> bytes: + 'Compute public key' + super().get_public_key() + return self._pub + + def load_hidden_service(self, path: str) -> None: + if not os.path.isdir(path): + raise Exception( + '{path} should be an existing directory'.format(path=path) + ) + if 'private_key' not in os.listdir(path): + raise EmptyDirException( + 'private_key file not found in {path}'.format(path=path) + ) + with open(os.path.join(path, 'private_key'), 'rb') as f: + self.set_private_key_from_file(f) + + def write_hidden_service(self, path: str = None, + force: bool = False) -> None: + path = path or self._hidden_service_path + if not path: + raise Exception('Missing hidden service path') + if not os.path.exists(path): + raise Exception( + '{path} should be an existing directory'.format(path=path) + ) + if os.path.exists(os.path.join(path, 'private_key')) and not force: + raise Exception( + 'Use force=True for non empty hidden service directory' + ) + with open(os.path.join(path, 'private_key'), 'wb') as f: + f.write(self._get_private_key_has_native()) + with open(os.path.join(path, 'hostname'), 'w') as f: + f.write(self.onion_address) + + def get_onion_str(self) -> str: + 'Compute onion address string' + return b32encode(sha1(self._pub[22:]).digest()[:10]).decode().lower() + + +class OnionV3(Onion): + ''' + Tor onion address v3 implement + ''' + + _version = 3 diff --git a/pytor/test/__init__.py b/pytor/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytor/test/test_onions.py b/pytor/test/test_onions.py new file mode 100644 index 0000000..f865fbf --- /dev/null +++ b/pytor/test/test_onions.py @@ -0,0 +1,124 @@ +from base64 import b64decode + +import pytest + +from ..onion import OnionV2 +from ..onion import OnionV3 + + +class _testOnion: + + private_key = None + public_key = None + onion_address = None + onion_cls = None + version = None + + def generate_new_key(self): + return self.onion_cls().get_private_key() + + def test_import_key(self): + o = self.onion_cls(private_key=self.private_key) + assert o.get_public_key() == self.public_key + assert o.onion_address == self.onion_address + assert o.get_private_key() == self.private_key.encode() + + def test_generate_key(self): + o = self.onion_cls() + assert o.onion_address.endswith('.onion') + assert len(o.onion_address) == len(self.onion_address) + + def test_import_file(self, fs): + fs.create_file('/test_tor/private_key', contents=self.private_key) + + o = self.onion_cls() + with open('/test_tor/private_key', 'rb') as f: + o.set_private_key_from_file(f) + assert o.onion_address == self.onion_address + + def test_import_hidden_directory(self, tmpdir): + d = tmpdir.mkdir("hidden_directory") + f = d.join('private_key') + f.write(self.private_key) + o = self.onion_cls(hidden_service_path=d) + assert o.onion_address == self.onion_address + + def test_write_hidden_directory(self, tmpdir): + d = tmpdir.mkdir("hidden_directory") + o = self.onion_cls(private_key=self.private_key) + o.write_hidden_service(path=str(d)) + assert d.join('private_key').read() == self.private_key + assert d.join('hostname').read() == o.onion_address + + def test_import_empty_hidden_directory(self, tmpdir): + d = tmpdir.mkdir("hidden_directory") + o = self.onion_cls(hidden_service_path=d) + o.write_hidden_service() + assert d.join('private_key').read() == o.get_private_key().decode() + assert d.join('hostname').read() == o.onion_address + + def test_import_hidden_directory_with_new_key(self, tmpdir): + d = tmpdir.mkdir("hidden_directory") + f = d.join('private_key') + f.write(self.generate_new_key()) + o = self.onion_cls(hidden_service_path=d, + private_key=self.private_key) + with pytest.raises(Exception): + o.write_hidden_service() + o.write_hidden_service(force=True) + assert d.join('private_key').read() == o.get_private_key().decode() + assert d.join('hostname').read() == o.onion_address + + def test_version(self): + assert self.onion_cls().version == str(self.version) + + +class TestOnionV2(_testOnion): + + private_key = ''' +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCsMP4gl6g1Q313miPhb1GnDr56ZxIWGsO2PwHM1infkbhlBakR +6DGQfpE31L1ZKTUxY0OexKbW088v8qCOfjD9Zk1i80JP4xzfWQcwFZ5yM/0fkhm3 +zLXqXdEahvRthmFsS8OWusRs/04U247ryTm4k5S0Ch5OTBuvMLzQ8W0yDwIDAQAB +AoGAAZr3U5B2ZgC6E7phKUHjbf5KMlPxrDkVqAZQWvuIKmhuYqq518vlYmZ7rhyS +o1kqAMrfH4TP1WLmJJlLe+ibRk2aonR4e0GbW4x151wcJdT1V3vdWAsVSzG3+dqX +PiGT//DIe0OPSH6ecI8ftFRLODd6f5iGkF4gsUSTcVzAFgkCQQDTY67dRpOD9Ozw +oYH48xe0B9NQCw7g4NSH85jPurJXnpn6lZ6bcl8x8ioAdgLyomR7fO/dJFYLw6uV +LZLqZsVbAkEA0Iei3QcpsJnYgcQG7l5I26Sq3LwoiGRDFKRI6k0e+en9JQJgA3Ay +tsLpyCHv9jQ762F6AVXFru5DmZX40F6AXQJBAIHoKac8Xx1h4FaEuo4WPkPZ50ey +dANIx/OAhTFrp3vnMPNpDV60K8JS8vLzkx4vJBcrkXDSirqSFhkIN9grLi8CQEO2 +l5MQPWBkRKK2pc2Hfj8cdIMi8kJ/1CyCwE6c5l8etR3sbIMRTtZ76nAbXRFkmsRv +La/7Syrnobngsh/vX90CQB+PSSBqiPSsK2yPz6Gsd6OLCQ9sdy2oRwFTasH8sZyl +bhJ3M9WzP/EMkAzyW8mVs1moFp3hRcfQlZHl6g1U9D8= +-----END RSA PRIVATE KEY----- + '''.strip() + + public_key = b64decode(''' +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsMP4gl6g1Q313miPhb1GnDr56 +ZxIWGsO2PwHM1infkbhlBakR6DGQfpE31L1ZKTUxY0OexKbW088v8qCOfjD9Zk1i +80JP4xzfWQcwFZ5yM/0fkhm3zLXqXdEahvRthmFsS8OWusRs/04U247ryTm4k5S0 +Ch5OTBuvMLzQ8W0yDwIDAQAB + ''') + onion_address = 'wcet3bgkj4purdfx.onion' + onion_cls = OnionV2 + version = 2 + + +class TestOnionV3(_testOnion): + + private_key = b64decode(''' +PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAACArobDQYyZAWXei4QZwr++j96H1X/gq14N +wLRZ2O5DXuL0EzYKkdhZSILY85q+kfwZH8z4ceqe7u1F+0pQi/sM + ''') + + public_key = b64decode(''' +PT0gZWQyNTUxOXYxLXB1YmxpYzogdHlwZTAgPT0AAAC9kzftiea/kb+TWlCEVNpfUJLVk+rFIoMG +m9/hW13isA== + ''') + + onion_address = ( + 'xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion' + ) + + onion_cls = OnionV3 + version = 3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fc38883 --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from setuptools import find_packages +from setuptools import setup + +setup( + name='pytor', + + version='0.1.0', + + packages=find_packages(), + + author="Christophe Mehay", + + author_email="cmehay@nospam.student.42.fr", + + description="Manage Tor hidden services keys", + + include_package_data=True, + + url='http://github.com/cmehay/pytor', + + classifiers=[ + "Programming Language :: Python", + "Development Status :: 1 - Planning", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], + + install_requires=['pycryptodome==3.8.1'], + + # entry_points={ + # 'console_scripts': [ + # 'pytor = pytor:main', + # ], + # }, + + license="WTFPL", +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..785afd8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py34, py35, py36, py37 +skip_missing_interpreters = true + +[testenv] +deps= + pytest + pyfakefs + pycryptodome +commands=pytest -v