diff --git a/certs/external/ca-digicert-ev.pem b/certs/external/ca-digicert-ev.pem new file mode 100644 index 000000000..9e6810ab7 --- /dev/null +++ b/certs/external/ca-digicert-ev.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- diff --git a/wrapper/include.am b/wrapper/include.am index eb6d8f7fc..dbf0e7fc8 100644 --- a/wrapper/include.am +++ b/wrapper/include.am @@ -3,6 +3,7 @@ # All paths should be given relative to the root include wrapper/python/wolfcrypt/include.am +include wrapper/python/wolfssl/include.am # wolfSSL CSharp wrapper files EXTRA_DIST+= wrapper/CSharp/wolfSSL-DTLS-PSK-Server/App.config diff --git a/wrapper/python/wolfcrypt/.gitignore b/wrapper/python/wolfcrypt/.gitignore index 421703396..5e6f6f9bd 100644 --- a/wrapper/python/wolfcrypt/.gitignore +++ b/wrapper/python/wolfcrypt/.gitignore @@ -13,3 +13,6 @@ dist/ .tox/ # Sphinx documentation docs/_build/ + +# Virtual env +.env diff --git a/wrapper/python/wolfcrypt/wolfcrypt/utils.py b/wrapper/python/wolfcrypt/wolfcrypt/utils.py index 34646ff8a..9f7369cb2 100644 --- a/wrapper/python/wolfcrypt/wolfcrypt/utils.py +++ b/wrapper/python/wolfcrypt/wolfcrypt/utils.py @@ -17,22 +17,20 @@ # 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 + +# pylint: disable=unused-import, undefined-variable + import sys from binascii import hexlify as b2h, unhexlify as h2b +_PY3 = sys.version_info[0] == 3 +_TEXT_TYPE = str if _PY3 else unicode +_BINARY_TYPE = bytes if _PY3 else str -if sys.version_info[0] == 3: - _text_type = str - _binary_type = bytes -else: - _text_type = unicode - _binary_type = str - - -def t2b(s): +def t2b(string): """ Converts text to bynary. """ - if isinstance(s, _binary_type): - return s - return _text_type(s).encode("utf-8") + if isinstance(string, _BINARY_TYPE): + return string + return _TEXT_TYPE(string).encode("utf-8") diff --git a/wrapper/python/wolfssl/.centos-provisioner.sh b/wrapper/python/wolfssl/.centos-provisioner.sh new file mode 100644 index 000000000..fc0ec19a7 --- /dev/null +++ b/wrapper/python/wolfssl/.centos-provisioner.sh @@ -0,0 +1,38 @@ +[ "$(whoami)" != "root" ] && echo "Sorry, you are not root." && exit 1 + +rpm -ivh http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + +yum install -y \ + git autoconf libtool libffi-devel python-devel python34-devel python2-pip + +pip install -U pip setuptools + +git clone --depth 1 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 + +pushd /vagrant + +pip install -r requirements-testing.txt + +make clean + +tox -epy27,py34 -- -v + +popd + +# pip install wolfssl +# [ $? -ne 0 ] && echo "\n\nCouldn't install wolfssl.\n\n" && exit 1 diff --git a/wrapper/python/wolfssl/.gitignore b/wrapper/python/wolfssl/.gitignore new file mode 100644 index 000000000..d3d507b23 --- /dev/null +++ b/wrapper/python/wolfssl/.gitignore @@ -0,0 +1,21 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution +build/ +dist/ +.eggs/ +*.egg-info/ + +# Unit test +.tox/ +htmlcov/ +.coverage + +# Sphinx documentation +docs/_build/ + +# Certificates +certs/ \ No newline at end of file diff --git a/wrapper/python/wolfssl/.ubuntu-provisioner.sh b/wrapper/python/wolfssl/.ubuntu-provisioner.sh new file mode 100644 index 000000000..30ee7f6fc --- /dev/null +++ b/wrapper/python/wolfssl/.ubuntu-provisioner.sh @@ -0,0 +1,36 @@ +[ "$(whoami)" != "root" ] && echo "Sorry, you are not root." && exit 1 + +apt-get update + +apt-get install -y \ + git autoconf libtool python-dev python3-dev python-pip libffi-dev + +pip install -U pip setuptools + +git clone --depth 1 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 + +pushd /vagrant + +pip install -r requirements-testing.txt + +make clean + +tox -epy27,py34 -- -v + +popd + +# pip install wolfssl +# [ $? -ne 0 ] && echo -e "\n\nCouldn't install wolfssl.\n\n" && exit 1 diff --git a/wrapper/python/wolfssl/LICENSING.rst b/wrapper/python/wolfssl/LICENSING.rst new file mode 100644 index 000000000..2fc46fc9c --- /dev/null +++ b/wrapper/python/wolfssl/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/wolfssl/MANIFEST.in b/wrapper/python/wolfssl/MANIFEST.in new file mode 100644 index 000000000..31b8bcfa9 --- /dev/null +++ b/wrapper/python/wolfssl/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSING.rst +recursive-include certs *.pem diff --git a/wrapper/python/wolfssl/Makefile b/wrapper/python/wolfssl/Makefile new file mode 100644 index 000000000..fd18e126d --- /dev/null +++ b/wrapper/python/wolfssl/Makefile @@ -0,0 +1,58 @@ +# Makefile +# +# Copyright (C) 2006-2017 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 + +.PHONY : all clean clean-test clean-build clean-pyc install test check upload + +# builds the module +all : + python ./setup.py build + +#builds and installs the module +install : all + python ./setup.py install + +## removes all build, test, coverage and Python artifacts +clean : clean-test clean-build clean-pyc + +## removes test and coverage artifacts +clean-test : + rm -rf .coverage .tox/ htmlcov/ + +## removes build artifacts +clean-build : + rm -rf build/ dist/ .eggs/ + find . -name '*.egg-info' -exec rm -rf {} + + find . -name '*.egg' -exec rm -v {} + + +## removes Python file artifacts +clean-pyc : + find src test -name '__pycache__' -exec rm -rf {} + + find src test -name '*.pyc' -exec rm -f {} + + find src test -name '*.pyo' -exec rm -f {} + + +# runs unit tests +check : test + +test : clean-pyc + tox + +# publishes module at pypi +upload : test + python ./setup.py sdist upload diff --git a/wrapper/python/wolfssl/README.rst b/wrapper/python/wolfssl/README.rst new file mode 100644 index 000000000..1a8e7250a --- /dev/null +++ b/wrapper/python/wolfssl/README.rst @@ -0,0 +1,96 @@ +Welcome +======= + +``wolfssl Python`` is a Python module that encapsulates ``wolfssl C``, a `lightweight C-language-based SSL/TLS library `_ targeted for embedded, RTOS, or +resource-constrained environments primarily because of its small size, speed, +and portability. + +Installation +============ + +In order to use ``wolfssl Python``, you'll also need to install ``wolfssl C``. + +Mac OSX +------- + +Installing from ``homebrew`` and ``pip`` package managers: + +.. code-block:: shell + + # wolfssl C installation + brew install wolfssl + + # wolfssl Python installation + sudo -H pip install wolfssl + +Installing from ``source code``: + +.. code-block:: shell + + # wolfssl C installation + git clone https://github.com/wolfssl/wolfssl.git + cd wolfssl/ + ./autogen.sh + ./configure --enable-sha512 + make + sudo make install + + # wolfssl Python installation + cd wrapper/python/wolfssl + sudo make install + + +Linux +----- + +.. code-block:: shell + + # dependencies installation + sudo apt-get update + sudo apt-get install -y git autoconf libtool + sudo apt-get install -y python-dev python3-dev python-pip libffi-dev + + # wolfssl C installation + git clone https://github.com/wolfssl/wolfssl.git + cd wolfssl/ + ./autogen.sh + ./configure --enable-sha512 + make + sudo make install + + sudo ldconfig + + # wolfssl Python installation + sudo -H pip install wolfssl + + +Testing +======= + +To run the tox tests in the source code, you'll need ``tox`` and a few other +requirements. The source code relies at **WOLFSSL_DIR/wrapper/python/wolfssl** +where **WOLFSSL_DIR** is the path of ``wolfssl C``'s source code. + +1. Make sure that the testing requirements are installed: + +.. code-block:: shell + + sudo -H pip install -r requirements-testing.txt + + +2. Run ``make check``: + +.. code-block:: console + + $ make check + ... + _________________________________ summary _________________________________ + py27: commands succeeded + SKIPPED: py34: InterpreterNotFound: python3.4 + py35: commands succeeded + py36: 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/wolfssl/Vagrantfile b/wrapper/python/wolfssl/Vagrantfile new file mode 100644 index 000000000..cd83995c6 --- /dev/null +++ b/wrapper/python/wolfssl/Vagrantfile @@ -0,0 +1,17 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + + config.vm.define "default" do |default| + default.vm.box = "ubuntu/trusty64" + default.vm.provision "shell", path: ".ubuntu-provisioner.sh" + end + + config.vm.define "centos", autostart: false do |centos| + centos.vm.box = "moisesguimaraes/centos72-64" + centos.vm.provision "shell", path: ".centos-provisioner.sh" + end + +end \ No newline at end of file diff --git a/wrapper/python/wolfssl/docs/Makefile b/wrapper/python/wolfssl/docs/Makefile new file mode 100644 index 000000000..655a78953 --- /dev/null +++ b/wrapper/python/wolfssl/docs/Makefile @@ -0,0 +1,59 @@ +# Makefile +# +# Copyright (C) 2006-2017 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 + +.PHONY : all clean html pdf man + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = a4 +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +all: + @echo "Please use \`make ' where is one of" + @echo " html to make a single large HTML file" + @echo " pdf to make LaTeX files and run them through pdflatex" + @echo " man to make manual pages" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pdf: + $(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." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." diff --git a/wrapper/python/wolfssl/docs/api.rst b/wrapper/python/wolfssl/docs/api.rst new file mode 100644 index 000000000..e6d2538ae --- /dev/null +++ b/wrapper/python/wolfssl/docs/api.rst @@ -0,0 +1,21 @@ +API Documentation +================= + +.. module:: wolfssl + +wrap_socket +----------- + +.. autofunction:: wrap_socket + +SSL/TLS Context +--------------- + +.. autoclass:: SSLContext + :members: + +SSL/TLS Socket +-------------- + +.. autoclass:: SSLSocket + :members: diff --git a/wrapper/python/wolfssl/docs/conf.py b/wrapper/python/wolfssl/docs/conf.py new file mode 100644 index 000000000..612116f38 --- /dev/null +++ b/wrapper/python/wolfssl/docs/conf.py @@ -0,0 +1,300 @@ +# -*- 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. + +# pylint: disable=invalid-name, redefined-builtin, exec-used + +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'wolfssl Python' +copyright = u'2017, 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, "src") +about = {} +with open(os.path.join(base_dir, "wolfssl", "__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 = 'wolfssl-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, 'wolfssl.tex', u'wolfssl 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, 'wolfssl', u'wolfssl 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, 'wolfssl', u'wolfssl Python Documentation', + author, 'wolfssl', '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/wolfssl/docs/examples.rst b/wrapper/python/wolfssl/docs/examples.rst new file mode 100644 index 000000000..5d2279f62 --- /dev/null +++ b/wrapper/python/wolfssl/docs/examples.rst @@ -0,0 +1,95 @@ +Client and Server Examples +========================== + +SSL/TLS Client Example +---------------------- + +.. code-block:: python + + import socket + import wolfssl + + CA_DATA = \ + """ + -----BEGIN CERTIFICATE----- + MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs + MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 + d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j + ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL + MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 + LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug + RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm + +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW + PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM + xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB + Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 + hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg + EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF + MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA + FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec + nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z + eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF + hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 + Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe + vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep + +OkuE6N36B9K + -----END CERTIFICATE----- + """ + + bind_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + + context = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2) + + context.verify_mode = wolfssl.CERT_REQUIRED + context.load_verify_locations(cadata=CA_DATA) + + secure_socket = context.wrap_socket(bind_socket) + + secure_socket.connect(("www.python.org", 443)) + + secure_socket.write(b"GET / HTTP/1.1\n\n") + + print(secure_socket.read()) + + secure_socket.close() + + +SSL/TLS Server Example +---------------------- + +.. code-block:: python + + import socket + import wolfssl + + bind_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + + bind_socket.bind(("", 4433)) + bind_socket.listen(5) + + context = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2, server_side=True) + + context.load_cert_chain("certs/server-cert.pem", "certs/server-key.pem") + + while True: + try: + secure_socket = None + + new_socket, from_addr = bind_socket.accept() + + secure_socket = context.wrap_socket(new_socket) + + print("Connection received from", from_addr) + + print("\n", secure_socket.read(), "\n") + secure_socket.write(b"I hear you fa shizzle!") + + except KeyboardInterrupt: + print() + break + + finally: + if secure_socket: + secure_socket.close() + + bind_socket.close() diff --git a/wrapper/python/wolfssl/docs/index.rst b/wrapper/python/wolfssl/docs/index.rst new file mode 100644 index 000000000..344919f7f --- /dev/null +++ b/wrapper/python/wolfssl/docs/index.rst @@ -0,0 +1,8 @@ +.. toctree:: + :maxdepth: 2 + + installation + usage + api + examples + licensing diff --git a/wrapper/python/wolfssl/docs/installation.rst b/wrapper/python/wolfssl/docs/installation.rst new file mode 100644 index 000000000..72a335581 --- /dev/null +++ b/wrapper/python/wolfssl/docs/installation.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/wrapper/python/wolfssl/docs/licensing.rst b/wrapper/python/wolfssl/docs/licensing.rst new file mode 100644 index 000000000..f5cc633bb --- /dev/null +++ b/wrapper/python/wolfssl/docs/licensing.rst @@ -0,0 +1 @@ +.. include:: ../LICENSING.rst diff --git a/wrapper/python/wolfssl/docs/requirements.txt b/wrapper/python/wolfssl/docs/requirements.txt new file mode 100644 index 000000000..ab3f3dd41 --- /dev/null +++ b/wrapper/python/wolfssl/docs/requirements.txt @@ -0,0 +1,2 @@ +Sphinx +sphinx_rtd_theme diff --git a/wrapper/python/wolfssl/docs/usage.rst b/wrapper/python/wolfssl/docs/usage.rst new file mode 100644 index 000000000..47c2f0981 --- /dev/null +++ b/wrapper/python/wolfssl/docs/usage.rst @@ -0,0 +1,87 @@ +Basic Usage +=========== + +The SSL/TLS protocol works securing an underlying TCP connection, this module +adds the secure layer around the Python standard library +`socket `_ module. + +There are three different paths to secure a socket in this module: + +* Using the top level function wolfssl.wrap_socket(); +* Using the method wrap_socket() from a SSLContext instance; +* Creating an SSLSocket object from the scratch. + +Note 1: + It is possible to use the same SSLContext for multiple SSLSockets to save + time and resources. + +Note 2: + Each path provides its own options for fine-tuning the securint parameters. + Check them out in the API documentation. + + +Using the top level function wolfssl.wrap_socket() +-------------------------------------------------- + +.. code-block:: python + + >>> import socket + >>> import wolfssl + >>> + >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + >>> + >>> secure_socket = wolfssl.wrap_socket(sock) + >>> + >>> secure_socket.connect(("www.python.org", 443)) + >>> + >>> secure_socket.write(b"GET / HTTP/1.1\n\n") + >>> + >>> print(secure_socket.read()) + b'HTTP/1.1 500 Domain Not Found\r\nServer: Varnish\r\nRetry-After: 0\r\ncontent-type: text/html\r\nCache-Control: private, no-cache\r\nconnection: keep-alive\r\nContent-Length: 179\r\nAccept-Ranges: bytes\r\nDate: Sun, 05 Feb 2017 21:26:48 GMT\r\nVia: 1.1 varnish\r\nConnection: keep-alive\r\n\r\n\n\n\nFastly error: unknown domain \n\n\nFastly error: unknown domain: . Please check that this domain has been added to a service.' + >>> + >>> secure_socket.close() + + +Using the method wrap_socket() from a SSLContext instance +--------------------------------------------------------- + +.. code-block:: python + + >>> import socket + >>> import wolfssl + >>> + >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + >>> + >>> context = wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2) + >>> + >>> secure_socket = context.wrap_socket(sock) + >>> + >>> secure_socket.connect(("www.python.org", 443)) + >>> + >>> secure_socket.write(b"GET / HTTP/1.1\n\n") + >>> + >>> print(secure_socket.read()) + b'HTTP/1.1 500 Domain Not Found\r\nServer: Varnish\r\nRetry-After: 0\r\ncontent-type: text/html\r\nCache-Control: private, no-cache\r\nconnection: keep-alive\r\nContent-Length: 179\r\nAccept-Ranges: bytes\r\nDate: Sun, 05 Feb 2017 21:26:48 GMT\r\nVia: 1.1 varnish\r\nConnection: keep-alive\r\n\r\n\n\n\nFastly error: unknown domain \n\n\nFastly error: unknown domain: . Please check that this domain has been added to a service.' + >>> + >>> secure_socket.close() + +Creating an SSLSocket object from the scratch +--------------------------------------------- + +.. code-block:: python + + >>> import socket + >>> import wolfssl + >>> + >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + >>> + >>> secure_socket = wolfssl.SSLSocket(sock) + >>> + >>> secure_socket.connect(("www.python.org", 443)) + >>> + >>> secure_socket.write(b"GET / HTTP/1.1\n\n") + >>> + >>> print(secure_socket.read()) + b'HTTP/1.1 500 Domain Not Found\r\nServer: Varnish\r\nRetry-After: 0\r\ncontent-type: text/html\r\nCache-Control: private, no-cache\r\nconnection: keep-alive\r\nContent-Length: 179\r\nAccept-Ranges: bytes\r\nDate: Sun, 05 Feb 2017 21:26:48 GMT\r\nVia: 1.1 varnish\r\nConnection: keep-alive\r\n\r\n\n\n\nFastly error: unknown domain \n\n\nFastly error: unknown domain: . Please check that this domain has been added to a service.' + >>> + >>> secure_socket.close() diff --git a/wrapper/python/wolfssl/examples/client.py b/wrapper/python/wolfssl/examples/client.py new file mode 100755 index 000000000..33ab063b6 --- /dev/null +++ b/wrapper/python/wolfssl/examples/client.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# +# -*- coding: utf-8 -*- +# +# client.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, invalid-name, import-error + +import sys +import socket +import argparse + +try: + import wolfssl +except ImportError: + print("You must run 'python setup.py install' to use the examples") + sys.exit() + +def build_arg_parser(): + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument( + "-?", "--help", action="help", + help="show this help message and exit" + ) + + parser.add_argument( + "-h", metavar="host", default="127.0.0.1", + help="Host to connect to, default 127.0.0.1" + ) + + parser.add_argument( + "-p", metavar="port", type=int, default=11111, + help="Port to connect on, not 0, default 11111" + ) + + parser.add_argument( + "-v", metavar="version", type=int, choices=[0, 1, 2, 3], default=3, + help="SSL version [0-3], SSLv3(0) - TLS1.2(3)), default 3" + ) + + parser.add_argument( + "-l", metavar="ciphers", type=str, default="", + help="Cipher suite list (: delimited)" + ) + + parser.add_argument( + "-c", metavar="certificate", default="./certs/client-cert.pem", + help="Certificate file, default ./certs/client-cert.pem" + ) + + parser.add_argument( + "-k", metavar="key", default="./certs/client-key.pem", + help="Key file, default ./certs/client-key.pem" + ) + + parser.add_argument( + "-A", metavar="ca_file", default="./certs/ca-cert.pem", + help="Certificate Authority file, default ./certs/ca-cert.pem" + ) + + parser.add_argument( + "-d", action="store_true", + help="Disable client cert check" + ) + + parser.add_argument( + "-g", action="store_true", + help="Send server HTTP GET" + ) + + return parser + + +def get_method(index): + return ( + wolfssl.PROTOCOL_SSLv3, + wolfssl.PROTOCOL_TLSv1, + wolfssl.PROTOCOL_TLSv1_1, + wolfssl.PROTOCOL_TLSv1_2 + )[index] + + +def main(): + args = build_arg_parser().parse_args() + + bind_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + + context = wolfssl.SSLContext(get_method(args.v)) + + context.load_cert_chain(args.c, args.k) + + if args.d: + context.verify_mode = wolfssl.CERT_NONE + else: + context.verify_mode = wolfssl.CERT_REQUIRED + context.load_verify_locations(args.A) + + if args.l: + context.set_ciphers(args.l) + + try: + secure_socket = context.wrap_socket(bind_socket) + + secure_socket.connect((args.h, args.p)) + + if args.g: + secure_socket.write(b"GET / HTTP/1.1\n\n") + else: + secure_socket.write(b"hello wolfssl") + + print("\n", secure_socket.read(), "\n") + + except KeyboardInterrupt: + print() + + finally: + secure_socket.close() + + +if __name__ == '__main__': + main() diff --git a/wrapper/python/wolfssl/examples/server.py b/wrapper/python/wolfssl/examples/server.py new file mode 100755 index 000000000..db78afdf6 --- /dev/null +++ b/wrapper/python/wolfssl/examples/server.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# +# -*- coding: utf-8 -*- +# +# server.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, invalid-name, import-error + +import sys +import socket +import argparse + +try: + import wolfssl +except ImportError: + print("You must run 'python setup.py install' to use the examples") + sys.exit() + +def build_arg_parser(): + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument( + "-?", "--help", action="help", + help="show this help message and exit" + ) + + parser.add_argument( + "-p", metavar="port", type=int, default=11111, + help="Port to listen on, not 0, default 11111" + ) + + parser.add_argument( + "-v", metavar="version", type=int, choices=[0, 1, 2, 3], default=3, + help="SSL version [0-3], SSLv3(0) - TLS1.2(3)), default 3" + ) + + parser.add_argument( + "-l", metavar="ciphers", type=str, default="", + help="Cipher suite list (: delimited)" + ) + + parser.add_argument( + "-c", metavar="certificate", default="./certs/server-cert.pem", + help="Certificate file, default ./certs/server-cert.pem" + ) + + parser.add_argument( + "-k", metavar="key", default="./certs/server-key.pem", + help="Key file, default ./certs/server-key.pem" + ) + + parser.add_argument( + "-A", metavar="ca_file", default="./certs/client-cert.pem", + help="Certificate Authority file, default ./certs/client-cert.pem" + ) + + parser.add_argument( + "-d", action="store_true", + help="Disable client cert check" + ) + + parser.add_argument( + "-b", action="store_true", + help="Bind to any interface instead of localhost only" + ) + + parser.add_argument( + "-i", action="store_true", + help="Loop indefinitely (allow repeated connections)" + ) + + return parser + + +def get_method(index): + return ( + wolfssl.PROTOCOL_SSLv3, + wolfssl.PROTOCOL_TLSv1, + wolfssl.PROTOCOL_TLSv1_1, + wolfssl.PROTOCOL_TLSv1_2 + )[index] + + +def main(): + args = build_arg_parser().parse_args() + + bind_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + bind_socket.bind(("" if args.b else "localhost", args.p)) + bind_socket.listen(5) + + print("Server listening on port", bind_socket.getsockname()[1]) + + context = wolfssl.SSLContext(get_method(args.v), server_side=True) + + context.load_cert_chain(args.c, args.k) + + if args.d: + context.verify_mode = wolfssl.CERT_NONE + else: + context.verify_mode = wolfssl.CERT_REQUIRED + context.load_verify_locations(args.A) + + if args.l: + context.set_ciphers(args.l) + + while True: + try: + secure_socket = None + + new_socket, from_addr = bind_socket.accept() + + secure_socket = context.wrap_socket(new_socket) + + print("Connection received from", from_addr) + + print("\n", secure_socket.read(), "\n") + secure_socket.write(b"I hear you fa shizzle!") + + except KeyboardInterrupt: + print() + break + + finally: + if secure_socket: + secure_socket.close() + + if not args.i: + break + + bind_socket.close() + + +if __name__ == '__main__': + main() diff --git a/wrapper/python/wolfssl/include.am b/wrapper/python/wolfssl/include.am new file mode 100644 index 000000000..0c5d06bc1 --- /dev/null +++ b/wrapper/python/wolfssl/include.am @@ -0,0 +1,38 @@ +# vim:ft=automake +# included from Top Level Makefile.am +# All paths should be given relative to the root + +EXTRA_DIST+= wrapper/python/wolfssl/.gitignore +EXTRA_DIST+= wrapper/python/wolfssl/docs/api.rst +EXTRA_DIST+= wrapper/python/wolfssl/docs/conf.py +EXTRA_DIST+= wrapper/python/wolfssl/docs/examples.rst +EXTRA_DIST+= wrapper/python/wolfssl/docs/index.rst +EXTRA_DIST+= wrapper/python/wolfssl/docs/installation.rst +EXTRA_DIST+= wrapper/python/wolfssl/docs/Makefile +EXTRA_DIST+= wrapper/python/wolfssl/docs/licensing.rst +EXTRA_DIST+= wrapper/python/wolfssl/docs/requirements.txt +EXTRA_DIST+= wrapper/python/wolfssl/docs/usage.rst +EXTRA_DIST+= wrapper/python/wolfssl/examples/client.py +EXTRA_DIST+= wrapper/python/wolfssl/examples/server.py +EXTRA_DIST+= wrapper/python/wolfssl/test/conftest.py +EXTRA_DIST+= wrapper/python/wolfssl/test/test_client.py +EXTRA_DIST+= wrapper/python/wolfssl/test/test_context.py +EXTRA_DIST+= wrapper/python/wolfssl/test/test_methods.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/__about__.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/__init__.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/build_ffi.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/_memory.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/_methods.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/exceptions.py +EXTRA_DIST+= wrapper/python/wolfssl/src/wolfssl/utils.py +EXTRA_DIST+= wrapper/python/wolfssl/LICENSING.rst +EXTRA_DIST+= wrapper/python/wolfssl/Makefile +EXTRA_DIST+= wrapper/python/wolfssl/MANIFEST.in +EXTRA_DIST+= wrapper/python/wolfssl/README.rst +EXTRA_DIST+= wrapper/python/wolfssl/requirements-testing.txt +EXTRA_DIST+= wrapper/python/wolfssl/setup.py +EXTRA_DIST+= wrapper/python/wolfssl/tox.ini +EXTRA_DIST+= wrapper/python/wolfssl/Vagrantfile +EXTRA_DIST+= wrapper/python/wolfssl/.centos-provisioner.sh +EXTRA_DIST+= wrapper/python/wolfssl/.ubuntu-provisioner.sh + diff --git a/wrapper/python/wolfssl/requirements-testing.txt b/wrapper/python/wolfssl/requirements-testing.txt new file mode 100644 index 000000000..c422b4d23 --- /dev/null +++ b/wrapper/python/wolfssl/requirements-testing.txt @@ -0,0 +1,3 @@ +pytest +cffi +tox diff --git a/wrapper/python/wolfssl/setup.py b/wrapper/python/wolfssl/setup.py new file mode 100755 index 000000000..78839496b --- /dev/null +++ b/wrapper/python/wolfssl/setup.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# setup.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=import-error, wrong-import-position + +from __future__ import absolute_import +import os +import sys +import shutil +from setuptools import setup, find_packages + +sys.path.insert(0, 'src') +from wolfssl.__about__ import METADATA + +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("src"), + "package_dir" : {"": "src"}, + "cffi_modules" : ["./src/wolfssl/build_ffi.py:ffi"], + }, + requirements={ + "setup_requires" : ["cffi>=1.6.0"], + "install_requires" : ["cffi>=1.6.0"], + }, + scripts={}, + plugins={}, + tests={}, +) + + +def update_certs(): + c_certs_dir = "../../../certs" + py_certs_dir = "certs" + certs = [ + "ca-cert.pem", + "client-cert.pem", + "client-key.pem", + "server-cert.pem", + "server-key.pem", + "external/ca-digicert-ev.pem" + ] + + if os.path.isdir(c_certs_dir): + if not os.path.isdir(py_certs_dir): + os.makedirs(py_certs_dir) + + for cert in certs: + shutil.copy(os.path.join(c_certs_dir, cert), py_certs_dir) + + +if __name__ == "__main__": + update_certs() + + KWARGS = {k:v for dct in INFO.values() for (k, v) in dct.items()} + setup(**KWARGS) diff --git a/wrapper/python/wolfssl/src/wolfssl/__about__.py b/wrapper/python/wolfssl/src/wolfssl/__about__.py new file mode 100644 index 000000000..b85588f63 --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/__about__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# __about__.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring + +METADATA = dict( + __name__="wolfssl", + __version__="0.1.0", + __license__="GPLv2 or Commercial License", + __author__="wolfSSL Inc.", + __author_email__="info@wolfssl.com", + __url__="https://wolfssl.github.io/wolfssl-py", + __description__= \ + u"A Python module that encapsulates wolfSSL's C SSL/TLS library.", + __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/wolfssl/src/wolfssl/__init__.py b/wrapper/python/wolfssl/src/wolfssl/__init__.py new file mode 100644 index 000000000..6d79fb025 --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/__init__.py @@ -0,0 +1,677 @@ +# -*- coding: utf-8 -*- +# +# __init__.py +# +# Copyright (C) 2006-2017 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 +import errno +from socket import ( + socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_TYPE, error as socket_error +) + +try: + from wolfssl._ffi import ffi as _ffi + from wolfssl._ffi import lib as _lib +except ImportError: + pass + +from wolfssl.utils import t2b + +from wolfssl.exceptions import ( + CertificateError, SSLError, SSLEOFError, SSLSyscallError, + SSLWantReadError, SSLWantWriteError, SSLZeroReturnError +) + +from wolfssl._methods import ( + PROTOCOL_SSLv23, PROTOCOL_SSLv3, PROTOCOL_TLSv1, + PROTOCOL_TLSv1_1, PROTOCOL_TLSv1_2, PROTOCOL_TLS, + WolfSSLMethod as _WolfSSLMethod +) + +from wolfssl.__about__ import ( + __all__, METADATA +) + +globals().update(METADATA) + +CERT_NONE = 0 +CERT_REQUIRED = 1 + +_VERIFY_MODE_LIST = [CERT_NONE, CERT_REQUIRED] + +_SSL_SUCCESS = 1 +_SSL_FILETYPE_PEM = 1 +_SSL_ERROR_WANT_READ = 2 + +_WOLFSSL_ECC_SECP160K1 = 15 +_WOLFSSL_ECC_SECP160R1 = 16 +_WOLFSSL_ECC_SECP160R2 = 17 +_WOLFSSL_ECC_SECP192K1 = 18 +_WOLFSSL_ECC_SECP192R1 = 19 +_WOLFSSL_ECC_SECP224K1 = 20 +_WOLFSSL_ECC_SECP224R1 = 21 +_WOLFSSL_ECC_SECP256K1 = 22 +_WOLFSSL_ECC_SECP256R1 = 23 +_WOLFSSL_ECC_SECP384R1 = 24 +_WOLFSSL_ECC_SECP521R1 = 25 +_WOLFSSL_ECC_BRAINPOOLP256R1 = 26 +_WOLFSSL_ECC_BRAINPOOLP384R1 = 27 +_WOLFSSL_ECC_BRAINPOOLP512R1 = 28 + +_SUPPORTED_CURVES = [ + _WOLFSSL_ECC_SECP160K1, _WOLFSSL_ECC_SECP160R1, _WOLFSSL_ECC_SECP160R2, + _WOLFSSL_ECC_SECP192K1, _WOLFSSL_ECC_SECP192R1, _WOLFSSL_ECC_SECP224K1, + _WOLFSSL_ECC_SECP224R1, _WOLFSSL_ECC_SECP256K1, _WOLFSSL_ECC_SECP256R1, + _WOLFSSL_ECC_SECP384R1, _WOLFSSL_ECC_SECP521R1, + _WOLFSSL_ECC_BRAINPOOLP256R1, _WOLFSSL_ECC_BRAINPOOLP384R1, + _WOLFSSL_ECC_BRAINPOOLP512R1 +] + +_PY3 = sys.version_info[0] == 3 + +class SSLContext(object): + """ + An SSLContext holds various SSL-related configuration options and + data, such as certificates and possibly a private key. + """ + + def __init__(self, protocol, server_side=False): + method = _WolfSSLMethod(protocol, server_side) + + self.protocol = protocol + self._side = server_side + self._verify_mode = None + self.native_object = _lib.wolfSSL_CTX_new(method.native_object) + + # wolfSSL_CTX_new() takes ownership of the method. + # the method is freed later inside wolfSSL_CTX_free() + # or if wolfSSL_CTX_new() failed to allocate the context object. + method.native_object = _ffi.NULL + + if self.native_object == _ffi.NULL: + raise MemoryError("Unnable to allocate context object") + + # verify_mode initialization needs a valid native_object. + self.verify_mode = CERT_NONE + + if not server_side: + for curve in _SUPPORTED_CURVES: + ret = _lib.wolfSSL_CTX_UseSupportedCurve(self.native_object, + curve) + if ret != _SSL_SUCCESS: + raise SSLError("unnable to set curve (%d)" % curve) + + + def __del__(self): + if getattr(self, 'native_object', _ffi.NULL) != _ffi.NULL: + _lib.wolfSSL_CTX_free(self.native_object) + + + @property + def verify_mode(self): + """ + Whether to try to verify other peers’ certificates and how to behave + if verification fails. This attribute must be one of CERT_NONE, + CERT_OPTIONAL or CERT_REQUIRED. + """ + return self._verify_mode + + + @verify_mode.setter + def verify_mode(self, value): + if value not in _VERIFY_MODE_LIST: + raise ValueError("verify_mode must be one of CERT_NONE, " + "CERT_OPTIONAL or CERT_REQUIRED") + + if value != self._verify_mode: + self._verify_mode = value + _lib.wolfSSL_CTX_set_verify(self.native_object, + self._verify_mode, + _ffi.NULL) + + + def wrap_socket(self, sock, server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True): + """ + Wrap an existing Python socket sock and return an SSLSocket object. + sock must be a SOCK_STREAM socket; other socket types are unsupported. + + The returned SSL socket is tied to the context, its settings and + certificates. The parameters server_side, do_handshake_on_connect and + suppress_ragged_eofs have the same meaning as in the top-level + wrap_socket() function. + """ + return SSLSocket(sock=sock, server_side=server_side, + do_handshake_on_connect=do_handshake_on_connect, + suppress_ragged_eofs=suppress_ragged_eofs, + _context=self) + + + def set_ciphers(self, ciphers): + """ + Set the available ciphers for sockets created with this context. It + should be a string in the wolfSSL cipher list format. If no cipher can + be selected (because compile-time options or other configuration forbids + use of all the specified ciphers), an SSLError will be raised. + """ + ret = _lib.wolfSSL_CTX_set_cipher_list(self.native_object, t2b(ciphers)) + + if ret != _SSL_SUCCESS: + raise SSLError("Unnable to set cipher list") + + + def load_cert_chain(self, certfile, keyfile=None, password=None): + """ + Load a private key and the corresponding certificate. The certfile + string must be the path to a single file in PEM format containing + the certificate as well as any number of CA certificates needed to + establish the certificate's authenticity. + + The keyfile string, if present, must point to a file containing the + private key in. + + The password parameter is not supported yet. + """ + + if password is not None: + raise NotImplementedError("password callback support not " + "implemented yet") + + if certfile is not None: + ret = _lib.wolfSSL_CTX_use_certificate_chain_file( + self.native_object, t2b(certfile)) + if ret != _SSL_SUCCESS: + raise SSLError("Unnable to load certificate chain. Err %d"% ret) + else: + raise TypeError("certfile should be a valid filesystem path") + + if keyfile is not None: + ret = _lib.wolfSSL_CTX_use_PrivateKey_file( + self.native_object, t2b(keyfile), _SSL_FILETYPE_PEM) + if ret != _SSL_SUCCESS: + raise SSLError("Unnable to load private key. Err %d" % ret) + + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + """ + Load a set of "certification authority" (CA) certificates used to + validate other peers' certificates when verify_mode is other than + CERT_NONE. At least one of cafile or capath must be specified. + + The cafile string, if present, is the path to a file of concatenated + CA certificates in PEM format. + + The capath string, if present, is the path to a directory containing + several CA certificates in PEM format. + """ + + if cafile is None and capath is None and cadata is None: + raise TypeError("cafile, capath and cadata cannot be all omitted") + + if cafile is not None or capath is not None: + ret = _lib.wolfSSL_CTX_load_verify_locations( + self.native_object, + t2b(cafile) if cafile else _ffi.NULL, + t2b(capath) if capath else _ffi.NULL) + + if ret != _SSL_SUCCESS: + raise SSLError("Unnable to load verify locations. Err %d" % ret) + + if cadata is not None: + ret = _lib.wolfSSL_CTX_load_verify_buffer( + self.native_object, t2b(cadata), len(cadata), _SSL_FILETYPE_PEM) + + if ret != _SSL_SUCCESS: + raise SSLError("Unnable to load verify locations. Err %d" % ret) + + +class SSLSocket(socket): + """ + This class implements a subtype of socket.socket that wraps the + underlying OS socket in an SSL/TLS connection, providing secure + read and write methods over that channel. + """ + + def __init__(self, sock=None, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_TLS, ca_certs=None, + do_handshake_on_connect=True, family=AF_INET, + sock_type=SOCK_STREAM, proto=0, fileno=None, + suppress_ragged_eofs=True, ciphers=None, + _context=None): + + # set options + self.do_handshake_on_connect = do_handshake_on_connect + self.suppress_ragged_eofs = suppress_ragged_eofs + self.server_side = server_side + + # set context + if _context: + self._context = _context + else: + if server_side and not certfile: + raise ValueError("certfile must be specified for server-side " + "operations") + + if keyfile and not certfile: + raise ValueError("certfile must be specified") + + if certfile and not keyfile: + keyfile = certfile + + self._context = SSLContext(ssl_version, server_side) + self._context.verify_mode = cert_reqs + if ca_certs: + self._context.load_verify_locations(ca_certs) + if certfile: + self._context.load_cert_chain(certfile, keyfile) + if ciphers: + self._context.set_ciphers(ciphers) + + self.keyfile = keyfile + self.certfile = certfile + self.cert_reqs = cert_reqs + self.ssl_version = ssl_version + self.ca_certs = ca_certs + self.ciphers = ciphers + + # preparing socket + if sock is not None: + # Can't use sock.type as other flags (such as SOCK_NONBLOCK) get + # mixed in. + if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM: + raise NotImplementedError("only stream sockets are supported") + + if _PY3: + socket.__init__(self, + family=sock.family, + type=sock.type, + proto=sock.proto, + fileno=sock.fileno()) + else: + socket.__init__(self, _sock=sock._sock) + + self.settimeout(sock.gettimeout()) + + if _PY3: + sock.detach() + + elif fileno is not None: + socket.__init__(self, fileno=fileno) + + else: + socket.__init__(self, family=family, type=sock_type, + proto=proto) + + # see if we are connected + try: + self.getpeername() + except socket_error as exception: + if exception.errno != errno.ENOTCONN: + raise + connected = False + else: + connected = True + + self._closed = False + self._connected = connected + + # create the SSL object + self.native_object = _lib.wolfSSL_new(self.context.native_object) + if self.native_object == _ffi.NULL: + raise MemoryError("Unnable to allocate ssl object") + + ret = _lib.wolfSSL_set_fd(self.native_object, self.fileno()) + if ret != _SSL_SUCCESS: + self._release_native_object() + raise ValueError("Unnable to set fd to ssl object") + + if connected: + try: + if do_handshake_on_connect: + self.do_handshake() + except: + self._release_native_object() + self.close() + raise + + + def __del__(self): + self._release_native_object() + + + def _release_native_object(self): + if getattr(self, 'native_object', _ffi.NULL) != _ffi.NULL: + _lib.wolfSSL_CTX_free(self.native_object) + self.native_object = _ffi.NULL + + + @property + def context(self): + """ + Returns the context used by this object. + """ + return self._context + + + def dup(self): + raise NotImplementedError("Can't dup() %s instances" % + self.__class__.__name__) + + + def _check_closed(self, call=None): + if self.native_object == _ffi.NULL: + raise ValueError("%s on closed or unwrapped secure channel" % call) + + def _check_connected(self): + if not self._connected: + # getpeername() will raise ENOTCONN if the socket is really + # not connected; note that we can be connected even without + # _connected being set, e.g. if connect() first returned + # EAGAIN. + self.getpeername() + + + def write(self, data): + """ + Write DATA to the underlying secure channel. + Returns number of bytes of DATA actually transmitted. + """ + self._check_closed("write") + self._check_connected() + + data = t2b(data) + + return _lib.wolfSSL_write(self.native_object, data, len(data)) + + + def send(self, data, flags=0): + if flags != 0: + raise NotImplementedError("non-zero flags not allowed in calls to " + "send() on %s" % self.__class__) + + return self.write(data) + + + def sendall(self, data, flags=0): + if flags != 0: + raise NotImplementedError("non-zero flags not allowed in calls to " + "sendall() on %s" % self.__class__) + + length = len(data) + sent = 0 + + while sent < length: + sent += self.write(data[sent:]) + + return sent + + + def sendto(self, data, flags_or_addr, addr=None): + # Ensure programs don't send unencrypted data trying to use this method + raise NotImplementedError("sendto not allowed on instances " + "of %s" % self.__class__) + + + def sendmsg(self, *args, **kwargs): + # Ensure programs don't send unencrypted data trying to use this method + raise NotImplementedError("sendmsg not allowed on instances " + "of %s" % self.__class__) + + + def sendfile(self, file, offset=0, count=None): + # Ensure programs don't send unencrypted files trying to use this method + raise NotImplementedError("sendfile not allowed on instances " + "of %s" % self.__class__) + + + def read(self, length=1024, buffer=None): + """ + Read up to LENGTH bytes and return them. + Return zero-length string on EOF. + """ + self._check_closed("read") + self._check_connected() + + if buffer is not None: + raise ValueError("buffer not allowed in calls to " + "read() on %s" % self.__class__) + + data = _ffi.new('byte[%d]' % length) + length = _lib.wolfSSL_read(self.native_object, data, length) + + if length < 0: + err = _lib.wolfSSL_get_error(self.native_object, 0) + if err == _SSL_ERROR_WANT_READ: + raise SSLWantReadError() + else: + raise SSLError("wolfSSL_read error (%d)" % err) + + return _ffi.buffer(data, length)[:] if length > 0 else b'' + + + def recv(self, length=1024, flags=0): + if flags != 0: + raise NotImplementedError("non-zero flags not allowed in calls to " + "recv() on %s" % self.__class__) + + return self.read(self, length) + + + def recv_into(self, buffer, nbytes=None, flags=0): + raise NotImplementedError("recv_into not allowed on instances " + "of %s" % self.__class__) + + + def recvfrom(self, length=1024, flags=0): + # Ensure programs don't receive encrypted data trying to use this method + raise NotImplementedError("recvfrom not allowed on instances " + "of %s" % self.__class__) + + + def recvfrom_into(self, buffer, nbytes=None, flags=0): + # Ensure programs don't receive encrypted data trying to use this method + raise NotImplementedError("recvfrom_into not allowed on instances " + "of %s" % self.__class__) + + + def recvmsg(self, *args, **kwargs): + raise NotImplementedError("recvmsg not allowed on instances of %s" % + self.__class__) + + + def recvmsg_into(self, *args, **kwargs): + raise NotImplementedError("recvmsg_into not allowed on instances of " + "%s" % self.__class__) + + + def shutdown(self, how): + if self.native_object != _ffi.NULL: + _lib.wolfSSL_shutdown(self.native_object) + self._release_native_object() + socket.shutdown(self, how) + + + def unwrap(self): + """ + Unwraps the underlying OS socket from the SSL/TLS connection. + Returns the wrapped OS socket. + """ + if self.native_object != _ffi.NULL: + _lib.wolfSSL_set_fd(self.native_object, -1) + + sock = socket(family=self.family, + sock_type=self.type, + proto=self.proto, + fileno=self.fileno()) + sock.settimeout(self.gettimeout()) + self.detach() + + return sock + + + def do_handshake(self, block=False): + """ + Perform a TLS/SSL handshake. + """ + self._check_closed("do_handshake") + self._check_connected() + + ret = _lib.wolfSSL_negotiate(self.native_object) + if ret != _SSL_SUCCESS: + raise SSLError("do_handshake failed with error %d" % ret) + + + def _real_connect(self, addr, connect_ex): + if self.server_side: + raise ValueError("can't connect in server-side mode") + + # Here we assume that the socket is client-side, and not + # connected at the time of the call. We connect it, then wrap it. + if self._connected: + raise ValueError("attempt to connect already-connected SSLSocket!") + + if connect_ex: + err = socket.connect_ex(self, addr) + else: + err = 0 + socket.connect(self, addr) + + if err == 0: + self._connected = True + if self.do_handshake_on_connect: + self.do_handshake() + + return err + + + def connect(self, addr): + """ + Connects to remote ADDR, and then wraps the connection in a secure + channel. + """ + self._real_connect(addr, False) + + + def connect_ex(self, addr): + """ + Connects to remote ADDR, and then wraps the connection in a secure + channel. + """ + return self._real_connect(addr, True) + + + def accept(self): + """ + Accepts a new connection from a remote client, and returns a tuple + containing that new connection wrapped with a server-side secure + channel, and the address of the remote client. + """ + if not self.server_side: + raise ValueError("can't accept in client-side mode") + + newsock, addr = socket.accept(self) + newsock = self.context.wrap_socket( + newsock, + do_handshake_on_connect=self.do_handshake_on_connect, + suppress_ragged_eofs=self.suppress_ragged_eofs, + server_side=True) + + return newsock, addr + + +def wrap_socket(sock, keyfile=None, certfile=None, server_side=False, + cert_reqs=CERT_NONE, ssl_version=PROTOCOL_TLS, ca_certs=None, + do_handshake_on_connect=True, suppress_ragged_eofs=True, + ciphers=None): + """ + Takes an instance sock of socket.socket, and returns an instance of + wolfssl.SSLSocket, wraping the underlying socket in an SSL context. + + The sock parameter must be a SOCK_STREAM socket; other socket types are + unsupported. + + The keyfile and certfile parameters specify optional files whith proper + key and the certificates used to identify the local side of the connection. + + The parameter server_side is a boolean which identifies whether server-side + or client-side behavior is desired from this socket. + + The parameter cert_reqs specifies whether a certificate is required from the + other side of the connection, and whether it will be validated if provided. + It must be one of the three values: + + * CERT_NONE (certificates ignored) + * CERT_OPTIONAL (not required, but validated if provided) + * CERT_REQUIRED (required and validated) + + If the value of this parameter is not CERT_NONE, then the ca_certs parameter + must point to a file of CA certificates. + + The ca_certs file contains a set of concatenated “certification authority” + certificates, which are used to validate certificates passed from the other + end of the connection. + + The parameter ssl_version specifies which version of the SSL protocol to + use. Typically, the server chooses a particular protocol version, and the + client must adapt to the server’s choice. Most of the versions are not + interoperable with the other versions. If not specified, the default is + PROTOCOL_TLS; it provides the most compatibility with other versions. + + Here’s a table showing which versions in a client (down the side) can + connect to which versions in a server (along the top): + + +------------------+-------+-----+-------+---------+---------+ + | client \\ server | SSLv3 | TLS | TLSv1 | TLSv1.1 | TLSv1.2 | + +------------------+-------+-----+-------+---------+---------+ + | SSLv3 | yes | yes | no | no | no | + +------------------+-------+-----+-------+---------+---------+ + | TLS (SSLv23) | yes | yes | yes | yes | yes | + +------------------+-------+-----+-------+---------+---------+ + | TLSv1 | no | yes | yes | no | no | + +------------------+-------+-----+-------+---------+---------+ + | TLSv1.1 | no | yes | no | yes | no | + +------------------+-------+-----+-------+---------+---------+ + | TLSv1.2 | no | yes | no | no | yes | + +------------------+-------+-----+-------+---------+---------+ + + Note: + Which connections succeed will vary depending on the versions of the ssl + providers on both sides of the communication. + + The ciphers parameter sets the available ciphers for this SSL object. It + should be a string in the wolfSSL cipher list format. + + The parameter do_handshake_on_connect specifies whether to do the SSL + handshake automatically after doing a socket.connect(), or whether the + application program will call it explicitly, by invoking the + SSLSocket.do_handshake() method. Calling SSLSocket.do_handshake() explicitly + gives the program control over the blocking behavior of the socket I/O + involved in the handshake. + + The parameter suppress_ragged_eofs is not supported yet. + """ + return SSLSocket(sock=sock, keyfile=keyfile, certfile=certfile, + server_side=server_side, cert_reqs=cert_reqs, + ssl_version=ssl_version, ca_certs=ca_certs, + do_handshake_on_connect=do_handshake_on_connect, + suppress_ragged_eofs=suppress_ragged_eofs, + ciphers=ciphers) diff --git a/wrapper/python/wolfssl/src/wolfssl/_memory.py b/wrapper/python/wolfssl/src/wolfssl/_memory.py new file mode 100644 index 000000000..456f5ad36 --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/_memory.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# _memory.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring + +try: + from wolfssl._ffi import ffi as _ffi + from wolfssl._ffi import lib as _lib +except ImportError: + pass + +_DYNAMIC_TYPE_METHOD = 11 + +def _native_free(native_object, dynamic_type): + _lib.wolfSSL_Free(native_object, _ffi.NULL, dynamic_type) diff --git a/wrapper/python/wolfssl/src/wolfssl/_methods.py b/wrapper/python/wolfssl/src/wolfssl/_methods.py new file mode 100644 index 000000000..5c2b9794a --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/_methods.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# +# _methods.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, invalid-name + +try: + from wolfssl._ffi import ffi as _ffi + from wolfssl._ffi import lib as _lib +except ImportError: + pass + +from wolfssl._memory import ( + _native_free, _DYNAMIC_TYPE_METHOD +) + + +PROTOCOL_SSLv23 = 1 +PROTOCOL_SSLv3 = 2 +PROTOCOL_TLS = 1 +PROTOCOL_TLSv1 = 3 +PROTOCOL_TLSv1_1 = 4 +PROTOCOL_TLSv1_2 = 5 + +_PROTOCOL_LIST = [PROTOCOL_SSLv23, PROTOCOL_SSLv3, PROTOCOL_TLS, + PROTOCOL_TLSv1, PROTOCOL_TLSv1_1, PROTOCOL_TLSv1_2] + + +class WolfSSLMethod(object): + """ + An SSLMethod holds SSL-related configuration options such as + protocol version and communication side. + """ + + def __init__(self, protocol, server_side): + if protocol not in _PROTOCOL_LIST: + raise ValueError("this protocol is not supported") + + elif protocol == PROTOCOL_SSLv3: + raise ValueError("this protocol is not supported") + + elif protocol == PROTOCOL_TLSv1: + raise ValueError("this protocol is not supported") + + elif protocol == PROTOCOL_TLSv1_1: + raise ValueError("this protocol is not supported") + + elif protocol == PROTOCOL_TLSv1_2: + self.native_object = \ + _lib.wolfTLSv1_2_server_method() if server_side else \ + _lib.wolfTLSv1_2_client_method() + + elif protocol in [PROTOCOL_SSLv23, PROTOCOL_TLS]: + self.native_object = \ + _lib.wolfSSLv23_server_method() if server_side else \ + _lib.wolfSSLv23_client_method() + + if self.native_object == _ffi.NULL: + raise MemoryError("Unnable to allocate method object") + + + def __del__(self): + if getattr(self, 'native_object', _ffi.NULL) != _ffi.NULL: + _native_free(self.native_object, _DYNAMIC_TYPE_METHOD) diff --git a/wrapper/python/wolfssl/src/wolfssl/build_ffi.py b/wrapper/python/wolfssl/src/wolfssl/build_ffi.py new file mode 100644 index 000000000..7c912ea50 --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/build_ffi.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# build_ffi.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, invalid-name + +from cffi import FFI + +ffi = FFI() + +ffi.set_source( + "wolfssl._ffi", + """ + #include + #include + + void wolfSSL_Free(void *ptr, void* heap, int type); + """, + include_dirs=["/usr/local/include"], + library_dirs=["/usr/local/lib"], + libraries=["wolfssl"], +) + +ffi.cdef( + """ + typedef unsigned char byte; + typedef unsigned int word32; + + void wolfSSL_Free(void*, void*, int); + + void* wolfSSLv23_server_method(void); + void* wolfSSLv23_client_method(void); + void* wolfTLSv1_2_server_method(void); + void* wolfTLSv1_2_client_method(void); + + void* wolfSSL_CTX_new(void*); + void wolfSSL_CTX_free(void*); + + void wolfSSL_CTX_set_verify(void*, int, void*); + int wolfSSL_CTX_set_cipher_list(void*, const char*); + int wolfSSL_CTX_use_PrivateKey_file(void*, const char*, int); + int wolfSSL_CTX_load_verify_locations(void*, const char*, const char*); + int wolfSSL_CTX_load_verify_buffer(void*, const unsigned char*, long, int); + int wolfSSL_CTX_use_certificate_chain_file(void*, const char *); + int wolfSSL_CTX_UseSupportedCurve(void*, short); + + + void* wolfSSL_new(void*); + void wolfSSL_free(void*); + + int wolfSSL_set_fd(void*, int); + int wolfSSL_get_error(void*, int); + int wolfSSL_negotiate(void*); + int wolfSSL_write(void*, const void*, int); + int wolfSSL_read(void*, void*, int); + int wolfSSL_shutdown(void*); + """ +) + +if __name__ == "__main__": + ffi.compile(verbose=1) diff --git a/wrapper/python/wolfssl/src/wolfssl/exceptions.py b/wrapper/python/wolfssl/src/wolfssl/exceptions.py new file mode 100644 index 000000000..4a925ed1a --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/exceptions.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# +# exceptions.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring + +from socket import error as socket_error + + +class SSLError(socket_error): + """ + Raised to signal an error from the wolfSSL's SSL/TLS library. This signifies + some problem in the higher-level encryption and authentication layer that's + superimposed on the underlying network connection. This error is a subtype + of socket.error, which in turn is a subtype of IOError. The error code and + message of SSLError instances are provided by the wolfSSL library. + """ + pass + + +class SSLZeroReturnError(SSLError): + """ + A subclass of SSLError raised when trying to read or write and the SSL + connection has been closed cleanly. Note that this doesn't mean that the + underlying transport (read TCP) has been closed. + """ + pass + + +class SSLWantReadError(SSLError): + """ + A subclass of SSLError raised by a non-blocking SSL socket when trying to + read or write data, but more data needs to be received on the underlying TCP + transport before the request can be fulfilled. + """ + pass + + +class SSLWantWriteError(SSLError): + """ + A subclass of SSLError raised by a non-blocking SSL socket when trying to + read or write data, but more data needs to be sent on the underlying TCP + transport before the request can be fulfilled. + """ + pass + + +class SSLSyscallError(SSLError): + """ + A subclass of SSLError raised when a system error was encountered while + trying to fulfill an operation on a SSL socket. Unfortunately, there is no + easy way to inspect the original errno number. + """ + pass + + +class SSLEOFError(SSLError): + """ + A subclass of SSLError raised when the SSL connection has been terminated + abruptly. Generally, you shouldn't try to reuse the underlying transport + when this error is encountered. + """ + pass + +class CertificateError(ValueError): + """ + Raised to signal an error with a certificate (such as mismatching hostname). + Certificate errors detected by wolfSSL, though, raise an SSLError. + """ + pass diff --git a/wrapper/python/wolfssl/src/wolfssl/utils.py b/wrapper/python/wolfssl/src/wolfssl/utils.py new file mode 100644 index 000000000..84e003dcf --- /dev/null +++ b/wrapper/python/wolfssl/src/wolfssl/utils.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# +# utils.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, unused-import, undefined-variable + +import sys +from binascii import hexlify as b2h, unhexlify as h2b + +_PY3 = sys.version_info[0] == 3 +_TEXT_TYPE = str if _PY3 else unicode +_BINARY_TYPE = bytes if _PY3 else str + +def t2b(string): + """ + Converts text to bynary. + """ + if isinstance(string, _BINARY_TYPE): + return string + return _TEXT_TYPE(string).encode("utf-8") diff --git a/wrapper/python/wolfssl/test/conftest.py b/wrapper/python/wolfssl/test/conftest.py new file mode 100644 index 000000000..5bb1a023f --- /dev/null +++ b/wrapper/python/wolfssl/test/conftest.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# conftest.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, redefined-outer-name + +import sys +import ssl +import wolfssl +import pytest + +@pytest.fixture +def tcp_socket(): + import socket + from contextlib import closing + + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + yield sock + +@pytest.fixture( + params=[ssl, wolfssl] if sys.version_info.major == 3 else [wolfssl], + ids=["ssl", "wolfssl"] if sys.version_info.major == 3 else ["wolfssl"]) +def ssl_provider(request): + return request.param + +@pytest.fixture +def ssl_context(ssl_provider): + return ssl_provider.SSLContext(ssl_provider.PROTOCOL_SSLv23) diff --git a/wrapper/python/wolfssl/test/test_client.py b/wrapper/python/wolfssl/test/test_client.py new file mode 100644 index 000000000..029e9c1f0 --- /dev/null +++ b/wrapper/python/wolfssl/test/test_client.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# +# test_client.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, invalid-name, import-error +# pylint: disable=redefined-outer-name + +import pytest + +HOST = "www.python.org" +PORT = 443 +CA_CERTS = "certs/ca-digicert-ev.pem" + +@pytest.fixture( + params=["wrap_socket", "wrap_socket_with_ca", + "wrap_socket_from_context", "ssl_socket"]) +def secure_socket(request, ssl_provider, tcp_socket): + sock = None + + if request.param == "wrap_socket": + sock = ssl_provider.wrap_socket(tcp_socket) + + elif request.param == "wrap_socket_with_ca": + sock = ssl_provider.wrap_socket( + tcp_socket, cert_reqs=ssl_provider.CERT_REQUIRED, ca_certs=CA_CERTS) + + elif request.param == "wrap_socket_from_context": + ctx = ssl_provider.SSLContext(ssl_provider.PROTOCOL_TLSv1_2) + + ctx.verify_mode = ssl_provider.CERT_REQUIRED + ctx.load_verify_locations(CA_CERTS) + + sock = ctx.wrap_socket(tcp_socket) + + elif request.param == "ssl_socket": + sock = ssl_provider.SSLSocket( + tcp_socket, cert_reqs=ssl_provider.CERT_REQUIRED, ca_certs=CA_CERTS) + + if sock: + yield sock + sock.close() + +def test_secure_connection(secure_socket): + secure_socket.connect((HOST, PORT)) + + secure_socket.write(b"GET / HTTP/1.1\n\n") + assert secure_socket.read(4) == b"HTTP" diff --git a/wrapper/python/wolfssl/test/test_context.py b/wrapper/python/wolfssl/test/test_context.py new file mode 100644 index 000000000..8de384eac --- /dev/null +++ b/wrapper/python/wolfssl/test/test_context.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# +# test_context.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, invalid-name, import-error +# pylint: disable=redefined-outer-name + +import pytest + +with open("certs/ca-cert.pem") as ca: + _CADATA = ca.read() + +def test_context_creation(ssl_context): + assert ssl_context != None + +def test_verify_mode(ssl_provider, ssl_context): + with pytest.raises(ValueError): + ssl_context.verify_mode = -1 + + assert ssl_context.verify_mode == ssl_provider.CERT_NONE + + ssl_context.verify_mode = ssl_provider.CERT_REQUIRED + assert ssl_context.verify_mode == ssl_provider.CERT_REQUIRED + +def test_set_ciphers(ssl_context): + ssl_context.set_ciphers("DHE-RSA-AES256-SHA256") + + with pytest.raises(Exception): + ssl_context.set_ciphers("foo") + +def test_load_cert_chain_raises(ssl_context): + with pytest.raises(TypeError): + ssl_context.load_cert_chain(None) + +def test_load_cert_chain(ssl_context): + ssl_context.load_cert_chain("certs/client-cert.pem", + "certs/client-key.pem") + +def test_load_verify_locations_raises(ssl_context): + with pytest.raises(TypeError): + ssl_context.load_verify_locations(None) + +def test_load_verify_locations_with_cafile(ssl_context): + ssl_context.load_verify_locations(cafile="certs/ca-cert.pem") + +def test_load_verify_locations_with_cadata(ssl_provider, ssl_context): + ssl_context.load_verify_locations(cadata=_CADATA) diff --git a/wrapper/python/wolfssl/test/test_methods.py b/wrapper/python/wolfssl/test/test_methods.py new file mode 100644 index 000000000..a5cbae30b --- /dev/null +++ b/wrapper/python/wolfssl/test/test_methods.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# test_methods.py +# +# Copyright (C) 2006-2017 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 + +# pylint: disable=missing-docstring, redefined-outer-name, import-error + +import pytest +from wolfssl._methods import (WolfSSLMethod, PROTOCOL_SSLv3, PROTOCOL_SSLv23, + PROTOCOL_TLS, PROTOCOL_TLSv1, PROTOCOL_TLSv1_1, + PROTOCOL_TLSv1_2) +from wolfssl._ffi import ffi as _ffi + +@pytest.fixture( + params=[-1, PROTOCOL_SSLv3, PROTOCOL_TLSv1, PROTOCOL_TLSv1_1], + ids=["invalid", "SSLv3", "TLSv1", "TLSv1_1"]) +def unsupported_method(request): + yield request.param + +@pytest.fixture( + params=[PROTOCOL_SSLv23, PROTOCOL_TLS, PROTOCOL_TLSv1_2], + ids=["SSLv23", "TLS", "TLSv1_2"]) +def supported_method(request): + yield request.param + + +def test_unsupported_method(unsupported_method): + with pytest.raises(ValueError): + WolfSSLMethod(unsupported_method, False) + + with pytest.raises(ValueError): + WolfSSLMethod(unsupported_method, True) + +def test_supported_method(supported_method): + client = WolfSSLMethod(supported_method, False) + server = WolfSSLMethod(supported_method, True) + + assert isinstance(client, WolfSSLMethod) + assert isinstance(server, WolfSSLMethod) + assert client.native_object != _ffi.NULL + assert server.native_object != _ffi.NULL diff --git a/wrapper/python/wolfssl/tox.ini b/wrapper/python/wolfssl/tox.ini new file mode 100644 index 000000000..d552f72b3 --- /dev/null +++ b/wrapper/python/wolfssl/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist=py27,py34,py35,py36 +skip_missing_interpreters=true + +[testenv] +deps=-rrequirements-testing.txt +commands=py.test test/ {posargs}