diff --git a/.gitignore b/.gitignore index 640b090..da551a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,68 @@ .idea .*.swp -*.pyc build/ smime.egg-info/ dist/ +.pytest_cache/ +.coverage + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +develop-eggs/ +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.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# editor +.idea +.vscode +*.sublime-* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e2cf41f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +# Config file for automatic testing at travis-ci.org +dist: xenial +language: python +sudo: required +matrix: + include: + - name: Python 2.7 + python: 2.7 + env: python_version=2.7 + + - name: Python 3.5 + python: 3.5 + env: python_version=3.5 + + - name: Python 3.6 + python: 3.6 + env: python_version=3.6 + + - name: Python 3.7 + python: 3.7 + env: python_version=3.7 + +cache: + directories: + - eggs +install: + - pip install -U pipenv + - pipenv install --dev + - sleep 5 +script: + - make lint + - make clean + - pytest -s --cov=smime/ -s --tb=native -v --cov-report term-missing --cov-append smime/ +after_success: + - codecov diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..05c58bc --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,14 @@ +======= +CHANGES +======= + +0.1.0b2 (unreleased) +-------------------- + +- Initial release. + + +0.1.0b1 (2019-01-06) +-------------------- + +- Initial release. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..15d4390 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,14 @@ +graft smime +include AUTHORS +include CONTRIBUTING.md +include CHANGES.rst +include LICENSE +include README.rst + +recursive-exclude test * +recursive-exclude tool * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +exclude Makefile .editorconfig Pipfile* tox.ini .coveragerc + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..77c628d --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + rm -fr .pytest_cache + +lint: ## check style with flake8 + flake8 smime/ + +isort: ## run isort recursively + isort smime/ -rc + +test: ## run tests quickly with the default Python + py.test smime/tests + +install: clean ## install the package to the active Python's site-packages + python setup.py install diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..d47827d --- /dev/null +++ b/Pipfile @@ -0,0 +1,19 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +smime = {path = ".",extras = ["test"],editable = true} + +[packages] +cryptography = "*" +asn1crypto = "*" +flake8 = "*" +flake8-isort = "*" +isort = "*" +certifi = "*" +zest-releaser = {extras = ["recommended"],version = "*"} + +[requires] +python_version = "2.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..399e54a --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,605 @@ +{ + "_meta": { + "hash": { + "sha256": "b36f3decc7aeac80e00d889956f045be3728233494e6b8e6a605b413d7f9d67b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "2.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asn1crypto": { + "hashes": [ + "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", + "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + ], + "index": "pypi", + "version": "==0.24.0" + }, + "bleach": { + "hashes": [ + "sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718", + "sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9" + ], + "version": "==3.0.2" + }, + "certifi": { + "hashes": [ + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + ], + "index": "pypi", + "version": "==2018.11.29" + }, + "cffi": { + "hashes": [ + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + ], + "version": "==1.11.5" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "check-manifest": { + "hashes": [ + "sha256:44e3cf4b0833a55460046bf7a3600eaadbcae5e9d13baf0c9d9789dd5c2c6452", + "sha256:728edbaccadaa86db0b8f876afac1fb90bce9452234753dfe4ef8fe786dcc2f1" + ], + "version": "==0.37" + }, + "colorama": { + "hashes": [ + "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", + "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" + ], + "version": "==0.4.1" + }, + "configparser": { + "hashes": [ + "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" + ], + "markers": "python_version < '3.2'", + "version": "==3.5.0" + }, + "cryptography": { + "hashes": [ + "sha256:05a6052c6a9f17ff78ba78f8e6eb1d777d25db3b763343a1ae89a7a8670386dd", + "sha256:0eb83a24c650a36f68e31a6d0a70f7ad9c358fa2506dc7b683398b92e354a038", + "sha256:0ff4a3d6ea86aa0c9e06e92a9f986de7ee8231f36c4da1b31c61a7e692ef3378", + "sha256:1699f3e916981df32afdd014fb3164db28cdb61c757029f502cb0a8c29b2fdb3", + "sha256:1b1f136d74f411f587b07c076149c4436a169dc19532e587460d9ced24adcc13", + "sha256:21e63dd20f5e5455e8b34179ac43d95b3fb1ffa54d071fd2ed5d67da82cfe6dc", + "sha256:2454ada8209bbde97065453a6ca488884bbb263e623d35ba183821317a58b46f", + "sha256:3cdc5f7ca057b2214ce4569e01b0f368b3de9d8ee01887557755ccd1c15d9427", + "sha256:418e7a5ec02a7056d3a4f0c0e7ea81df374205f25f4720bb0e84189aa5fd2515", + "sha256:471a097076a7c4ab85561d7fa9a1239bd2ae1f9fd0047520f13d8b340bf3210b", + "sha256:5ecaf9e7db3ca582c6de6229525d35db8a4e59dc3e8a40a331674ed90e658cbf", + "sha256:63b064a074f8dc61be81449796e2c3f4e308b6eba04a241a5c9f2d05e882c681", + "sha256:6afe324dfe6074822ccd56d80420df750e19ac30a4e56c925746c735cf22ae8b", + "sha256:70596e90398574b77929cd87e1ac6e43edd0e29ba01e1365fed9c26bde295aa5", + "sha256:70c2b04e905d3f72e2ba12c58a590817128dfca08949173faa19a42c824efa0b", + "sha256:8908f1db90be48b060888e9c96a0dee9d842765ce9594ff6a23da61086116bb6", + "sha256:af12dfc9874ac27ebe57fc28c8df0e8afa11f2a1025566476b0d50cdb8884f70", + "sha256:b4fc04326b2d259ddd59ed8ea20405d2e695486ab4c5e1e49b025c484845206e", + "sha256:da5b5dda4aa0d5e2b758cc8dfc67f8d4212e88ea9caad5f61ba132f948bab859" + ], + "index": "pypi", + "version": "==2.4.2" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "enum34": { + "hashes": [ + "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", + "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", + "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", + "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" + ], + "markers": "python_version < '3.4'", + "version": "==1.1.6" + }, + "flake8": { + "hashes": [ + "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", + "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" + ], + "index": "pypi", + "version": "==3.6.0" + }, + "flake8-isort": { + "hashes": [ + "sha256:3c107c405dd6e3dbdcccb2f84549d76d58a07120cd997a0560fab8b84c305f2a", + "sha256:76d7dd6aec2762c608b442abebb0aaedb72fc75f9a075241a89e4784d8a39900" + ], + "index": "pypi", + "version": "==2.6.0" + }, + "futures": { + "hashes": [ + "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265", + "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1" + ], + "version": "==3.2.0" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "ipaddress": { + "hashes": [ + "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", + "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" + ], + "markers": "python_version < '3'", + "version": "==1.0.22" + }, + "isort": { + "hashes": [ + "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", + "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", + "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" + ], + "index": "pypi", + "version": "==4.3.4" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "pkginfo": { + "hashes": [ + "sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", + "sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee" + ], + "version": "==1.4.2" + }, + "pycodestyle": { + "hashes": [ + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + ], + "version": "==2.4.0" + }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "version": "==2.19" + }, + "pyflakes": { + "hashes": [ + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + ], + "version": "==2.0.0" + }, + "pygments": { + "hashes": [ + "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", + "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" + ], + "version": "==2.3.1" + }, + "pyroma": { + "hashes": [ + "sha256:3ed770a0ed1616ee2ffa576852e34fb1ea5b54c37ff78dd33cff67d0e4bb584b", + "sha256:94a11cb077976bff9bd37ac8b487902556f216c4ee90b74a2344367f73b6ee7f" + ], + "version": "==2.4" + }, + "readme-renderer": { + "hashes": [ + "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", + "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d" + ], + "version": "==24.0" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "version": "==2.21.0" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", + "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5" + ], + "version": "==0.8.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "testfixtures": { + "hashes": [ + "sha256:1e0affc9b459f039ebf9ae6e8af4059ded4d293863d4af9ffcd83e3b5e8df9cc", + "sha256:b040b59e0089809c2f157d3463ea288a10d890661695581649f40ae967944829" + ], + "version": "==6.4.1" + }, + "tqdm": { + "hashes": [ + "sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392", + "sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb" + ], + "version": "==4.28.1" + }, + "twine": { + "hashes": [ + "sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c", + "sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c" + ], + "version": "==1.12.1" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, + "wheel": { + "hashes": [ + "sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6", + "sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44" + ], + "version": "==0.32.3" + }, + "zest-releaser": { + "extras": [ + "recommended" + ], + "hashes": [ + "sha256:64bcf954c5a7327cce7d402fc97f616369272c74fabf2b6b34b7a63a743e2d70" + ], + "index": "pypi", + "version": "==6.15.3" + } + }, + "develop": { + "asn1crypto": { + "hashes": [ + "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", + "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + ], + "index": "pypi", + "version": "==0.24.0" + }, + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "version": "==1.2.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "cffi": { + "hashes": [ + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + ], + "version": "==1.11.5" + }, + "configparser": { + "hashes": [ + "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" + ], + "markers": "python_version < '3.2'", + "version": "==3.5.0" + }, + "coverage": { + "hashes": [ + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "version": "==4.5.2" + }, + "cryptography": { + "hashes": [ + "sha256:05a6052c6a9f17ff78ba78f8e6eb1d777d25db3b763343a1ae89a7a8670386dd", + "sha256:0eb83a24c650a36f68e31a6d0a70f7ad9c358fa2506dc7b683398b92e354a038", + "sha256:0ff4a3d6ea86aa0c9e06e92a9f986de7ee8231f36c4da1b31c61a7e692ef3378", + "sha256:1699f3e916981df32afdd014fb3164db28cdb61c757029f502cb0a8c29b2fdb3", + "sha256:1b1f136d74f411f587b07c076149c4436a169dc19532e587460d9ced24adcc13", + "sha256:21e63dd20f5e5455e8b34179ac43d95b3fb1ffa54d071fd2ed5d67da82cfe6dc", + "sha256:2454ada8209bbde97065453a6ca488884bbb263e623d35ba183821317a58b46f", + "sha256:3cdc5f7ca057b2214ce4569e01b0f368b3de9d8ee01887557755ccd1c15d9427", + "sha256:418e7a5ec02a7056d3a4f0c0e7ea81df374205f25f4720bb0e84189aa5fd2515", + "sha256:471a097076a7c4ab85561d7fa9a1239bd2ae1f9fd0047520f13d8b340bf3210b", + "sha256:5ecaf9e7db3ca582c6de6229525d35db8a4e59dc3e8a40a331674ed90e658cbf", + "sha256:63b064a074f8dc61be81449796e2c3f4e308b6eba04a241a5c9f2d05e882c681", + "sha256:6afe324dfe6074822ccd56d80420df750e19ac30a4e56c925746c735cf22ae8b", + "sha256:70596e90398574b77929cd87e1ac6e43edd0e29ba01e1365fed9c26bde295aa5", + "sha256:70c2b04e905d3f72e2ba12c58a590817128dfca08949173faa19a42c824efa0b", + "sha256:8908f1db90be48b060888e9c96a0dee9d842765ce9594ff6a23da61086116bb6", + "sha256:af12dfc9874ac27ebe57fc28c8df0e8afa11f2a1025566476b0d50cdb8884f70", + "sha256:b4fc04326b2d259ddd59ed8ea20405d2e695486ab4c5e1e49b025c484845206e", + "sha256:da5b5dda4aa0d5e2b758cc8dfc67f8d4212e88ea9caad5f61ba132f948bab859" + ], + "index": "pypi", + "version": "==2.4.2" + }, + "enum34": { + "hashes": [ + "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", + "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", + "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", + "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" + ], + "markers": "python_version < '3.4'", + "version": "==1.1.6" + }, + "flake8": { + "hashes": [ + "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", + "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" + ], + "index": "pypi", + "version": "==3.6.0" + }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "markers": "python_version < '3.0'", + "version": "==1.0.2" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "ipaddress": { + "hashes": [ + "sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", + "sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c" + ], + "markers": "python_version < '3'", + "version": "==1.0.22" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", + "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", + "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + ], + "version": "==5.0.0" + }, + "pathlib2": { + "hashes": [ + "sha256:25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742", + "sha256:5887121d7f7df3603bca2f710e7219f3eca0eb69e0b7cc6e0a022e155ac931a7" + ], + "markers": "python_version < '3.6'", + "version": "==2.3.3" + }, + "pluggy": { + "hashes": [ + "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", + "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" + ], + "version": "==0.8.0" + }, + "py": { + "hashes": [ + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + ], + "version": "==1.7.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + ], + "version": "==2.4.0" + }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "version": "==2.19" + }, + "pyflakes": { + "hashes": [ + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + ], + "version": "==2.0.0" + }, + "pytest": { + "hashes": [ + "sha256:f689bf2fc18c4585403348dd56f47d87780bf217c53ed9ae7a3e2d7faa45f8e9", + "sha256:f812ea39a0153566be53d88f8de94839db1e8a05352ed8a49525d7d7f37861e9" + ], + "version": "==4.0.2" + }, + "pytest-cov": { + "hashes": [ + "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", + "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762" + ], + "version": "==2.6.0" + }, + "pytest-flake8": { + "hashes": [ + "sha256:4f30f5be3efb89755f38f11bdb2a5e22d19a6f5faa73428f703a3292a9572cd3", + "sha256:c740ad6aa19e3958947d2118f70bed218caf1d2097039fb7318573a2a72f89a1" + ], + "version": "==1.0.2" + }, + "scandir": { + "hashes": [ + "sha256:04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", + "sha256:1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", + "sha256:1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", + "sha256:346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", + "sha256:44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", + "sha256:61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", + "sha256:a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", + "sha256:c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", + "sha256:c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", + "sha256:c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", + "sha256:f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd" + ], + "markers": "python_version < '3.5'", + "version": "==1.9.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "smime": { + "editable": true, + "extras": [ + "test" + ], + "path": "." + } + } +} diff --git a/README.rst b/README.rst index 495c01b..8133d8e 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,11 @@ Python S/MIME Toolkit ===================== +.. warning:: + This package is pure fork version from https://github.com/balena/python-smime with immediate release version which contains some necessary refactoring. + All credits go to original Author(s). + Publisher of this fork version (Md Nazrul Islam) doest not reserve any Copyright rights. + This library implements a S/MIME handler. It supports only S/MIME messages encryption using a public RSA key, in AES128-CBC, AES192-CBC or AES256-CBC modes. diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..13ce799 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,29 @@ +[tool:pytest] +flake8-max-line-length = 120 + +[bdist_wheel] +universal = 1 + +[flake8] +exclude = + docs + smime/tools/ + smime/testdata/ + smime/test/ + +max_line_length = 120 +no-accept-encodings = True + +[aliases] +# Define setup.py command aliases here +test = pytest + +[isort] +lines_after_imports=2 +force_single_line=true +line_length=120 +not_skip=__init__.py + +[zest.releaser] +create-wheel = yes +register = no \ No newline at end of file diff --git a/setup.py b/setup.py index 40ecc5f..bbdcae8 100755 --- a/setup.py +++ b/setup.py @@ -11,8 +11,12 @@ with open(path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() +with open(path.join(here, 'CHANGES.rst'), encoding='utf-8') as f: + long_description += '\n\n' + f.read() + +test_requires = ['pytest','pytest-flake8', 'pytest-cov'] setup( - name='smime', + name='smime-py23', version=__import__('smime').__version__, description='Python S/MIME Toolkit', long_description=long_description, @@ -27,6 +31,9 @@ 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries', 'Topic :: Communications :: Email', 'Topic :: Security :: Cryptography', @@ -35,5 +42,12 @@ packages=find_packages(exclude=['smime/test', 'smime/crypto/testdata', 'smime/crypto/tools', '*_test.py']), platforms=["all"], - install_requires=['cryptography', 'asn1crypto'], + install_requires=['cryptography', 'asn1crypto', 'six'], + setup_requires=['pytest-runner'], + tests_require=test_requires, + test_suite='tests', + extras_require={ + 'test': test_requires + }, + zip_safe=False, ) diff --git a/smime/__init__.py b/smime/__init__.py index 63c179b..5e5e165 100644 --- a/smime/__init__.py +++ b/smime/__init__.py @@ -2,8 +2,6 @@ __author__ = 'G. B. Versiani' __license__ = 'Apache License (2.0)' -__version__ = '0.0.4' +__version__ = '0.1.0b2' __all__ = [__author__, __license__, __version__] - -from .encrypt import encrypt diff --git a/smime/api.py b/smime/api.py new file mode 100644 index 0000000..3dbb7bf --- /dev/null +++ b/smime/api.py @@ -0,0 +1,2 @@ +""" """ +from .encrypt import encrypt # noqa: F401 diff --git a/smime/block.py b/smime/block.py index fa1d3b2..f9e10b9 100644 --- a/smime/block.py +++ b/smime/block.py @@ -3,14 +3,16 @@ from __future__ import unicode_literals import os +from abc import ABCMeta +from abc import abstractmethod -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import modes -from abc import ABCMeta, abstractmethod - -class BlockCipher(): +class BlockCipher: __metaclass__ = ABCMeta @abstractmethod @@ -48,14 +50,17 @@ def session_key(self): def encrypt(self, data): padded_data = self._pad(data, self.block_size) - encrypted_content = self._encryptor.update(padded_data.encode('utf-8')) + self._encryptor.finalize() + encrypted_content = ( + self._encryptor.update(padded_data.encode("utf-8")) + + self._encryptor.finalize() + ) return { - 'content_type': 'data', - 'content_encryption_algorithm': { - 'algorithm': self.algorithm, - 'parameters': self._iv + "content_type": "data", + "content_encryption_algorithm": { + "algorithm": self.algorithm, + "parameters": self._iv, }, - 'encrypted_content': encrypted_content + "encrypted_content": encrypted_content, } @staticmethod @@ -70,9 +75,9 @@ def parameters(self): def get_cipher(algorithm): algorithms = { - 'aes128_cbc': (AES, (modes.CBC, 16)), - 'aes192_cbc': (AES, (modes.CBC, 24)), - 'aes256_cbc': (AES, (modes.CBC, 32)), + "aes128_cbc": (AES, (modes.CBC, 16)), + "aes192_cbc": (AES, (modes.CBC, 24)), + "aes256_cbc": (AES, (modes.CBC, 32)), } if algorithm in algorithms: cipher, parameters = algorithms[algorithm] diff --git a/smime/cert.py b/smime/cert.py index d9f1c1f..d4cf469 100644 --- a/smime/cert.py +++ b/smime/cert.py @@ -6,19 +6,23 @@ import hashlib -from asn1crypto import x509, cms, pem +from asn1crypto import cms +from asn1crypto import pem +from asn1crypto import x509 from .pubkey import RSAPublicKeyCipher class CertificateError(Exception): """Certificate has errors.""" + pass class Certificate(object): """X509 certificates.""" - PEM_MARKERS = ('CERTIFICATE',) + + PEM_MARKERS = ("CERTIFICATE",) def __init__(self, der_string): """Initialize from a DER string. @@ -86,7 +90,7 @@ def from_pem_file(cls, pem_file): Returns: a Certificate object """ - with open(pem_file, 'rb') as pem_cert_file: + with open(pem_file, "rb") as pem_cert_file: return cls.from_pem(pem_cert_file.read()) @classmethod @@ -99,7 +103,7 @@ def from_der_file(cls, der_file): Returns: a Certificate object. """ - with open(der_file, 'rb') as der_cert_file: + with open(der_file, "rb") as der_cert_file: return cls.from_der(der_cert_file.read()) def to_der(self): @@ -125,8 +129,10 @@ def self_signed(self): Returns: True or False. """ - return (self._cert['tbs_certificate']['issuer'] == - self._cert['tbs_certificate']['subject']) + return ( + self._cert["tbs_certificate"]["issuer"] + == self._cert["tbs_certificate"]["subject"] + ) def fingerprint(self, hashfunc="sha1"): """Get the certificate fingerprint. @@ -151,8 +157,7 @@ def key_hash(self, hashfunc="sha1"): a (binary) hash digest of the public key. """ h = hashlib.new(hashfunc) - h.update( - self._cert['tbs_certificate']['subject_public_key_info'].dump()) + h.update(self._cert["tbs_certificate"]["subject_public_key_info"].dump()) return h.digest() def _get_public_key_cipher(self): @@ -160,9 +165,7 @@ def _get_public_key_cipher(self): :return: The PublicKey object for this certificate """ - algorithms = { - RSAPublicKeyCipher.algo: RSAPublicKeyCipher - } + algorithms = {RSAPublicKeyCipher.algo: RSAPublicKeyCipher} public_key_info = self._cert.public_key algorithm = public_key_info.algorithm if algorithm not in algorithms: @@ -172,28 +175,28 @@ def _get_public_key_cipher(self): def recipient_info(self, session_key): cipher = self._get_public_key_cipher() - if cipher == None: + if cipher is None: return None encrypted_key = cipher.encrypt(session_key) - tbs_cert = self._cert['tbs_certificate'] + tbs_cert = self._cert["tbs_certificate"] # TODO: use subject_key_identifier when available return cms.RecipientInfo( - name = 'ktri', - value = { - 'version': u'v0', - 'rid': cms.RecipientIdentifier( - name = 'issuer_and_serial_number', - value = { - 'issuer': tbs_cert['issuer'], - 'serial_number': tbs_cert['serial_number'] - } + name="ktri", + value={ + "version": "v0", + "rid": cms.RecipientIdentifier( + name="issuer_and_serial_number", + value={ + "issuer": tbs_cert["issuer"], + "serial_number": tbs_cert["serial_number"], + }, ), - 'key_encryption_algorithm': { - 'algorithm': cipher.algo, - 'parameters': cipher.parameters + "key_encryption_algorithm": { + "algorithm": cipher.algo, + "parameters": cipher.parameters, }, - 'encrypted_key': encrypted_key - } + "encrypted_key": encrypted_key, + }, ) @@ -219,5 +222,5 @@ def certs_from_pem_file(pem_file): Yields: Certificate objects. """ - with open(pem_file, 'rb') as certs_pem_file: + with open(pem_file, "rb") as certs_pem_file: return certs_from_pem(certs_pem_file.read()) diff --git a/smime/encrypt.py b/smime/encrypt.py index f85470f..5b2e2a0 100644 --- a/smime/encrypt.py +++ b/smime/encrypt.py @@ -1,19 +1,17 @@ -# Refer to RFC3565 -# coding: utf-8 - -from __future__ import unicode_literals - +# _*_ coding: utf-8 _*_ +"""Refer to RFC3565""" from base64 import b64encode +from copy import deepcopy from email import message_from_string from email.mime.text import MIMEText -from .cert import certs_from_pem +import six +from asn1crypto import cms + from .block import get_cipher +from .cert import certs_from_pem from .print_util import wrap_lines -from asn1crypto import cms -from email.message import EmailMessage - def __iterate_recipient_infos(certs, session_key): if isinstance(certs, (tuple, list)): @@ -27,7 +25,7 @@ def __iterate_recipient_infos(certs, session_key): yield recipient_info -def encrypt(message, certs, algorithm='aes256_cbc'): +def encrypt(message, certs, algorithm="aes256_cbc"): """ Takes the contents of the message parameter, formatted as in RFC 2822 (type str or message), and encrypts them, so that they can only be read by the intended recipient specified by pubkey. @@ -35,56 +33,64 @@ def encrypt(message, certs, algorithm='aes256_cbc'): """ # Get the chosen block cipher block_cipher = get_cipher(algorithm) - if block_cipher == None: - raise ValueError('Unknown block algorithm') + if block_cipher is None: + raise ValueError("Unknown block algorithm") # Get the message content. This could be a string, or a message object - passed_as_str = isinstance(message, str) + passed_as_str = isinstance(message, six.string_types) + if passed_as_str: - msg = message_from_string(message) - else: - msg = message + message = message_from_string(message) # Extract the message payload without conversion, & the outermost MIME header / Content headers. This allows # the MIME content to be rendered for any outermost MIME type incl. multipart - pl = EmailMessage() - for i in msg.items(): - hname = i[0].lower() - if hname == 'mime-version' or hname.startswith('content-'): - pl.add_header(i[0], i[1]) - pl._payload = msg._payload - content = pl.as_string() + copied_msg = deepcopy(message) + + headers = {} + # bellows headers are wiped from cloned and would be added in newly created message instance + for hdr_name in ("Subject", "To", "BCC", "CC", "From"): + values = copied_msg.get_all(hdr_name) + if values: + del copied_msg[hdr_name] + headers[hdr_name] = values + content = copied_msg.as_string() recipient_infos = [] + for recipient_info in __iterate_recipient_infos(certs, block_cipher.session_key): - if recipient_info == None: - raise ValueError('Unknown public-key algorithm') + if recipient_info is None: + raise ValueError("Unknown public-key algorithm") recipient_infos.append(recipient_info) # Encode the content encrypted_content_info = block_cipher.encrypt(content) # Build the enveloped data and encode in base64 - enveloped_data = cms.ContentInfo({ - 'content_type': 'enveloped_data', - 'content': { - 'version': 'v0', - 'recipient_infos': recipient_infos, - 'encrypted_content_info': encrypted_content_info + enveloped_data = cms.ContentInfo( + { + u"content_type": u"enveloped_data", + u"content": { + u"version": u"v0", + u"recipient_infos": recipient_infos, + u"encrypted_content_info": encrypted_content_info, + }, } - }) - encoded_content = '\n'.join(wrap_lines(b64encode(enveloped_data.dump()), 64)) + ) + encoded_content = "\n".join(wrap_lines(b64encode(enveloped_data.dump()), 64)) # Create the resulting message result_msg = MIMEText(encoded_content) overrides = ( - ('MIME-Version', '1.0'), - ('Content-Type', 'application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m'), - ('Content-Transfer-Encoding', 'base64'), - ('Content-Disposition', 'attachment; filename=smime.p7m') + ("MIME-Version", "1.0"), + ( + "Content-Type", + "application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m", + ), + ("Content-Transfer-Encoding", "base64"), + ("Content-Disposition", "attachment; filename=smime.p7m"), ) - for name, value in list(msg.items()): - if name in [x for x, _ in overrides]: + for name, value in list(copied_msg.items()): + if name in [x for x, _ in overrides]: continue result_msg.add_header(name, value) @@ -93,8 +99,13 @@ def encrypt(message, certs, algorithm='aes256_cbc'): del result_msg[name] result_msg[name] = value + # adds header + for hrd, values in six.iteritems(headers): + for val in values: + result_msg.add_header(hrd, val) + # return the same type as was passed in if passed_as_str: return result_msg.as_string() else: - return result_msg \ No newline at end of file + return result_msg diff --git a/smime/encrypt_test.py b/smime/encrypt_test.py index eecab06..216f8b0 100644 --- a/smime/encrypt_test.py +++ b/smime/encrypt_test.py @@ -1,22 +1,19 @@ #!/usr/bin/env python -# coding=utf-8 - -from __future__ import unicode_literals - +# _*_ coding: utf-8 _*_ import os import unittest -from subprocess import Popen, PIPE +from email import message_from_string +from subprocess import PIPE +from subprocess import Popen from tempfile import mkstemp +from smime.api import encrypt from smime.test import test_config -from smime import encrypt - -from email import message_from_string class EncryptTest(unittest.TestCase): - _CARL_PUBLIC_CERTIFICATE = 'CarlRSASelf.pem' - _CARL_PRIVATE_CERTIFICATE = 'CarlPrivRSASign.pem' + _CARL_PUBLIC_CERTIFICATE = "CarlRSASelf.pem" + _CARL_PRIVATE_CERTIFICATE = "CarlPrivRSASign.pem" def get_file(self, filename): return test_config.get_test_file_path(filename) @@ -25,49 +22,52 @@ def get_cmd_output(self, args): child = Popen(args, stdout=PIPE, stderr=PIPE) result = [] while True: - for line in iter(child.stdout.readline, ''): + for line in iter(child.stdout.readline, ""): result.append(line) if child.poll() is not None: break if child.returncode != 0: error = [] - for line in iter(child.stderr.readline, ''): + for line in iter(child.stderr.readline, ""): error.append(line) - self.fail(("Command: %s\n%s" % - (' '.join(args), ''.join(error)))) - return '\n'.join(result) + self.fail(("Command: %s\n%s" % (" ".join(args), "".join(error)))) + return "\n".join(result) def assertMessageToCarlWith(self, algorithm): message = [ 'From: "Alice" ', 'To: "Carl" ', - 'Subject: A message from python', - '', - 'Now you see me.' + "Subject: A message from python", + "", + "Now you see me.", ] with open(self.get_file(self._CARL_PUBLIC_CERTIFICATE)) as cert: - result = encrypt('\n'.join(message), cert.read(), algorithm=algorithm) + result = encrypt(u"\n".join(message), cert.read(), algorithm=algorithm) fd, tmp_file = mkstemp() os.write(fd, result) cmd = [ - 'openssl', 'smime', '-decrypt', - '-in', tmp_file, - '-inkey', self.get_file(self._CARL_PRIVATE_CERTIFICATE) + "openssl", + "smime", + "-decrypt", + "-in", + tmp_file, + "-inkey", + self.get_file(self._CARL_PRIVATE_CERTIFICATE), ] cmd_output = self.get_cmd_output(cmd) private_message = message_from_string(cmd_output) payload = private_message.get_payload().splitlines() - self.assertEquals('Now you see me.', payload[len(payload)-1]) + self.assertEquals("Now you see me.", payload[len(payload) - 1]) def test_message_to_carl_aes256_cbc(self): - self.assertMessageToCarlWith('aes256_cbc') + self.assertMessageToCarlWith(u"aes256_cbc") def test_message_to_carl_aes192_cbc(self): - self.assertMessageToCarlWith('aes192_cbc') + self.assertMessageToCarlWith(u"aes192_cbc") def test_message_to_carl_aes128_cbc(self): - self.assertMessageToCarlWith('aes128_cbc') + self.assertMessageToCarlWith(u"aes128_cbc") if __name__ == "__main__": diff --git a/smime/print_util.py b/smime/print_util.py index 01e04dc..b18d7e6 100644 --- a/smime/print_util.py +++ b/smime/print_util.py @@ -14,7 +14,7 @@ def bits_to_hex(bit_array, delimiter=":"): pad_length = 8 - partial_bits if partial_bits else 0 bitstring = "0" * pad_length + "".join(map(str, bit_array)) - byte_array = [int(bitstring[i:i + 8], 2) for i in range(0, len(bitstring), 8)] + byte_array = [int(bitstring[i: i + 8], 2) for i in range(0, len(bitstring), 8)] return delimiter.join(map(lambda x: "%02x" % x, byte_array)) @@ -39,13 +39,13 @@ def int_to_hex(int_value, delimiter=":"): ret = "" pos = 0 # Accommodate for negative integers. - if hex_string[0] == '-': - ret += ' -' + delimiter + if hex_string[0] == "-": + ret += " -" + delimiter hex_string = hex_string[1:] # If the first digit is a half-byte, pad with a 0. remaining_len = len(hex_string) - pos hex_string = hex_string.zfill(remaining_len + remaining_len % 2) - byte_values = [hex_string[i:i + 2] for i in range(pos, len(hex_string), 2)] + byte_values = [hex_string[i: i + 2] for i in range(pos, len(hex_string), 2)] return ret + delimiter.join(byte_values) @@ -60,7 +60,7 @@ def wrap_lines(long_string, wrap): a list of lines of at most |wrap| characters each.""" if not long_string: return [] - long_lines = long_string.decode('utf-8').split('\n') + long_lines = long_string.decode("utf-8").split("\n") if wrap <= 0: return long_lines ret = [] @@ -69,7 +69,7 @@ def wrap_lines(long_string, wrap): # Empty line ret += [line] else: - ret += [line[i:i + wrap] for i in range(0, len(line), wrap)] + ret += [line[i: i + wrap] for i in range(0, len(line), wrap)] return ret diff --git a/smime/print_util_test.py b/smime/print_util_test.py index f5b3608..665c8b0 100755 --- a/smime/print_util_test.py +++ b/smime/print_util_test.py @@ -1,11 +1,12 @@ #!/usr/bin/env python - import unittest -from smime.crypto.asn1 import print_util + +from smime import print_util + class PrintUtilTest(unittest.TestCase): def test_bits_to_hex(self): - bit_array = [0,1,1,0,1,0,1,1,1,0] + bit_array = [0, 1, 1, 0, 1, 0, 1, 1, 1, 0] self.assertEqual("01:ae", print_util.bits_to_hex(bit_array)) self.assertEqual("01ae", print_util.bits_to_hex(bit_array, delimiter="")) self.assertEqual("", print_util.bits_to_hex("")) @@ -17,7 +18,7 @@ def test_bytes_to_hex(self): self.assertEqual("", print_util.bytes_to_hex("")) def test_int_to_hex(self): - integer = 1234 # 0x4d2 + integer = 1234 # 0x4d2 self.assertEqual("04:d2", print_util.int_to_hex(integer)) self.assertEqual("04d2", print_util.int_to_hex(integer, delimiter="")) negative_integer = -1234 @@ -25,13 +26,15 @@ def test_int_to_hex(self): def test_wrap_lines(self): long_multiline_string = "hello\nworld" - self.assertEqual(["hel", "lo", "wor", "ld"], - print_util.wrap_lines(long_multiline_string, 3)) + self.assertEqual( + ["hel", "lo", "wor", "ld"], print_util.wrap_lines(long_multiline_string, 3) + ) def test_wrap_lines_no_wrap(self): long_multiline_string = "hello\nworld" - self.assertEqual(["hello", "world"], - print_util.wrap_lines(long_multiline_string, 0)) + self.assertEqual( + ["hello", "world"], print_util.wrap_lines(long_multiline_string, 0) + ) def test_append_lines_appends(self): buf = ["hello"] @@ -47,5 +50,6 @@ def test_append_lines_honours_wrap(self): print_util.append_lines(lines, 10, buf) self.assertEqual(["hello", "beautiful", "world"], buf) + if __name__ == "__main__": unittest.main() diff --git a/smime/pubkey.py b/smime/pubkey.py index a84ad1c..052eaa5 100644 --- a/smime/pubkey.py +++ b/smime/pubkey.py @@ -1,9 +1,11 @@ from __future__ import unicode_literals -from abc import ABCMeta, abstractmethod +from abc import ABCMeta +from abc import abstractmethod from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa class PublicKeyCipher(object): @@ -21,11 +23,11 @@ def parameters(self): class RSAPublicKeyCipher(PublicKeyCipher): - algo = 'rsa' + algo = "rsa" def __init__(self, public_key_info): - rsaparams = public_key_info['public_key'].native - key = rsa.RSAPublicNumbers(rsaparams['public_exponent'], rsaparams['modulus']) + rsaparams = public_key_info["public_key"].native + key = rsa.RSAPublicNumbers(rsaparams["public_exponent"], rsaparams["modulus"]) backend = default_backend() self._cipher = key.public_key(backend) self._padding = padding.PKCS1v15() @@ -37,4 +39,3 @@ def encrypt(self, session_key): def parameters(self): # AlgorithmIdentifier parameters is always NULL return None - diff --git a/smime/test/test_config.py b/smime/test/test_config.py index 1d61b04..61d72df 100644 --- a/smime/test/test_config.py +++ b/smime/test/test_config.py @@ -2,7 +2,8 @@ import os + CRYPTO_TEST_DATA_DIR = "smime/testdata/" def get_test_file_path(filename): - return os.path.join(os.curdir, CRYPTO_TEST_DATA_DIR, filename) \ No newline at end of file + return os.path.join(os.curdir, CRYPTO_TEST_DATA_DIR, filename) diff --git a/smime/tests/__init__.py b/smime/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/smime/tests/conftest.py b/smime/tests/conftest.py new file mode 100644 index 0000000..457b022 --- /dev/null +++ b/smime/tests/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ['smime.tests.fixtures'] diff --git a/smime/tests/fixtures.py b/smime/tests/fixtures.py new file mode 100644 index 0000000..6a297ed --- /dev/null +++ b/smime/tests/fixtures.py @@ -0,0 +1,18 @@ +# _*_ coding: utf-8 _*_ +import os + +import pytest + + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +@pytest.fixture(scope='module') +def base_settings(): + + settings = dict() + settings['testdata_dir'] = os.path.join(BASE_PATH, 'testdata') + settings['carl_public_certificate'] = os.path.join(settings['testdata_dir'], 'CarlRSASelf.pem') + settings['carl_private_certificate'] = os.path.join(settings['testdata_dir'], 'CarlPrivRSASign.pem') + + yield settings diff --git a/smime/tests/test_encrypt.py b/smime/tests/test_encrypt.py new file mode 100644 index 0000000..5077e69 --- /dev/null +++ b/smime/tests/test_encrypt.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# _*_ coding: utf-8 _*_ +import os +import sys +from email import message_from_string +from subprocess import PIPE +from subprocess import Popen +from tempfile import mkstemp + +from smime.api import encrypt + + +def get_cmd_output(args): + child = Popen(args, stdout=PIPE, stderr=PIPE) + result = [] + while True: + for line in iter(child.stdout.readline, ""): + result.append(line) + if child.poll() is not None: + break + if child.returncode != 0: + error = [] + for line in iter(child.stderr.readline, ""): + error.append(line) + sys.stderr.write("Command: %s\n%s" % (" ".join(args), "".join(error))) + return "\n".join(result) + + +def assert_message_to_carl(settings, algorithm): + message = [ + 'From: "Alice" ', + 'To: "Carl" ', + "Subject: A message from python", + "", + "Now you see me.", + ] + with open(settings['carl_public_certificate']) as cert: + result = encrypt(u"\n".join(message), cert.read(), algorithm=algorithm) + + fd, tmp_file = mkstemp() + os.write(fd, result) + + cmd = [ + "openssl", + "smime", + "-decrypt", + "-in", + tmp_file, + "-inkey", + settings['carl_private_certificate'], + ] + cmd_output = get_cmd_output(cmd) + private_message = message_from_string(cmd_output) + payload = private_message.get_payload().splitlines() + + assert "Now you see me." == payload[len(payload) - 1] + + return 1 + + +def test_message_to_carl_aes256_cbc(base_settings): + settings = base_settings + assert assert_message_to_carl(settings, u"aes256_cbc") == 1 + + +def test_message_to_carl_aes192_cbc(base_settings): + settings = base_settings + assert assert_message_to_carl(settings, u"aes192_cbc") == 1 + + +def test_message_to_carl_aes128_cbc(base_settings): + settings = base_settings + assert assert_message_to_carl(settings, u"aes128_cbc") == 1 diff --git a/smime/tests/test_print_util.py b/smime/tests/test_print_util.py new file mode 100755 index 0000000..e6c9c4f --- /dev/null +++ b/smime/tests/test_print_util.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +from smime import print_util + + +def test_bits_to_hex(): + bit_array = [0, 1, 1, 0, 1, 0, 1, 1, 1, 0] + assert "01:ae" == print_util.bits_to_hex(bit_array) + assert "01ae" == print_util.bits_to_hex(bit_array, delimiter="") + assert "" == print_util.bits_to_hex("") + + +def test_bytes_to_hex(): + byte_array = "\x01\xae" + assert "01:ae" == print_util.bytes_to_hex(byte_array) + assert "01ae" == print_util.bytes_to_hex(byte_array, delimiter="") + assert "" == print_util.bytes_to_hex("") + + +def test_int_to_hex(): + integer = 1234 # 0x4d2 + assert "04:d2" == print_util.int_to_hex(integer) + assert "04d2" == print_util.int_to_hex(integer, delimiter="") + + negative_integer = -1234 + assert " -:04:d2" == print_util.int_to_hex(negative_integer) + + +def test_wrap_lines(): + long_multiline_string = "hello\nworld" + assert ["hel", "lo", "wor", "ld"] == print_util.wrap_lines(long_multiline_string, 3) + + +def test_wrap_lines_no_wrap(): + long_multiline_string = "hello\nworld" + assert ["hello", "world"] == print_util.wrap_lines(long_multiline_string, 0) + + +def test_append_lines_appends(): + buf = ["hello"] + lines = ["beautiful", "world"] + # "hellobeautiful" is more than 10 characters long + print_util.append_lines(lines, 20, buf) + assert ["hellobeautiful", "world"] == buf + + +def test_append_lines_honours_wrap(): + buf = ["hello"] + lines = ["beautiful", "world"] + # "hellobeautiful" is more than 10 characters long + print_util.append_lines(lines, 10, buf) + assert ["hello", "beautiful", "world"] == buf diff --git a/smime/tools/cert_util.py b/smime/tools/cert_util.py index 46c1995..90d8f9d 100644 --- a/smime/tools/cert_util.py +++ b/smime/tools/cert_util.py @@ -27,10 +27,11 @@ - print the SHA-256 fingerprint """ +import argparse import sys + from smime import cert from smime import print_util -import argparse def print_cert(args, certificate): diff --git a/smime/tools/cms_util.py b/smime/tools/cms_util.py index 6029762..5ee1308 100755 --- a/smime/tools/cms_util.py +++ b/smime/tools/cms_util.py @@ -18,10 +18,11 @@ cms_util.py --debug message.pem - print full ASN.1 structure """ -import sys import argparse +import sys from base64 import b64decode from traceback import print_exc + from smime.crypto import error from smime.crypto.asn1 import cms @@ -94,5 +95,3 @@ def main(): if __name__ == "__main__": main() - -