diff --git a/Vagrantfile b/Vagrantfile index ddf37ce83..ee7b4c49f 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,13 +8,13 @@ apt-get update apt-get install -y git autoconf libtool make valgrind libpq-dev -URL=https://sourceforge.net/projects/levent/files/libevent -LIB=libevent-2.0 -VER=22-stable +URL=https://github.com/libevent/libevent/releases/download/release +LIB=libevent +VER=2.0.22-stable -wget -q $URL/$LIB/$LIB.$VER.tar.gz && tar -zxf $LIB.$VER.tar.gz -cd $LIB.$VER/ && ./autogen.sh && ./configure -q && make -s -sudo make install && cd .. && rm -rf $LIB.$VER* +wget -q $URL-$VER/$LIB-$VER.tar.gz && tar -zxf $LIB-$VER.tar.gz +cd $LIB-$VER/ && ./autogen.sh && ./configure -q && make -s +sudo make install && cd .. && rm -rf $LIB-$VER* DST=wolfssl diff --git a/wrapper/include.am b/wrapper/include.am index bb61de307..56b63fec4 100644 --- a/wrapper/include.am +++ b/wrapper/include.am @@ -28,3 +28,31 @@ EXTRA_DIST+= wrapper/CSharp/wolfSSL_CSharp/Properties/Resources.Designer.cs EXTRA_DIST+= wrapper/CSharp/wolfSSL_CSharp/Properties/Resources.resx EXTRA_DIST+= wrapper/CSharp/wolfSSL_CSharp/wolfSSL.cs EXTRA_DIST+= wrapper/CSharp/wolfSSL_CSharp/wolfSSL_CSharp.csproj + +# wolfcrypt python wrapper files +EXTRA_DIST+= wrapper/python/.gitignore +EXTRA_DIST+= wrapper/python/docs/asymmetric.rst +EXTRA_DIST+= wrapper/python/docs/conf.py +EXTRA_DIST+= wrapper/python/docs/digest.rst +EXTRA_DIST+= wrapper/python/docs/index.rst +EXTRA_DIST+= wrapper/python/docs/mac.rst +EXTRA_DIST+= wrapper/python/docs/Makefile +EXTRA_DIST+= wrapper/python/docs/random.rst +EXTRA_DIST+= wrapper/python/docs/symmetric.rst +EXTRA_DIST+= wrapper/python/LICENSING.rst +EXTRA_DIST+= wrapper/python/MANIFEST.in +EXTRA_DIST+= wrapper/python/README.rst +EXTRA_DIST+= wrapper/python/requirements-testing.txt +EXTRA_DIST+= wrapper/python/setup.py +EXTRA_DIST+= wrapper/python/test/test_ciphers.py +EXTRA_DIST+= wrapper/python/test/test_hashes.py +EXTRA_DIST+= wrapper/python/test/test_random.py +EXTRA_DIST+= wrapper/python/tox.ini +EXTRA_DIST+= wrapper/python/wolfcrypt/__about__.py +EXTRA_DIST+= wrapper/python/wolfcrypt/__init__.py +EXTRA_DIST+= wrapper/python/wolfcrypt/build_ffi.py +EXTRA_DIST+= wrapper/python/wolfcrypt/ciphers.py +EXTRA_DIST+= wrapper/python/wolfcrypt/exceptions.py +EXTRA_DIST+= wrapper/python/wolfcrypt/hashes.py +EXTRA_DIST+= wrapper/python/wolfcrypt/random.py +EXTRA_DIST+= wrapper/python/wolfcrypt/utils.py diff --git a/wrapper/python/.centos-provisioner.sh b/wrapper/python/.centos-provisioner.sh new file mode 100644 index 000000000..8f74110d2 --- /dev/null +++ b/wrapper/python/.centos-provisioner.sh @@ -0,0 +1,31 @@ +[ "$(whoami)" != "root" ] && echo "Sorry, you are not root." && exit 1 + +rpm -ivh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-6.noarch.rpm +yum update +yum install -y git autoconf libtool + +git clone https://github.com/wolfssl/wolfssl.git +[ $? -ne 0 ] && echo "\n\nCouldn't download wolfssl.\n\n" && exit 1 + +pushd wolfssl + +./autogen.sh +./configure +make +make install +echo /usr/local/lib > wolfssl.conf +mv wolfssl.conf /etc/ld.so.conf +ldconfig + +popd +rm -rf wolfssl + +yum install -y libffi-devel python-devel python-pip + +pip install wolfcrypt +[ $? -ne 0 ] && echo "\n\nCouldn't install wolfcrypt.\n\n" && exit 1 + +echo "Test should print:" +echo "da39a3ee5e6b4b0d3255bfef95601890afd80709" +echo "Running test:" +python -c "from wolfcrypt.hashes import Sha; print(Sha().hexdigest())" diff --git a/wrapper/python/.gitignore b/wrapper/python/.gitignore new file mode 100644 index 000000000..421703396 --- /dev/null +++ b/wrapper/python/.gitignore @@ -0,0 +1,15 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution +build/ +dist/ +.eggs/ +*.egg-info/ + +# Unit test +.tox/ +# Sphinx documentation +docs/_build/ diff --git a/wrapper/python/.ubuntu-provisioner.sh b/wrapper/python/.ubuntu-provisioner.sh new file mode 100644 index 000000000..ab9e54cb3 --- /dev/null +++ b/wrapper/python/.ubuntu-provisioner.sh @@ -0,0 +1,28 @@ +[ "$(whoami)" != "root" ] && echo "Sorry, you are not root." && exit 1 + +apt-get update +apt-get install -y git autoconf libtool + +git clone https://github.com/wolfssl/wolfssl.git +[ $? -ne 0 ] && echo "\n\nCouldn't download wolfssl.\n\n" && exit 1 + +pushd wolfssl + +./autogen.sh +./configure +make +make install +ldconfig + +popd +rm -rf wolfssl + +apt-get install -y libffi-dev python-dev python-pip + +pip install wolfcrypt +[ $? -ne 0 ] && echo "\n\nCouldn't install wolfcrypt.\n\n" && exit 1 + +echo "Test should print:" +echo "da39a3ee5e6b4b0d3255bfef95601890afd80709" +echo "Running test:" +python -c "from wolfcrypt.hashes import Sha; print(Sha().hexdigest())" diff --git a/wrapper/python/LICENSING.rst b/wrapper/python/LICENSING.rst new file mode 100644 index 000000000..a7f0f3727 --- /dev/null +++ b/wrapper/python/LICENSING.rst @@ -0,0 +1,23 @@ +Licensing +--------- + +wolfSSL’s software is available under two distinct licensing models: +open source and standard commercial licensing. Please see the relevant +section below for information on each type of license. + +Open Source +~~~~~~~~~~~ + +wolfCrypt and wolfSSL software are free software downloads and may be modified +to the needs of the user as long as the user adheres to version two of the GPL +License. The GPLv2 license can be found on the `gnu.org website +`_. + +Commercial Licensing +~~~~~~~~~~~~~~~~~~~~ + +Businesses and enterprises who wish to incorporate wolfSSL products into +proprietary appliances or other commercial software products for +re-distribution must license commercial versions. Licenses are generally issued +for one product and include unlimited royalty-free distribution. Custom +licensing terms are also available at licensing@wolfssl.com. diff --git a/wrapper/python/MANIFEST.in b/wrapper/python/MANIFEST.in new file mode 100644 index 000000000..3c56fcf44 --- /dev/null +++ b/wrapper/python/MANIFEST.in @@ -0,0 +1 @@ +include LICENSING.rst diff --git a/wrapper/python/README.rst b/wrapper/python/README.rst new file mode 100644 index 000000000..87121192b --- /dev/null +++ b/wrapper/python/README.rst @@ -0,0 +1,144 @@ + + +wolfcrypt: the wolfSSL Crypto Engine +==================================== + +**wolfCrypt Python**, a.k.a. ``wolfcrypt`` is a Python library that encapsulates +**wolfSSL's wolfCrypt API**. + +`wolfCrypt `_ is a +lightweight, portable, C-language-based crypto library +targeted at IoT, embedded, and RTOS environments primarily because of its size, +speed, and feature set. It works seamlessly in desktop, enterprise, and cloud +environments as well. It is the crypto engine behind `wolfSSl's embedded ssl +library `_. + + +Installation +------------ + +In order to use ``wolfcrypt``, first you'll need to install ``wolfssl`` C +embedded ssl library. + +Installing ``wolfssl`` : +~~~~~~~~~~~~~~~~~~~~~~~~ + +**Mac OSX** + +.. code-block:: console + + brew install wolfssl + +or + +.. code-block:: console + + git clone https://github.com/wolfssl/wolfssl.git + cd wolfssl/ + ./autogen.sh + ./configure --enable-sha512 + make + sudo make install + + +**Ubuntu** + +.. code-block:: console + + sudo apt-get update + sudo apt-get install -y git autoconf libtool + + git clone https://github.com/wolfssl/wolfssl.git + cd wolfssl/ + ./autogen.sh + ./configure --enable-sha512 + make + sudo make install + + sudo ldconfig + +**CentOS** + +.. code-block:: console + + sudo rpm -ivh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-6.noarch.rpm + sudo yum update + sudo yum install -y git autoconf libtool + + git clone git@github.com:wolfssl/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-sha512 + make + sudo make install + + echo /usr/local/lib > wolfssl.conf + sudo mv wolfssl.conf /etc/ld.so.conf + sudo ldconfig + + +Installing ``wolfcrypt`` : +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Mac OSX** + +.. code-block:: console + + sudo -H pip install wolfcrypt + + +**Ubuntu** + +.. code-block:: console + + sudo apt-get install -y python-dev python3-dev python-pip libffi-dev + sudo -H pip install wolfcrypt + + +**CentOS** + +.. code-block:: console + + sudo yum install -y python-devel python3-devel python-pip libffi-devel + sudo -H pip install wolfcrypt + + +Testing ``wolfcrypt`` : +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: console + + python -c "from wolfcrypt.hashes import Sha; print Sha().hexdigest()" + +expected output: **da39a3ee5e6b4b0d3255bfef95601890afd80709** + + +Testing ``wolfcrypt``'s source code with ``tox`` : +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run the unit tests in the source code, you'll need ``tox`` and a few other +requirements. The source code relies at 'WOLFSSL_DIR/wrapper/python' where +WOLFSSL_DIR is the path of ``wolfssl``'s source code. + +1. Make sure that the testing requirements are installed: + +.. code-block:: console + + $ sudo -H pip install -r requirements-testing.txt + + +2. Run ``tox``: + +.. code-block:: console + + $ tox + ... + _________________________________ summary _________________________________ + py27: commands succeeded + SKIPPED: py34: InterpreterNotFound: python3.4 + py35: commands succeeded + congratulations :) + +Note: the test is performed using multiple versions of python. If you are +missing a version the test will be skipped with an **InterpreterNotFound +error**. diff --git a/wrapper/python/Vagrantfile b/wrapper/python/Vagrantfile new file mode 100644 index 000000000..e164331df --- /dev/null +++ b/wrapper/python/Vagrantfile @@ -0,0 +1,14 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : +BOX = "ubuntu" +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + if BOX == "ubuntu" + config.vm.box = "ubuntu/trusty64" + config.vm.provision "shell", path: ".ubuntu-provisioner.sh" + else + config.vm.box = "moisesguimaraes/centos72-64" + config.vm.provision "shell", path: ".centos-provisioner.sh" + end +end diff --git a/wrapper/python/docs/Makefile b/wrapper/python/docs/Makefile new file mode 100644 index 000000000..c552bc9b3 --- /dev/null +++ b/wrapper/python/docs/Makefile @@ -0,0 +1,230 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) + $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/wolfcrypt.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/wolfcrypt.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/wolfcrypt" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wolfcrypt" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/wrapper/python/docs/asymmetric.rst b/wrapper/python/docs/asymmetric.rst new file mode 100644 index 000000000..970078dee --- /dev/null +++ b/wrapper/python/docs/asymmetric.rst @@ -0,0 +1,74 @@ +Asymmetric Key Algorithms +========================= + +.. module:: wolfcrypt.ciphers + +**Asymmetric key algorithms** are encryption algorithms that use **a pair +of cryptographic keys**, one for data encryption and signing and the other +one for data decryption and signature verification. + +``wolfcrypt`` provides access to the following **Asymmetric Key Ciphers**: + +Asymmetric Key Encryption Classes +--------------------------------- + +.. autoclass:: RsaPublic + :members: + :inherited-members: + +.. autoclass:: RsaPrivate + :members: + :inherited-members: + + +Example +------- + + >>> from wolfcrypt.ciphers import RsaPrivate, RsaPublic + >>> from wolfcrypt.utils import h2b + >>> + >>> private = "3082025C02010002818100BC730EA849F374A2A9EF18A5DA559921F9C8ECB36D" \ + ... + "48E53535757737ECD161905F3ED9E4D5DF94CAC1A9D719DA86C9E84DC4613682" \ + ... + "FEABAD7E7725BB8D11A5BC623AA838CC39A20466B4F7F7F3AADA4D020EBB5E8D" \ + ... + "6948DC77C9280E22E96BA426BA4CE8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B" \ + ... + "3C67C8DC2700F6916865A902030100010281801397EAE8387825A25C04CE0D40" \ + ... + "7C31E5C470CD9B823B5809863B665FDC3190F14FD5DB15DDDED73B9593311831" \ + ... + "0E5EA3D6A21A716E81481C4BCFDB8E7A866132DCFB55C1166D279224458BF1B8" \ + ... + "48B14B1DACDEDADD8E2FC291FBA5A96EF83A6AF1FD5018EF9FE7C3CA78EA56D3" \ + ... + "D3725B96DD4E064E3AC3D9BE72B66507074C01024100FA47D47A7C923C55EF81" \ + ... + "F041302DA3CF8F1CE6872705700DDF9835D6F18B382F24B5D084B6794F712994" \ + ... + "5AF0646AACE772C6ED4D59983E673AF3742CF9611769024100C0C1820D0CEBC6" \ + ... + "2FDC92F99D821A31E9E9F74BF282871CEE166AD11D188270F3C0B62FF6F3F71D" \ + ... + "F18623C84EEB8F568E8FF5BFF1F72BB5CC3DC657390C1B54410241009D7E05DE" \ + ... + "EDF4B7B2FBFC304B551DE32F0147966905CD0E2E2CBD8363B6AB7CB76DCA5B64" \ + ... + "A7CEBE86DF3B53DE61D21EEBA5F637EDACAB78D94CE755FBD71199C102401898" \ + ... + "1829E61E2739702168AC0A2FA172C121869538C65890A0579CBAE3A7B115C8DE" \ + ... + "F61BC2612376EFB09D1C44BE1343396717C89DCAFBF545648B38822CF2810240" \ + ... + "3989E59C195530BAB7488C48140EF49F7E779743E1B419353123759C3B44AD69" \ + ... + "1256EE0061641666D37C742B15B4A2FEBF086B1A5D3F9012B105863129DBD9E2" + >>> + >>> prv = RsaPrivate(h2b(private)) + >>> + >>> public = "30819F300D06092A864886F70D010101050003818D0030818902818100BC730E" \ + ... + "A849F374A2A9EF18A5DA559921F9C8ECB36D48E53535757737ECD161905F3ED9" \ + ... + "E4D5DF94CAC1A9D719DA86C9E84DC4613682FEABAD7E7725BB8D11A5BC623AA8" \ + ... + "38CC39A20466B4F7F7F3AADA4D020EBB5E8D6948DC77C9280E22E96BA426BA4C" \ + ... + "E8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B3C67C8DC2700F6916865A90203010001" + >>> + >>> pub = RsaPublic(h2b(public)) + >>> + >>> plaintext = b"Everyone gets Friday off." + >>> + >>> ciphertext = pub.encrypt(plaintext) + >>> ciphertext # doctest: +SKIP + b'e\xb7\xc2\xad\x0c\x04.\xefU8\x17QB\x852\x03\x01\xef\xbe=\xb4\xaf\xaf\x97\x9e4\x96\x9f\xc3\x8e\x87\x9a8o$.|_e\x1d\xa2yi?\x83\x18\xf9Yr|\x1fQ\x1a\x18\x1e\xab\xd17\xc5\x8c\xae\x08c)\xbc\nIr\x8d\xc3\x88\x7f\xde\x1f\x1a^lB\r\xf1\xc0\xfd0\xdeA\xf3\xd2\xe5q\x9a0\xee\xb4,\x97\x80\xa4|U;\xe6\x11\xf0\xc2Q\x987\xe1>F\xf5\x14\x186@G~(Q\xf2;\xcb\x05\xee\x88\x0b\xd8\xa7' + >>> + >>> prv.decrypt(ciphertext) + b'Everyone gets Friday off.' + >>> + >>> signature = prv.sign(plaintext) + >>> signature # doctest: +SKIP + b'~\xc4\xe65\x15\xb17\x7fX\xaf,\xc2lw\xbd\x8f\t\x9d\xbf\xac\xdez\x90\xb4\x9f\x1aM\x88#Z\xea\xcb\xa6\xdb\x99\xf55\xd0\xfe|Mu\xb6\xb79(t\x81+h\xf2\xcd\x88v\xa8\xbaM\x86\xcfk\xe8\xf3\x0b\xb8\x8ew\xda>\xf8\xd5[H\xeaAh\xc6\xdaQlo]\xdd\xf8w\xe7#M-\x12f\xae,\xdd\xa6d FP<;R\xa2\x96hJ\xee_\x1fh\xaa\xc8\xdfAJ\xa5\xdd\x05\xc4\x89\x0c\xd7\xa0C\xb7u"U\x03' + >>> + >>> pub.verify(signature) + b'Everyone gets Friday off.' diff --git a/wrapper/python/docs/conf.py b/wrapper/python/docs/conf.py new file mode 100644 index 000000000..3e9de701c --- /dev/null +++ b/wrapper/python/docs/conf.py @@ -0,0 +1,299 @@ +# -*- coding: utf-8 -*- +# +# wolfcrypt documentation build configuration file, created by +# sphinx-quickstart on Fri Apr 29 16:47:53 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import sphinx_rtd_theme + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'wolfcrypt' +copyright = u'2016, wolfSSL Inc. All rights reserved' +author = u'wolfSSL' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +base_dir = os.path.join(os.path.dirname(__file__), os.pardir) +about = {} +with open(os.path.join(base_dir, "wolfcrypt", "__about__.py")) as f: + exec(f.read(), about) + +version = release = about["__version__"] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +#html_title = u'%s v%s' % (project, release) + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +#html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'wolfcrypt-pydoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'wolfcrypt.tex', u'wolfcrypt Python Documentation', + u'wolfSSL', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'wolfcrypt', u'wolfcrypt Python Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'wolfcrypt', u'wolfcrypt Python Documentation', + author, 'wolfcrypt', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + +# Preserves the order of the members, doesn't sorts them alphabetically. +autodoc_member_order = 'bysource' diff --git a/wrapper/python/docs/digest.rst b/wrapper/python/docs/digest.rst new file mode 100644 index 000000000..0d79f8ef0 --- /dev/null +++ b/wrapper/python/docs/digest.rst @@ -0,0 +1,71 @@ +Message Digests +=============== + +.. module:: wolfcrypt.hashes + +A **message digest** is the output of a **cryptographic hash function** +containing a string of bytes created by a **one-way formula** using the +original message as input. + +Message digests are designed to protect the integrity of a piece of data or +media to detect changes and alterations to any part of a message. + + +Hashing Classes +--------------- + +Interface +~~~~~~~~~ + +All Hashing Functions available in this module implements the following +interface: + +.. autoclass:: _Hash + :members: + +SHA-1 +~~~~~ + +.. attention:: + + NIST has deprecated SHA-1 in favor of the SHA-2 variants. New applications + are strongly suggested to use SHA-2 over SHA-1. + +.. autoclass:: Sha + +SHA-2 family +~~~~~~~~~~~~ + +.. autoclass:: Sha256 + + +.. autoclass:: Sha384 + + +.. autoclass:: Sha512 + + +Example +------- + +.. doctest:: + + >>> from wolfcrypt.hashes import Sha256 + >>> + >>> s = Sha256() + >>> s.update(b'wolf') + >>> s.update(b'crypt') + >>> s.digest() + b'\x96\xe0.{\x1c\xbc\xd6\xf1\x04\xfe\x1f\xdbFR\x02zU\x05\xb6\x86R\xb7\x00\x95\xc61\x8f\x9d\xce\r\x18D' + >>> s.hexdigest() + b'96e02e7b1cbcd6f104fe1fdb4652027a5505b68652b70095c6318f9dce0d1844' + >>> + >>> s.update(b'rocks') + >>> s.hexdigest() + b'e1a50df419d65715c48316bdc6a6f7f0485f4b26c1b107228faf17988b61c83f' + >>> + >>> Sha256(b'wolfcryptrocks').hexdigest() + b'e1a50df419d65715c48316bdc6a6f7f0485f4b26c1b107228faf17988b61c83f' + >>> + >>> Sha256.new(b'wolfcryptrocks').hexdigest() + b'e1a50df419d65715c48316bdc6a6f7f0485f4b26c1b107228faf17988b61c83f' diff --git a/wrapper/python/docs/index.rst b/wrapper/python/docs/index.rst new file mode 100644 index 000000000..efd3431a8 --- /dev/null +++ b/wrapper/python/docs/index.rst @@ -0,0 +1,15 @@ +.. include:: ../README.rst + +Summary +------- + +.. toctree:: + :maxdepth: 1 + + symmetric + asymmetric + digest + mac + random + +.. include:: ../LICENSING.rst diff --git a/wrapper/python/docs/mac.rst b/wrapper/python/docs/mac.rst new file mode 100644 index 000000000..14b39cd78 --- /dev/null +++ b/wrapper/python/docs/mac.rst @@ -0,0 +1,74 @@ +Message Authentication Codes +============================ + +.. module:: wolfcrypt.hashes + +A **message authentication code** (MAC) is a short piece of information used +to authenticate a message — in other words, to confirm that the message came +from the stated sender (its authenticity) and has not been changed in transit +(its integrity). + +``wolfcrypt`` implements the **Hash-based message authentication code** (HMAC), +which uses a cryptographic hash function coupled with a secret key to produce +**message authentication codes**. + + +Hmac Classes +------------ + +Interface +~~~~~~~~~ + +All Hmac classes available in this module implements the following +interface: + +.. autoclass:: _Hmac + :members: + :inherited-members: + +SHA-1 +~~~~~ + +.. attention:: + + NIST has deprecated SHA-1 in favor of the SHA-2 variants. New applications + are strongly suggested to use SHA-2 over SHA-1. + +.. autoclass:: HmacSha + +SHA-2 family +~~~~~~~~~~~~ + +.. autoclass:: HmacSha256 + + +.. autoclass:: HmacSha384 + + +.. autoclass:: HmacSha512 + + +Example +------- + +.. doctest:: + + >>> from wolfcrypt.hashes import HmacSha256 + >>> + >>> h = HmacSha256('secret') + >>> h.update("wolf") + >>> h.update("crypt") + >>> h.digest() + b'\x18\xbf*\t9\xa2o\xdf\\\xc8\xe0\xc2U\x94,\x8dY\x02;\x1c>> h.hexdigest() + b'18bf2a0939a26fdf5cc8e0c255942c8d59023b1c3c51df8ddb8633fbc166236f' + >>> + >>> h.update("rocks") + >>> h.hexdigest() + b'85dc8c1995d20b17942d52773d8a597d028ad958e5736beafb59a4742f63889e' + >>> + >>> HmacSha256('secret', 'wolfcryptrocks').hexdigest() + b'85dc8c1995d20b17942d52773d8a597d028ad958e5736beafb59a4742f63889e' + >>> + >>> HmacSha256.new('secret', 'wolfcryptrocks').hexdigest() + b'85dc8c1995d20b17942d52773d8a597d028ad958e5736beafb59a4742f63889e' diff --git a/wrapper/python/docs/random.rst b/wrapper/python/docs/random.rst new file mode 100644 index 000000000..ba8e33bda --- /dev/null +++ b/wrapper/python/docs/random.rst @@ -0,0 +1,30 @@ +Random Number Generation +======================== + +A **cryptographically secure pseudo-random number generator** (CSPRNG) is a +**pseudo-random number generator** (PRNG) with properties that make it suitable +for use in cryptography. + +Using the standard random module APIs for cryptographic keys or initialization +vectors can result in major security issues depending on the algorithms in use. + +``wolfcrypt`` provides the following CSPRNG implementation: + +.. module:: wolfcrypt.random + +.. autoclass:: Random + :members: + + +Example +------- + + >>> from wolfcrypt.random import Random + >>> + >>> r = Random() + >>> b = r.byte() + >>> b # doctest: +SKIP + b'\x8c' + >>> b16 = r.bytes(16) + >>> b16 # doctest: +SKIP + b']\x93nk\x95\xbc@\xffX\xab\xdcB\xda\x11\xf7\x03' diff --git a/wrapper/python/docs/symmetric.rst b/wrapper/python/docs/symmetric.rst new file mode 100644 index 000000000..0535080b0 --- /dev/null +++ b/wrapper/python/docs/symmetric.rst @@ -0,0 +1,44 @@ +Symmetric Key Algorithms +======================== + +.. module:: wolfcrypt.ciphers + +**Symmetric key algorithms** are encryption algorithms that use the **same +cryptographic keys** for both encryption and decryption of data. +This operation is also known as **Symmetric Key Encryption**. + +``wolfcrypt`` provides access to the following **Symmetric Key Ciphers**: + +Symmetric Key Encryption Classes +-------------------------------- + +Interface +~~~~~~~~~ + +All **Symmetric Key Ciphers** available in this module implements the following +interface: + +.. autoclass:: _Cipher + :members: + +Classes +~~~~~~~ + +.. autoclass:: Aes + +.. autoclass:: Des3 + + +Example +------- + +.. doctest:: + + >>> from wolfcrypt.ciphers import Aes, MODE_CBC + >>> + >>> cipher = Aes(b'0123456789abcdef', MODE_CBC, b'1234567890abcdef') + >>> ciphertext = cipher.encrypt('now is the time ') + >>> ciphertext + b'\x95\x94\x92W_B\x81S,\xcc\x9dFw\xa23\xcb' + >>> cipher.decrypt(ciphertext) + b'now is the time ' diff --git a/wrapper/python/requirements-testing.txt b/wrapper/python/requirements-testing.txt new file mode 100644 index 000000000..61def5278 --- /dev/null +++ b/wrapper/python/requirements-testing.txt @@ -0,0 +1,3 @@ +pytest>=2.9.1 +cffi>=1.6.0 +tox>=2.3.1 diff --git a/wrapper/python/setup.py b/wrapper/python/setup.py new file mode 100755 index 000000000..1eeaed17e --- /dev/null +++ b/wrapper/python/setup.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +# Python 2.7 Standard Library +from __future__ import absolute_import +import os +import sys +from wolfcrypt.__about__ import metadata +from setuptools import setup, find_packages + +os.chdir(os.path.dirname(sys.argv[0]) or ".") + +long_description = open("README.rst", "rt").read().replace( + ".. include:: LICENSING.rst\n", + open("LICENSING.rst", "rt").read() +) + +info = dict( + metadata = {k[2:-2]: metadata[k] for k in metadata}, + contents = { + "long_description": long_description, + "package_data": {"": ["*.txt"]}, + "packages": find_packages(), + "cffi_modules": ["./wolfcrypt/build_ffi.py:ffi"], + }, + requirements = { + "setup_requires": ["cffi>=1.6.0"], + "install_requires": ["cffi>=1.6.0"], + }, + scripts = {}, + plugins = {}, + tests = {}, +) + +if __name__ == "__main__": + kwargs = {k:v for dct in info.values() for (k,v) in dct.items()} + setup(**kwargs) diff --git a/wrapper/python/test/test_ciphers.py b/wrapper/python/test/test_ciphers.py new file mode 100644 index 000000000..c1929c89e --- /dev/null +++ b/wrapper/python/test/test_ciphers.py @@ -0,0 +1,246 @@ +# test_ciphers.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +import unittest +from wolfcrypt.ciphers import * +from wolfcrypt.utils import t2b, h2b + +class TestDes3(unittest.TestCase): + key = h2b("0123456789abcdeffedeba987654321089abcdef01234567") + IV = h2b("1234567890abcdef") + plain = t2b("Now is the time for all ") + cipher = h2b("43a0297ed184f80e8964843212d508981894157487127db0") + + + def setUp(self): + self.des3 = Des3.new(self.key, MODE_CBC, self.IV) + + + def test_raises(self): + # invalid key length + self.assertRaises(ValueError, Des3.new, "key", MODE_CBC, self.IV) + + # invalid mode + self.assertRaises(ValueError, Des3.new, self.key, MODE_ECB, self.IV) + + # invalid iv length + self.assertRaises(ValueError, Des3.new, self.key, MODE_CBC, "IV") + + # invalid data length + self.assertRaises(ValueError, self.des3.encrypt, "foo") + self.assertRaises(ValueError, self.des3.decrypt, "bar") + + + def test_single_encryption(self): + assert self.des3.encrypt(self.plain) == self.cipher + + + def test_multi_encryption(self): + result = t2b("") + segments = tuple(self.plain[i:i + Des3.block_size] \ + for i in range(0, len(self.plain), Des3.block_size)) + + for segment in segments: + result += self.des3.encrypt(segment) + + assert result == self.cipher + + + def test_single_decryption(self): + assert self.des3.decrypt(self.cipher) == self.plain + + + def test_multi_decryption(self): + result = t2b("") + segments = tuple(self.cipher[i:i + Des3.block_size] \ + for i in range(0, len(self.cipher), Des3.block_size)) + + for segment in segments: + result += self.des3.decrypt(segment) + + assert result == self.plain + + +class TestAes(unittest.TestCase): + key = "0123456789abcdef" + IV = "1234567890abcdef" + plain = t2b("now is the time ") + cipher = h2b("959492575f4281532ccc9d4677a233cb") + + + def setUp(self): + self.aes = Aes.new(self.key, MODE_CBC, self.IV) + + + def test_raises(self): + # invalid key length + self.assertRaises(ValueError, Aes.new, "key", MODE_CBC, self.IV) + + # invalid mode + self.assertRaises(ValueError, Aes.new, self.key, MODE_ECB, self.IV) + + # invalid iv length + self.assertRaises(ValueError, Aes.new, self.key, MODE_CBC, "IV") + + # invalid data length + self.assertRaises(ValueError, self.aes.encrypt, "foo") + self.assertRaises(ValueError, self.aes.decrypt, "bar") + + + def test_single_encryption(self): + assert self.aes.encrypt(self.plain) == self.cipher + + + def test_multi_encryption(self): + result = t2b("") + segments = tuple(self.plain[i:i + self.aes.block_size] \ + for i in range(0, len(self.plain), self.aes.block_size)) + + for segment in segments: + result += self.aes.encrypt(segment) + + assert result == self.cipher + + + def test_single_decryption(self): + assert self.aes.decrypt(self.cipher) == self.plain + + + def test_multi_decryption(self): + result = t2b("") + segments = tuple(self.cipher[i:i + self.aes.block_size] \ + for i in range(0, len(self.cipher), self.aes.block_size)) + + for segment in segments: + result += self.aes.decrypt(segment) + + assert result == self.plain + + +class TestRsaPrivate(unittest.TestCase): + key = "3082025C02010002818100BC730EA849F374A2A9EF18A5DA559921F9C8ECB36D" \ + + "48E53535757737ECD161905F3ED9E4D5DF94CAC1A9D719DA86C9E84DC4613682" \ + + "FEABAD7E7725BB8D11A5BC623AA838CC39A20466B4F7F7F3AADA4D020EBB5E8D" \ + + "6948DC77C9280E22E96BA426BA4CE8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B" \ + + "3C67C8DC2700F6916865A902030100010281801397EAE8387825A25C04CE0D40" \ + + "7C31E5C470CD9B823B5809863B665FDC3190F14FD5DB15DDDED73B9593311831" \ + + "0E5EA3D6A21A716E81481C4BCFDB8E7A866132DCFB55C1166D279224458BF1B8" \ + + "48B14B1DACDEDADD8E2FC291FBA5A96EF83A6AF1FD5018EF9FE7C3CA78EA56D3" \ + + "D3725B96DD4E064E3AC3D9BE72B66507074C01024100FA47D47A7C923C55EF81" \ + + "F041302DA3CF8F1CE6872705700DDF9835D6F18B382F24B5D084B6794F712994" \ + + "5AF0646AACE772C6ED4D59983E673AF3742CF9611769024100C0C1820D0CEBC6" \ + + "2FDC92F99D821A31E9E9F74BF282871CEE166AD11D188270F3C0B62FF6F3F71D" \ + + "F18623C84EEB8F568E8FF5BFF1F72BB5CC3DC657390C1B54410241009D7E05DE" \ + + "EDF4B7B2FBFC304B551DE32F0147966905CD0E2E2CBD8363B6AB7CB76DCA5B64" \ + + "A7CEBE86DF3B53DE61D21EEBA5F637EDACAB78D94CE755FBD71199C102401898" \ + + "1829E61E2739702168AC0A2FA172C121869538C65890A0579CBAE3A7B115C8DE" \ + + "F61BC2612376EFB09D1C44BE1343396717C89DCAFBF545648B38822CF2810240" \ + + "3989E59C195530BAB7488C48140EF49F7E779743E1B419353123759C3B44AD69" \ + + "1256EE0061641666D37C742B15B4A2FEBF086B1A5D3F9012B105863129DBD9E2" + + plain = t2b("Everyone gets Friday off.") + + + def setUp(self): + self.rsa = RsaPrivate(h2b(self.key)) + + + def test_raises(self): + # invalid key + self.assertRaises(WolfCryptError, RsaPrivate, 'key') + + + def test_output_size(self): + assert self.rsa.output_size == 1024 / 8 + + + def test_encrypt_decrypt(self): + cipher = self.rsa.encrypt(self.plain) + result = self.rsa.decrypt(cipher) + + assert len(cipher) == self.rsa.output_size == 1024 / 8 + assert self.plain == result + + + def test_sign_verify(self): + signature = self.rsa.sign(self.plain) + result = self.rsa.verify(signature) + + assert len(signature) == self.rsa.output_size == 1024 / 8 + assert self.plain == result + + +class TestRsaPublic(unittest.TestCase): + prv = "3082025C02010002818100BC730EA849F374A2A9EF18A5DA559921F9C8ECB36D" \ + + "48E53535757737ECD161905F3ED9E4D5DF94CAC1A9D719DA86C9E84DC4613682" \ + + "FEABAD7E7725BB8D11A5BC623AA838CC39A20466B4F7F7F3AADA4D020EBB5E8D" \ + + "6948DC77C9280E22E96BA426BA4CE8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B" \ + + "3C67C8DC2700F6916865A902030100010281801397EAE8387825A25C04CE0D40" \ + + "7C31E5C470CD9B823B5809863B665FDC3190F14FD5DB15DDDED73B9593311831" \ + + "0E5EA3D6A21A716E81481C4BCFDB8E7A866132DCFB55C1166D279224458BF1B8" \ + + "48B14B1DACDEDADD8E2FC291FBA5A96EF83A6AF1FD5018EF9FE7C3CA78EA56D3" \ + + "D3725B96DD4E064E3AC3D9BE72B66507074C01024100FA47D47A7C923C55EF81" \ + + "F041302DA3CF8F1CE6872705700DDF9835D6F18B382F24B5D084B6794F712994" \ + + "5AF0646AACE772C6ED4D59983E673AF3742CF9611769024100C0C1820D0CEBC6" \ + + "2FDC92F99D821A31E9E9F74BF282871CEE166AD11D188270F3C0B62FF6F3F71D" \ + + "F18623C84EEB8F568E8FF5BFF1F72BB5CC3DC657390C1B54410241009D7E05DE" \ + + "EDF4B7B2FBFC304B551DE32F0147966905CD0E2E2CBD8363B6AB7CB76DCA5B64" \ + + "A7CEBE86DF3B53DE61D21EEBA5F637EDACAB78D94CE755FBD71199C102401898" \ + + "1829E61E2739702168AC0A2FA172C121869538C65890A0579CBAE3A7B115C8DE" \ + + "F61BC2612376EFB09D1C44BE1343396717C89DCAFBF545648B38822CF2810240" \ + + "3989E59C195530BAB7488C48140EF49F7E779743E1B419353123759C3B44AD69" \ + + "1256EE0061641666D37C742B15B4A2FEBF086B1A5D3F9012B105863129DBD9E2" + + pub = "30819F300D06092A864886F70D010101050003818D0030818902818100BC730E" \ + + "A849F374A2A9EF18A5DA559921F9C8ECB36D48E53535757737ECD161905F3ED9" \ + + "E4D5DF94CAC1A9D719DA86C9E84DC4613682FEABAD7E7725BB8D11A5BC623AA8" \ + + "38CC39A20466B4F7F7F3AADA4D020EBB5E8D6948DC77C9280E22E96BA426BA4C" \ + + "E8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B3C67C8DC2700F6916865A90203010001" + + plain = t2b("Everyone gets Friday off.") + + + def setUp(self): + self.private = RsaPrivate(h2b(self.prv)) + self.public = RsaPublic(h2b(self.pub)) + + + def test_raises(self): + # invalid key + self.assertRaises(WolfCryptError, RsaPublic, 'key') + + + def test_output_size(self): + assert self.public.output_size == 1024 / 8 + + + def test_encrypt_decrypt(self): + cipher = self.public.encrypt(self.plain) + result = self.private.decrypt(cipher) + + assert len(cipher) == self.public.output_size == 1024 / 8 + assert self.plain == result + + + def test_sign_verify(self): + signature = self.private.sign(self.plain) + result = self.public.verify(signature) + + assert len(signature) == self.public.output_size == 1024 / 8 + assert self.plain == result diff --git a/wrapper/python/test/test_hashes.py b/wrapper/python/test/test_hashes.py new file mode 100644 index 000000000..79e953e9c --- /dev/null +++ b/wrapper/python/test/test_hashes.py @@ -0,0 +1,149 @@ +# test_hashes.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +import unittest +from wolfcrypt.hashes import * +from wolfcrypt.utils import t2b, h2b + + +class TestSha(unittest.TestCase): + _class = Sha + digest = t2b("1b6182d68ae91ce0853bd9c6b6edfedd4b6a510d") + + + def setUp(self): + self.hash = self._class() + + + def test_new(self): + # update inside constructor + assert self._class.new("wolfcrypt").hexdigest() == self.digest + + + def test_hash_update_001(self): + self.hash.update("wolfcrypt") + + assert self.hash.hexdigest() == self.digest + assert self.hash.digest() == h2b(self.digest) + + + def test_hash_update_002(self): + self.hash.update("wolf") + self.hash.update("crypt") + + assert self.hash.hexdigest() == self.digest + assert self.hash.digest() == h2b(self.digest) + + + def test_hash_copy(self): + copy = self.hash.copy() + + assert self.hash.hexdigest() == copy.hexdigest() + + self.hash.update("wolfcrypt") + + assert self.hash.hexdigest() != copy.hexdigest() + + copy.update("wolfcrypt") + + assert self.hash.hexdigest() == copy.hexdigest() == self.digest + + +class TestSha256(TestSha): + _class = Sha256 + digest = t2b("96e02e7b1cbcd6f104fe1fdb4652027a" \ + + "5505b68652b70095c6318f9dce0d1844") + + +class TestSha384(TestSha): + _class = Sha384 + digest = t2b("4c79d80531203a16f91bee325f18c6aada47f9382fe44fc1" \ + + "1f92917837e9b7902f5dccb7d3656f667a1dce3460bc884b") + + +class TestSha512(TestSha): + _class = Sha512 + digest = t2b("88fcf67ffd8558d713f9cedcd852db47" \ + + "9e6573f0bd9955610a993f609637553c" \ + + "e8fff55e644ee8a106aae19c07f91b3f" \ + + "2a2a6d40dfa7302c0fa6a1a9a5bfa03f") + + +_HMAC_KEY = "python" + + +class TestHmacSha(unittest.TestCase): + _class = HmacSha + digest = t2b("5dfabcfb3a25540824867cd21f065f52f73491e0") + + + def setUp(self): + self.hash = self._class(_HMAC_KEY) + + + def test_new(self): + # update inside constructor + assert self._class.new(_HMAC_KEY,"wolfcrypt").hexdigest() == self.digest + + + def test_hash_update_001(self): + self.hash.update("wolfcrypt") + + assert self.hash.hexdigest() == self.digest + + + def test_hash_update_002(self): + self.hash.update("wolf") + self.hash.update("crypt") + + assert self.hash.hexdigest() == self.digest + + + def test_hash_copy(self): + copy = self.hash.copy() + + assert self.hash.hexdigest() == copy.hexdigest() + + self.hash.update("wolfcrypt") + + assert self.hash.hexdigest() != copy.hexdigest() + + copy.update("wolfcrypt") + + assert self.hash.hexdigest() == copy.hexdigest() == self.digest + + +class TestHmacSha256(TestHmacSha): + _class = HmacSha256 + digest = t2b("4b641d721493d80f019d9447830ebfee" \ + + "89234a7d594378b89f8bb73873576bf6") + + +class TestHmacSha384(TestHmacSha): + _class = HmacSha384 + digest = t2b("e72c72070c9c5c78e3286593068a510c1740cdf9dc34b512" \ + + "ccec97320295db1fe673216b46fe72e81f399a9ec04780ab") + + +class TestHmacSha512(TestHmacSha): + _class = HmacSha512 + digest = t2b("c7f48db79314fc2b5be9a93fd58601a1" \ + + "bf42f397ec7f66dba034d44503890e6b" \ + + "5708242dcd71a248a78162d815c685f6" \ + + "038a4ac8cb34b8bf18986dbd300c9b41") diff --git a/wrapper/python/test/test_random.py b/wrapper/python/test/test_random.py new file mode 100644 index 000000000..7c5456480 --- /dev/null +++ b/wrapper/python/test/test_random.py @@ -0,0 +1,38 @@ +# test_random.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +import unittest +from wolfcrypt.random import * + + +class TestRandom(unittest.TestCase): + + + def setUp(self): + self.random = Random() + + + def test_byte(self): + assert len(self.random.byte()) == 1 + + + def test_bytes(self): + assert len(self.random.bytes(1)) == 1 + assert len(self.random.bytes(10)) == 10 + assert len(self.random.bytes(100)) == 100 diff --git a/wrapper/python/tox.ini b/wrapper/python/tox.ini new file mode 100644 index 000000000..98ec7f995 --- /dev/null +++ b/wrapper/python/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist=py27,py34,py35 +skip_missing_interpreters=true + +[testenv] +deps=-rrequirements-testing.txt +commands=py.test test/ diff --git a/wrapper/python/wolfcrypt/__about__.py b/wrapper/python/wolfcrypt/__about__.py new file mode 100644 index 000000000..b5c05386a --- /dev/null +++ b/wrapper/python/wolfcrypt/__about__.py @@ -0,0 +1,45 @@ +# __about__.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +metadata = dict( + __name__ = "wolfcrypt", + __version__ = "0.1.8", + __license__ = "GPLv2 or Commercial License", + __author__ = "wolfSSL Inc.", + __author_email__ = "info@wolfssl.com", + __url__ = "https://wolfssl.github.io/wolfcrypt-py", + __description__ = \ + u"A Python library that encapsulates wolfSSL's wolfCrypt API.", + __keywords__ = "security, cryptography, ssl, embedded, embedded ssl", + __classifiers__ = [ + u"License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + u"License :: Other/Proprietary License", + u"Operating System :: OS Independent", + u"Programming Language :: Python :: 2.7", + u"Programming Language :: Python :: 3.5", + u"Topic :: Security", + u"Topic :: Security :: Cryptography", + u"Topic :: Software Development" + ] +) + +globals().update(metadata) + +__all__ = list(metadata.keys()) diff --git a/wrapper/python/wolfcrypt/__init__.py b/wrapper/python/wolfcrypt/__init__.py new file mode 100644 index 000000000..562fb1275 --- /dev/null +++ b/wrapper/python/wolfcrypt/__init__.py @@ -0,0 +1,21 @@ +# __init__.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from .__about__ import * diff --git a/wrapper/python/wolfcrypt/build_ffi.py b/wrapper/python/wolfcrypt/build_ffi.py new file mode 100644 index 000000000..720e96cfc --- /dev/null +++ b/wrapper/python/wolfcrypt/build_ffi.py @@ -0,0 +1,133 @@ +# build_ffi.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +import os + +from cffi import FFI + +ffi = FFI() + +ffi.set_source("wolfcrypt._ffi", + """ + #include + + #include + #include + #include + + #include + + #include + #include + #include + + #include + + #include + """, + include_dirs=["/usr/local/include"], + library_dirs=["/usr/local/lib"], + libraries=["wolfssl"], +) + +ffi.cdef( +""" + + typedef unsigned char byte; + typedef unsigned int word32; + + typedef struct { ...; } Sha; + + int wc_InitSha(Sha*); + int wc_ShaUpdate(Sha*, const byte*, word32); + int wc_ShaFinal(Sha*, byte*); + + + typedef struct { ...; } Sha256; + + int wc_InitSha256(Sha256*); + int wc_Sha256Update(Sha256*, const byte*, word32); + int wc_Sha256Final(Sha256*, byte*); + + + typedef struct { ...; } Sha384; + + int wc_InitSha384(Sha384*); + int wc_Sha384Update(Sha384*, const byte*, word32); + int wc_Sha384Final(Sha384*, byte*); + + + typedef struct { ...; } Sha512; + + int wc_InitSha512(Sha512*); + int wc_Sha512Update(Sha512*, const byte*, word32); + int wc_Sha512Final(Sha512*, byte*); + + + typedef struct { ...; } Hmac; + + int wc_HmacSetKey(Hmac*, int, const byte*, word32); + int wc_HmacUpdate(Hmac*, const byte*, word32); + int wc_HmacFinal(Hmac*, byte*); + + + typedef struct { ...; } Aes; + + int wc_AesSetKey(Aes*, const byte*, word32, const byte*, int); + int wc_AesCbcEncrypt(Aes*, byte*, const byte*, word32); + int wc_AesCbcDecrypt(Aes*, byte*, const byte*, word32); + + + typedef struct { ...; } Des3; + + int wc_Des3_SetKey(Des3*, const byte*, const byte*, int); + int wc_Des3_CbcEncrypt(Des3*, byte*, const byte*, word32); + int wc_Des3_CbcDecrypt(Des3*, byte*, const byte*, word32); + + + typedef struct { ...; } WC_RNG; + + int wc_InitRng(WC_RNG*); + int wc_RNG_GenerateBlock(WC_RNG*, byte*, word32); + int wc_RNG_GenerateByte(WC_RNG*, byte*); + int wc_FreeRng(WC_RNG*); + + + typedef struct {...; } RsaKey; + + int wc_InitRsaKey(RsaKey* key, void*); + int wc_FreeRsaKey(RsaKey* key); + + int wc_RsaPrivateKeyDecode(const byte*, word32*, RsaKey*, word32); + int wc_RsaPublicKeyDecode(const byte*, word32*, RsaKey*, word32); + int wc_RsaEncryptSize(RsaKey*); + + int wc_RsaPrivateDecrypt(const byte*, word32, byte*, word32, + RsaKey* key); + int wc_RsaPublicEncrypt(const byte*, word32, byte*, word32, + RsaKey*, WC_RNG*); + + int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*); + int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*); + +""" +) + +if __name__ == "__main__": + ffi.compile(verbose=1) diff --git a/wrapper/python/wolfcrypt/ciphers.py b/wrapper/python/wolfcrypt/ciphers.py new file mode 100644 index 000000000..4084e6bf7 --- /dev/null +++ b/wrapper/python/wolfcrypt/ciphers.py @@ -0,0 +1,349 @@ +# ciphers.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +from wolfcrypt._ffi import ffi as _ffi +from wolfcrypt._ffi import lib as _lib +from wolfcrypt.utils import t2b +from wolfcrypt.random import Random + +from wolfcrypt.exceptions import * + + +# key direction flags +_ENCRYPTION = 0 +_DECRYPTION = 1 + + +# feedback modes +MODE_ECB = 1 # Electronic Code Book +MODE_CBC = 2 # Cipher Block Chaining +MODE_CFB = 3 # Cipher Feedback +MODE_OFB = 5 # Output Feedback +MODE_CTR = 6 # Counter + +_FEEDBACK_MODES = [MODE_ECB, MODE_CBC, MODE_CFB, MODE_OFB, MODE_CTR] + + +class _Cipher(object): + """ + A **PEP 272: Block Encryption Algorithms** compliant + **Symmetric Key Cipher**. + """ + def __init__(self, key, mode, IV=None): + if mode not in _FEEDBACK_MODES: + raise ValueError("this mode is not supported") + + if mode == MODE_CBC: + if IV is None: + raise ValueError("this mode requires an 'IV' string") + else: + raise ValueError("this mode is not supported by this cipher") + + if self.key_size: + if self.key_size != len(key): + raise ValueError("key must be %d in length" % self.key_size) + elif self._key_sizes: + if len(key) not in self._key_sizes: + raise ValueError("key must be %s in length" % self._key_sizes) + else: + if not len(key): + raise ValueError("key must not be 0 in length") + + if IV is not None and len(IV) != self.block_size: + raise ValueError("IV must be %d in length" % self.block_size) + + self._native_object = _ffi.new(self._native_type) + self._enc = None + self._dec = None + self._key = t2b(key) + + if IV: + self._IV = t2b(IV) + else: + self._IV = t2b("\0" * self.block_size) + + + @classmethod + def new(cls, key, mode, IV=None, **kwargs): + """ + Returns a ciphering object, using the secret key contained in + the string **key**, and using the feedback mode **mode**, which + must be one of MODE_* defined in this module. + + If **mode** is MODE_CBC or MODE_CFB, **IV** must be provided and + must be a string of the same length as the block size. Not + providing a value of **IV** will result in a ValueError exception + being raised. + """ + return cls(key, mode, IV) + + + def encrypt(self, string): + """ + Encrypts a non-empty string, using the key-dependent data in + the object, and with the appropriate feedback mode. The + string's length must be an exact multiple of the algorithm's + block size or, in CFB mode, of the segment size. Returns a + string containing the ciphertext. + """ + string = t2b(string) + + if not string or len(string) % self.block_size: + raise ValueError( + "string must be a multiple of %d in length" % self.block_size) + + if self._enc is None: + self._enc = _ffi.new(self._native_type) + ret = self._set_key(_ENCRYPTION) + if ret < 0: + raise WolfCryptError("Invalid key error (%d)" % ret) + + result = t2b("\0" * len(string)) + ret = self._encrypt(result, string) + if ret < 0: + raise WolfCryptError("Encryption error (%d)" % ret) + + return result + + + def decrypt(self, string): + """ + Decrypts **string**, using the key-dependent data in the + object and with the appropriate feedback mode. The string's + length must be an exact multiple of the algorithm's block + size or, in CFB mode, of the segment size. Returns a string + containing the plaintext. + """ + string = t2b(string) + + if not string or len(string) % self.block_size: + raise ValueError( + "string must be a multiple of %d in length" % self.block_size) + + if self._dec is None: + self._dec = _ffi.new(self._native_type) + ret = self._set_key(_DECRYPTION) + if ret < 0: + raise WolfCryptError("Invalid key error (%d)" % ret) + + result = t2b("\0" * len(string)) + ret = self._decrypt(result, string) + if ret < 0: + raise WolfCryptError("Decryption error (%d)" % ret) + + return result + + +class Aes(_Cipher): + """ + The **Advanced Encryption Standard** (AES), a.k.a. Rijndael, is + a symmetric-key cipher standardized by **NIST**. + """ + block_size = 16 + key_size = None # 16, 24, 32 + _key_sizes = [16, 24, 32] + _native_type = "Aes *" + + + def _set_key(self, direction): + if direction == _ENCRYPTION: + return _lib.wc_AesSetKey( + self._enc, self._key, len(self._key), self._IV, _ENCRYPTION) + else: + return _lib.wc_AesSetKey( + self._dec, self._key, len(self._key), self._IV, _DECRYPTION) + + + def _encrypt(self, destination, source): + return _lib.wc_AesCbcEncrypt(self._enc, destination, source,len(source)) + + + def _decrypt(self, destination, source): + return _lib.wc_AesCbcDecrypt(self._dec, destination, source,len(source)) + + +class Des3(_Cipher): + """ + **Triple DES** (3DES) is the common name for the **Triple Data + Encryption Algorithm** (TDEA or Triple DEA) symmetric-key block + cipher, which applies the **Data Encryption Standard** (DES) + cipher algorithm three times to each data block. + """ + block_size = 8 + key_size = 24 + _native_type = "Des3 *" + + + def _set_key(self, direction): + if direction == _ENCRYPTION: + return _lib.wc_Des3_SetKey(self._enc,self._key,self._IV,_ENCRYPTION) + else: + return _lib.wc_Des3_SetKey(self._dec,self._key,self._IV,_DECRYPTION) + + + def _encrypt(self, destination, source): + return _lib.wc_Des3_CbcEncrypt(self._enc,destination,source,len(source)) + + + def _decrypt(self, destination, source): + return _lib.wc_Des3_CbcDecrypt(self._dec,destination,source,len(source)) + + +class _Rsa(object): + RSA_MIN_PAD_SIZE = 11 + + def __init__(self): + self.native_object = _ffi.new("RsaKey *") + ret = _lib.wc_InitRsaKey(self.native_object, _ffi.NULL) + if ret < 0: + raise WolfCryptError("Invalid key error (%d)" % ret) + + self._random = Random() + + + def __del__(self): + if self.native_object: + _lib.wc_FreeRsaKey(self.native_object) + + +class RsaPublic(_Rsa): + def __init__(self, key): + key = t2b(key) + + _Rsa.__init__(self) + + idx = _ffi.new("word32*") + idx[0] = 0 + + ret = _lib.wc_RsaPublicKeyDecode(key, idx, self.native_object, len(key)) + if ret < 0: + raise WolfCryptError("Invalid key error (%d)" % ret) + + self.output_size = _lib.wc_RsaEncryptSize(self.native_object) + if self.output_size <= 0: + raise WolfCryptError("Invalid key error (%d)" % self.output_size) + + + def encrypt(self, plaintext): + """ + Encrypts **plaintext**, using the public key data in the + object. The plaintext's length must not be greater than: + + **self.output_size - self.RSA_MIN_PAD_SIZE** + + Returns a string containing the ciphertext. + """ + + plaintext = t2b(plaintext) + ciphertext = t2b("\0" * self.output_size) + + ret = _lib.wc_RsaPublicEncrypt(plaintext, len(plaintext), + ciphertext, len(ciphertext), + self.native_object, + self._random.native_object) + + if ret != self.output_size: + raise WolfCryptError("Encryption error (%d)" % ret) + + return ciphertext + + + def verify(self, signature): + """ + Verifies **signature**, using the public key data in the + object. The signature's length must be equal to: + + **self.output_size** + + Returns a string containing the plaintext. + """ + signature = t2b(signature) + plaintext = t2b("\0" * self.output_size) + + ret = _lib.wc_RsaSSL_Verify(signature, len(signature), + plaintext, len(plaintext), + self.native_object) + + if ret < 0: + raise WolfCryptError("Verify error (%d)" % ret) + + return plaintext[:ret] + + +class RsaPrivate(RsaPublic): + def __init__(self, key): + key = t2b(key) + + _Rsa.__init__(self) + + idx = _ffi.new("word32*") + idx[0] = 0 + + ret = _lib.wc_RsaPrivateKeyDecode(key, idx, self.native_object,len(key)) + if ret < 0: + raise WolfCryptError("Invalid key error (%d)" % ret) + + self.output_size = _lib.wc_RsaEncryptSize(self.native_object) + if self.output_size <= 0: + raise WolfCryptError("Invalid key error (%d)" % self.output_size) + + + def decrypt(self, ciphertext): + """ + Decrypts **ciphertext**, using the private key data in the + object. The ciphertext's length must be equal to: + + **self.output_size** + + Returns a string containing the plaintext. + """ + ciphertext = t2b(ciphertext) + plaintext = t2b("\0" * self.output_size) + + ret = _lib.wc_RsaPrivateDecrypt(ciphertext, len(ciphertext), + plaintext, len(plaintext), + self.native_object) + + if ret < 0: + raise WolfCryptError("Decryption error (%d)" % ret) + + return plaintext[:ret] + + + def sign(self, plaintext): + """ + Signs **plaintext**, using the private key data in the object. + The plaintext's length must not be greater than: + + **self.output_size - self.RSA_MIN_PAD_SIZE** + + Returns a string containing the signature. + """ + plaintext = t2b(plaintext) + signature = t2b("\0" * self.output_size) + + ret = _lib.wc_RsaSSL_Sign(plaintext, len(plaintext), + signature, len(signature), + self.native_object, + self._random.native_object) + + if ret != self.output_size: + raise WolfCryptError("Signature error (%d)" % ret) + + return signature diff --git a/wrapper/python/wolfcrypt/exceptions.py b/wrapper/python/wolfcrypt/exceptions.py new file mode 100644 index 000000000..838a2b2b0 --- /dev/null +++ b/wrapper/python/wolfcrypt/exceptions.py @@ -0,0 +1,23 @@ +# exceptions.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +class WolfCryptError(Exception): + pass diff --git a/wrapper/python/wolfcrypt/hashes.py b/wrapper/python/wolfcrypt/hashes.py new file mode 100644 index 000000000..816f205cc --- /dev/null +++ b/wrapper/python/wolfcrypt/hashes.py @@ -0,0 +1,302 @@ +# hashes.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +from wolfcrypt._ffi import ffi as _ffi +from wolfcrypt._ffi import lib as _lib +from wolfcrypt.utils import t2b, b2h + +from wolfcrypt.exceptions import * + +class _Hash(object): + """ + A **PEP 247: Cryptographic Hash Functions** compliant + **Hash Function Interface**. + """ + def __init__(self, string=None): + self._native_object = _ffi.new(self._native_type) + ret = self._init() + if ret < 0: + raise WolfCryptError("Hash init error (%d)" % ret) + + if (string): + self.update(string) + + + @classmethod + def new(cls, string=None): + """ + Creates a new hashing object and returns it. The optional + **string** parameter, if supplied, will be immediately + hashed into the object's starting state, as if + obj.update(string) was called. + """ + return cls(string) + + + def copy(self): + """ + Returns a separate copy of this hashing object. An update + to this copy won't affect the original object. + """ + copy = self.new("") + + _ffi.memmove(copy._native_object, + self._native_object, + self._native_size) + + return copy + + + def update(self, string): + """ + Hashes **string** into the current state of the hashing + object. update() can be called any number of times during + a hashing object's lifetime. + """ + string = t2b(string) + + ret = self._update(string) + if ret < 0: + raise WolfCryptError("Hash update error (%d)" % ret) + + + def digest(self): + """ + Returns the hash value of this hashing object as a string + containing 8-bit data. The object is not altered in any + way by this function; you can continue updating the object + after calling this function. + """ + result = t2b("\0" * self.digest_size) + + if self._native_object: + obj = _ffi.new(self._native_type) + + _ffi.memmove(obj, self._native_object, self._native_size) + + ret = self._final(obj, result) + if ret < 0: + raise WolfCryptError("Hash finalize error (%d)" % ret) + + return result + + + def hexdigest(self): + """ + Returns the hash value of this hashing object as a string + containing hexadecimal digits. Lowercase letters are used + for the digits 'a' through 'f'. Like the .digest() method, + this method doesn't alter the object. + """ + return b2h(self.digest()) + + +class Sha(_Hash): + """ + **SHA-1** is a cryptographic hash function standardized by **NIST**. + + It produces an [ **160-bit | 20 bytes** ] message digest. + """ + digest_size = 20 + _native_type = "Sha *" + _native_size = _ffi.sizeof("Sha") + + + def _init(self): + return _lib.wc_InitSha(self._native_object) + + + def _update(self, data): + return _lib.wc_ShaUpdate(self._native_object, data, len(data)) + + + def _final(self, obj, ret): + return _lib.wc_ShaFinal(obj, ret) + + +class Sha256(_Hash): + """ + **SHA-256** is a cryptographic hash function from the + **SHA-2 family** and is standardized by **NIST**. + + It produces a [ **256-bit | 32 bytes** ] message digest. + """ + digest_size = 32 + _native_type = "Sha256 *" + _native_size = _ffi.sizeof("Sha256") + + + def _init(self): + return _lib.wc_InitSha256(self._native_object) + + + def _update(self, data): + return _lib.wc_Sha256Update(self._native_object, data, len(data)) + + + def _final(self, obj, ret): + return _lib.wc_Sha256Final(obj, ret) + + +class Sha384(_Hash): + """ + **SHA-384** is a cryptographic hash function from the + **SHA-2 family** and is standardized by **NIST**. + + It produces a [ **384-bit | 48 bytes** ] message digest. + """ + digest_size = 48 + _native_type = "Sha384 *" + _native_size = _ffi.sizeof("Sha384") + + + def _init(self): + return _lib.wc_InitSha384(self._native_object) + + + def _update(self, data): + return _lib.wc_Sha384Update(self._native_object, data, len(data)) + + + def _final(self, obj, ret): + return _lib.wc_Sha384Final(obj, ret) + + +class Sha512(_Hash): + """ + **SHA-512** is a cryptographic hash function from the + **SHA-2 family** and is standardized by **NIST**. + + It produces a [ **512-bit | 64 bytes** ] message digest. + """ + digest_size = 64 + _native_type = "Sha512 *" + _native_size = _ffi.sizeof("Sha512") + + + def _init(self): + return _lib.wc_InitSha512(self._native_object) + + + def _update(self, data): + return _lib.wc_Sha512Update(self._native_object, data, len(data)) + + + def _final(self, obj, ret): + return _lib.wc_Sha512Final(obj, ret) + + +# Hmac types + +_TYPE_SHA = 1 +_TYPE_SHA256 = 2 +_TYPE_SHA384 = 5 +_TYPE_SHA512 = 4 +_HMAC_TYPES = [_TYPE_SHA, _TYPE_SHA256, _TYPE_SHA384, _TYPE_SHA512] + + +class _Hmac(_Hash): + """ + A **PEP 247: Cryptographic Hash Functions** compliant + **Keyed Hash Function Interface**. + """ + digest_size = None + _native_type = "Hmac *" + _native_size = _ffi.sizeof("Hmac") + + + def __init__(self, key, string=None): + key = t2b(key) + + self._native_object = _ffi.new(self._native_type) + ret = self._init(self._type, key) + if ret < 0: + raise WolfCryptError("Hmac init error (%d)" % ret) + + if (string): + self.update(string) + + + + @classmethod + def new(cls, key, string=None): + """ + Creates a new hashing object and returns it. **key** is + a required parameter containing a string giving the key + to use. The optional **string** parameter, if supplied, + will be immediately hashed into the object's starting + state, as if obj.update(string) was called. + """ + return cls(key, string) + + + def _init(self, type, key): + return _lib.wc_HmacSetKey(self._native_object, type, key, len(key)) + + + def _update(self, data): + return _lib.wc_HmacUpdate(self._native_object, data, len(data)) + + + def _final(self, obj, ret): + return _lib.wc_HmacFinal(obj, ret) + + +class HmacSha(_Hmac): + """ + A HMAC function using **SHA-1** as it's cryptographic + hash function. + + It produces a [ **512-bit | 64 bytes** ] message digest. + """ + _type = _TYPE_SHA + digest_size = Sha.digest_size + + +class HmacSha256(_Hmac): + """ + A HMAC function using **SHA-256** as it's cryptographic + hash function. + + It produces a [ **512-bit | 64 bytes** ] message digest. + """ + _type = _TYPE_SHA256 + digest_size = Sha256.digest_size + + +class HmacSha384(_Hmac): + """ + A HMAC function using **SHA-384** as it's cryptographic + hash function. + + It produces a [ **512-bit | 64 bytes** ] message digest. + """ + _type = _TYPE_SHA384 + digest_size = Sha384.digest_size + + +class HmacSha512(_Hmac): + """ + A HMAC function using **SHA-512** as it's cryptographic + hash function. + + It produces a [ **512-bit | 64 bytes** ] message digest. + """ + _type = _TYPE_SHA512 + digest_size = Sha512.digest_size diff --git a/wrapper/python/wolfcrypt/random.py b/wrapper/python/wolfcrypt/random.py new file mode 100644 index 000000000..f819f51d8 --- /dev/null +++ b/wrapper/python/wolfcrypt/random.py @@ -0,0 +1,68 @@ +# random.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +from wolfcrypt._ffi import ffi as _ffi +from wolfcrypt._ffi import lib as _lib +from wolfcrypt.utils import t2b + +from wolfcrypt.exceptions import * + + +class Random(object): + """ + A Cryptographically Secure Pseudo Random Number Generator - CSPRNG + """ + def __init__(self): + self.native_object = _ffi.new("WC_RNG *") + + ret = _lib.wc_InitRng(self.native_object) + if ret < 0: + self.native_object = None + raise WolfCryptError("RNG init error (%d)" % ret) + + + def __del__(self): + if self.native_object: + _lib.wc_FreeRng(self.native_object) + + + def byte(self): + """ + Generate and return a random byte. + """ + result = t2b("\0") + + ret = _lib.wc_RNG_GenerateByte(self.native_object, result) + if ret < 0: + raise WolfCryptError("RNG generate byte error (%d)" % ret) + + return result + + + def bytes(self, length): + """ + Generate and return a random sequence of length bytes. + """ + result = t2b("\0" * length) + + ret = _lib.wc_RNG_GenerateBlock(self.native_object, result, length) + if ret < 0: + raise WolfCryptError("RNG generate block error (%d)" % ret) + + return result diff --git a/wrapper/python/wolfcrypt/utils.py b/wrapper/python/wolfcrypt/utils.py new file mode 100644 index 000000000..34646ff8a --- /dev/null +++ b/wrapper/python/wolfcrypt/utils.py @@ -0,0 +1,38 @@ +# utils.py +# +# Copyright (C) 2006-2016 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +import sys +from binascii import hexlify as b2h, unhexlify as h2b + + +if sys.version_info[0] == 3: + _text_type = str + _binary_type = bytes +else: + _text_type = unicode + _binary_type = str + + +def t2b(s): + """ + Converts text to bynary. + """ + if isinstance(s, _binary_type): + return s + return _text_type(s).encode("utf-8")