diff options
author | Colin Watson <cjwatson@debian.org> | 2021-08-16 00:07:32 +0100 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2021-08-16 00:07:32 +0100 |
commit | 7ef4f5a87346ec1362c58904e7e0e686ac25da1e (patch) | |
tree | b2a13b970526abbd5048c2c8412c906dc968a260 |
Import python-libnacl_1.8.0.orig.tar.gz
[dgit import orig python-libnacl_1.8.0.orig.tar.gz]
88 files changed, 5246 insertions, 0 deletions
@@ -0,0 +1,3 @@ +Thomas S Hatch +Sam Smith +Pedro Algarvio @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {2014} Thomas S Hatch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c5c3bf6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include LICENSE +include AUTHORS +include README.rst +recursive-include tests *.py +recursive-include doc * +recursive-include pkg * diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..5b4fd06 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,25 @@ +Metadata-Version: 2.1 +Name: libnacl +Version: 1.8.0 +Summary: Python bindings for libsodium based on ctypes +Home-page: https://libnacl.readthedocs.org/ +Author: Thomas S Hatch +Author-email: thatch@saltstack.com +License: UNKNOWN +Platform: UNKNOWN +Classifier: Operating System :: OS Independent +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Topic :: Security :: Cryptography +License-File: LICENSE +License-File: AUTHORS + +UNKNOWN + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..bbf32de --- /dev/null +++ b/README.rst @@ -0,0 +1,61 @@ +============== +Python libnacl +============== + +This library is used to gain direct access to the functions exposed by +Daniel J. Bernstein's nacl library via libsodium. It has +been constructed to maintain extensive documentation on how to use nacl +as well as being completely portable. The file in libnacl/__init__.py +can be pulled out and placed directly in any project to give a single file +binding to all of nacl. + +Higher Level Classes +==================== + +The libnacl code also ships with many high level classes which make nacl +cryptography easy and safe, for documentation please see: +http://libnacl.readthedocs.org/ + +Why libnacl +=========== + +There are a number of libraries out there binding to libsodium, so why make +libnacl? + +1. libnacl does not have any non-python hard deps outside of libsodium +2. libnacl does not need to be compiled +3. libnacl is easy to package and very portable +4. Inclusion of high level pythonic encryption classes +5. Ability to have a single embeddable and transferable bindings file + that can be added directly to python applications without needing + to dep libnacl + +This makes libnacl very portable, very easy to use and easy to distribute. + +Install +======= + +The libnacl code is easiy installed via a setup.py from the source or via pip. + +From Source: + +.. code-block:: bash + + tar xvf libnacl-1.5.2.tar.gz + cd libnacl-1.5.2 + python setup.py install + +Via Pip: + +.. code-block:: bash + + pip install libnacl + +Remember that libnacl can be installed for python 2 and 3. + +Linux distributions +------------------- + +Libnacl is shiped with many linux distributions, check your distribution +package manager for the package ``python-libnacl``, ``python2-libnacl`` +and/or ``python3-libnacl``. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..ab5262e --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,177 @@ +# 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 clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make <target>' where <target> 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 " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @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)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +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." + +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/Python-NaCl.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Python-NaCl.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Python-NaCl" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Python-NaCl" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +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)." + +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." + +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." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +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)." + +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." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +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." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..26dd69f --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,331 @@ +# -*- coding: utf-8 -*- +# +# libnacl documentation build configuration file, created by +# sphinx-quickstart on Thu May 29 10:29:25 2014. +# +# 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 +sys.path.insert(0, os.path.abspath('..')) +from libnacl import version + +# 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', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +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'libnacl' +copyright = u'2020, Thomas S Hatch' + +# 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. +# +# The short X.Y version. +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#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. +exclude_patterns = ['_build'] + +# 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 + + +# -- 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 = 'default' + +# 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 = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# 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 (within the static path) to use as 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 '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# 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 <link> 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 + +# Output file base name for HTML help builder. +#htmlhelp_basename = 'libnacl' + + +# -- 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': '', +} + +# 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 = [ + ('index', 'libnacl.tex', u'libnacl Documentation', + u'Thomas S Hatch', '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 = [ + ('index', 'libnacl', u'libnacl Documentation', + [u'Thomas S Hatch'], 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 = [ + ('index', 'libnacl', u'libnacl Documentation', + u'Thomas S Hatch', 'libnacl', '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 + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'libnacl' +epub_author = u'Thomas S Hatch' +epub_publisher = u'Thomas S Hatch' +epub_copyright = u'2020, Thomas S Hatch' + +# The basename for the epub file. It defaults to the project name. +#epub_basename = u'libnacl' + +# The HTML theme for the epub output. Since the default themes are not optimized +# for small screen space, using the same theme for HTML and epub output is +# usually not wise. This defaults to 'epub', a theme designed to save visual +# space. +#epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +#epub_tocscope = 'default' + +# Fix unsupported image types using the PIL. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..4638f71 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,29 @@ +libnacl: Python bindings to NaCl +================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + + topics/public + topics/sealed + topics/secret + topics/sign + topics/dual + topics/utils + topics/raw_public + topics/raw_sealed + topics/raw_secret + topics/raw_sign + topics/raw_hash + topics/raw_generichash + topics/releases/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/topics/dual.rst b/doc/topics/dual.rst new file mode 100644 index 0000000..59cd3dd --- /dev/null +++ b/doc/topics/dual.rst @@ -0,0 +1,87 @@ +=================== +Dual Key Management +=================== + +The libnacl library abstracts a "Dual Key" model. The Dual Key creates a single +key management object that can be used for both signing and encrypting, it +generates and maintains a Curve25519 encryption key pair and an ED25519 signing +keypair. All methods for encryption and signing work with and from Dual Keys. + +To encrypt messages using Dual Keys: + +.. code-block:: python + + import libnacl.dual + + # Define a message to send + msg = b"You've got two empty halves of coconut and you're bangin' 'em together." + + # Generate the key pairs for Alice and bob, if secret keys already exist + # they can be passed in, otherwise new keys will be automatically generated + bob = libnacl.dual.DualSecret() + alice = libnacl.dual.DualSecret() + + # Create the boxes, this is an object which represents the combination of the + # sender's secret key and the receiver's public key + bob_box = libnacl.public.Box(bob.sk, alice.pk) + alice_box = libnacl.public.Box(alice.sk, bob.pk) + + # Bob's box encrypts messages for Alice + bob_ctxt = bob_box.encrypt(msg) + # Alice's box decrypts messages from Bob + bclear = alice_box.decrypt(bob_ctxt) + # Alice can send encrypted messages which only Bob can decrypt + alice_ctxt = alice_box.encrypt(msg) + aclear = alice_box.decrypt(alice_ctxt) + +.. note:: + + Every encryption routine requires a nonce. The nonce is a 24 char string + that must never be used twice with the same keypair. If no nonce is passed + in then a nonce is generated based on random data. + If it is desired to generate a nonce manually this can be done by passing + it into the encrypt method. + +DualKey Object +============== + +The DualKey object is used to manage both public and secret keys, this object +contains a number of methods for both convenience and utility. The key data is +also available. + +Keys +---- + +The raw public key is available as DualKey.pk, to generate a hex encoded +version of the key the pk_hex method is available: + +.. code-block:: python + + import libnacl.dual + + fred = libnacl.dual.DualSecret() + + raw_sk = fred.sk + hex_sk = fred.hex_sk() + + raw_pk = fred.pk + hex_pk = fred.hex_pk() + +By saving only the binary keys in memory libnacl ensures that the minimal +memory footprint is needed. + +Saving Keys to Disk +=================== + +All libnacl key objects can be safely saved to disk via the save method. This +method changes the umask before saving the key file to ensure that the saved +file can only be read by the user creating it and cannot be written to. +When using dual keys the encrypting and signing keys will be saved togather in +a single file. + +.. code-block:: python + + import libnacl.dual + + fred = libnacl.dual.DualSecret() + fred.save('/etc/nacl/fred.key') diff --git a/doc/topics/public.rst b/doc/topics/public.rst new file mode 100644 index 0000000..eb2f1bb --- /dev/null +++ b/doc/topics/public.rst @@ -0,0 +1,104 @@ +===================== +Public Key Encryption +===================== + +Unlike traditional means for public key asymmetric encryption, the nacl +encryption systems are very high speed. The CurveCP network protocol for +instance only uses public key encryption for all transport. + +Public key encryption is very simple, as is evidenced with this communication +between Alice and Bob: + +.. code-block:: python + + import libnacl.public + + # Define a message to send + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + + # Generate the key pairs for Alice and bob, if secret keys already exist + # they can be passed in, otherwise new keys will be automatically generated + bob = libnacl.public.SecretKey() + alice = libnacl.public.SecretKey() + + # Create the boxes, this is an object which represents the combination of the + # sender's secret key and the receiver's public key + bob_box = libnacl.public.Box(bob.sk, alice.pk) + alice_box = libnacl.public.Box(alice.sk, bob.pk) + + # Bob's box encrypts messages for Alice + bob_ctxt = bob_box.encrypt(msg) + # Alice's box decrypts messages from Bob + bclear = alice_box.decrypt(bob_ctxt) + # Alice can send encrypted messages which only Bob can decrypt + alice_ctxt = alice_box.encrypt(msg) + aclear = bob_box.decrypt(alice_ctxt) + +.. note:: + + Every encryption routine requires a nonce. The nonce is a 24 char string + that must never be used twice with the same keypair. If no nonce is passed + in then a nonce is generated based on random data. + If it is desired to generate a nonce manually this can be done by passing + it into the encrypt method. + +.. _secretkey-object: + +SecretKey Object +================ + +The SecretKey object is used to manage both public and secret keys, this object +contains a number of methods for both convenience and utility. The key data is +also available. + +Keys +---- + +The raw public key is available as SecretKey.sk, to generate a hex encoded +version of the key the sk_hex method is available. The same items are +available for the public keys: + +.. code-block:: python + + import libnacl.public + + fred = libnacl.public.SecretKey() + + raw_sk = fred.sk + hex_sk = fred.hex_sk() + + raw_pk = fred.pk + hex_pk = fred.hex_pk() + +By saving only the binary keys in memory libnacl ensures that the minimal +memory footprint is needed. + +.. _publickey-object: + +PublicKey Object +================ + +To manage only the public key end, a public key object exists: + +.. code-block:: python + + import libnacl.public + + tom = libnacl.public.PublicKey(tom_public_key_hex) + + raw_pk = tom.pk + hex_pk = tom.hex_pk() + +Saving Keys to Disk +=================== + +All libnacl key objects can be safely saved to disk via the save method. This +method changes the umask before saving the key file to ensure that the saved +file can only be read by the user creating it and cannot be written to. + +.. code-block:: python + + import libnacl.public + + fred = libnacl.public.SecretKey() + fred.save('/etc/nacl/fred.key') diff --git a/doc/topics/raw_generichash.rst b/doc/topics/raw_generichash.rst new file mode 100644 index 0000000..ba42c6e --- /dev/null +++ b/doc/topics/raw_generichash.rst @@ -0,0 +1,31 @@ +==================================== +Raw Generic Hash (Blake2b) Functions +==================================== + +The nacl library comes with blake hashing libraries. + +More information on Blake can be found here: +https://blake2.net + +The blake2b hashing algorithm is a keyed hashing algorithm, which allows +for a key to be associated with a hash. Blake can be executed with or without +a key. + +With a key (they key can should be between 16 and 64 bytes): + +.. code-block:: python + + import libnacl + + msg = 'Is there someone else up there we could talk to?' + key = libnacl.randombytes(32) + h_msg = libnacl.crypto_generichash(msg, key) + +Without a key: + +.. code-block:: python + + import libnacl + + msg = 'Is there someone else up there we could talk to?' + h_msg = libnacl.crypto_generichash(msg) diff --git a/doc/topics/raw_hash.rst b/doc/topics/raw_hash.rst new file mode 100644 index 0000000..e161098 --- /dev/null +++ b/doc/topics/raw_hash.rst @@ -0,0 +1,23 @@ +================== +Raw Hash Functions +================== + +The nacl library comes with sha256 and sha512 hashing libraries. They do not +seem to offer any benefit over python's hashlib, but for completeness they are +included. Creating a hash of a message is very simple: + +.. code-block:: python + + import libnacl + + msg = 'Is there someone else up there we could talk to?' + h_msg = libnacl.crypto_hash(msg) + +crypto_hash defaults to sha256, sha512 is also available: + +.. code-block:: python + + import libnacl + + msg = 'Is there someone else up there we could talk to?' + h_msg = libnacl.crypto_hash_sha512(msg) diff --git a/doc/topics/raw_public.rst b/doc/topics/raw_public.rst new file mode 100644 index 0000000..9464dc1 --- /dev/null +++ b/doc/topics/raw_public.rst @@ -0,0 +1,74 @@ +========================= +Raw Public Key Encryption +========================= + +.. note:: + + While these routines are perfectly safe, higher level convenience + wrappers are under development to make these routines easier. + +Public key encryption inside the nacl library has been constructed to ensure +that all cryptographic routines are executed correctly and safely. + +The public key encryption is executed via the functions which begin with +`crypto_box` and can be easily executed. + +First generate a public key and secret key keypair for the two communicating +parties, who for tradition's sake, will be referred to as Alice and Bob: + +.. code-block:: python + + import libnacl + + alice_pk, alice_sk = libnacl.crypto_box_keypair() + bob_pk, bob_sk = libnacl.crypto_box_keypair() + +Once the keys have been generated a cryptographic box needs to be created. The +cryptographic box takes the party's secret key and the receiving party's public +key. These are used to create a message which is both signed and encrypted. + +Before creating the box a nonce is required. The nonce is a 24 character +string which should only be used for this message, the nonce should never be +reused. This means that the nonce needs to be generated in such a way that +the probability of reusing the nonce string with the same keypair is very +low. The libnacl wrapper ships with a convenience function which generates a +nonce from random bytes: + +.. code-block:: python + + import libnacl.utils + nonce = libnacl.utils.rand_nonce() + +Now, with a nonce a cryptographic box can be created, Alice will send a +message: + +.. code-block:: python + + msg = 'Quiet, quiet. Quiet! There are ways of telling whether she is a witch.' + box = libnacl.crypto_box(msg, nonce, bob_pk, alice_sk) + +Now with a box in hand it can be decrypted by Bob: + +.. code-block:: python + + clear_msg = libnacl.crypto_box_open(box, nonce, alice_pk, bob_sk) + +The trick here is that the box AND the nonce need to be sent to Bob, so he can +decrypt the message. The nonce can be safely sent to Bob in the clear. + +To bring it all together: + +.. code-block:: python + + import libnacl + import libnacl.utils + + alice_pk, alice_sk = libnacl.crypto_box_keypair() + bob_pk, bob_sk = libnacl.crypto_box_keypair() + + nonce = libnacl.utils.rand_nonce() + + msg = 'Quiet, quiet. Quiet! There are ways of telling whether she is a witch.' + box = libnacl.crypto_box(msg, nonce, bob_pk, alice_sk) + + clear_msg = libnacl.crypto_box_open(box, nonce, alice_pk, bob_sk) diff --git a/doc/topics/raw_sealed.rst b/doc/topics/raw_sealed.rst new file mode 100644 index 0000000..b2c890e --- /dev/null +++ b/doc/topics/raw_sealed.rst @@ -0,0 +1,41 @@ +========================= +Raw Sealed Box Encryption +========================= + +Sealed box is a variant of :doc:`public key encryption scheme </raw_public.rst>` +where the sender is not authenticated. This is done by generating an +ephemeral key pair, which the public key is prefixed to the cipher text. + +First, generate a keypair for the receiver. The sender doesn't need a keypair. + +.. code-block:: python + + import libnacl + + pk, sk = libnacl.crypto_box_keypair() + +Then a sealed box is created by the sender, using the receiver's public key + +.. code-block:: python + + msg = 'Quiet, quiet. Quiet! There are ways of telling whether she is a witch.' + box = libnacl.crypto_box_seal(msg, pk) + +The receiver then can decrypt the box using their keypair. + +.. code-block:: python + + clear_msg = libnacl.crypto_box_seal_open(box, pk, sk) + +To bring it all together: + +.. code-block:: python + + import libnacl + + pk, sk = libnacl.crypto_box_keypair() + + msg = 'Quiet, quiet. Quiet! There are ways of telling whether she is a witch.' + box = libnacl.crypto_box_seal(msg, pk) + + clear_msg = libnacl.crypto_box_seal_open(box, pk, sk) diff --git a/doc/topics/raw_secret.rst b/doc/topics/raw_secret.rst new file mode 100644 index 0000000..40f7ae8 --- /dev/null +++ b/doc/topics/raw_secret.rst @@ -0,0 +1,61 @@ +========================= +Raw Secret Key Encryption +========================= + +.. note:: + + While these routines are perfectly safe, higher level convenience + wrappers are under development to make these routines easier. + +Secret key encryption is high speed encryption based on a shared secret key. + +.. note:: + + The nacl library uses the salsa20 stream encryption cipher for secret key + encryption, more information about the salsa20 cipher can be found here: + http://cr.yp.to/salsa20.html + +The means of encryption assumes that the two sides of the conversation both +have access to the same shared secret key. First generate a secret key, libnacl +provides a convenience function for the generation of this key called +libnacl.utils.salsa_key, then generate a nonce, a new nonce should be used +every time a new message is encrypted. A convenience function to create a unique +nonce based on random bytes: + +.. code-block:: python + + import libnacl + import libnacl.utils + + key = libnacl.utils.salsa_key() + nonce = libnacl.utils.rand_nonce() + +With the key and nonce in hand, the cryptographic secret box can now be +generated: + +.. code-block:: python + + msg = 'Who are you who are so wise in the ways of science?' + box = libnacl.crypto_secretbox(msg, nonce, key) + +Now the message can be decrypted on the other end. The nonce and the key are +both required to decrypt: + +.. code-block:: python + + clear_msg = libnacl.crypto_secretbox_open(box, nonce, key) + +When placed all together the sequence looks like this: + +.. code-block:: python + + import libnacl + import libnacl.utils + + key = libnacl.utils.salsa_key() + nonce = libnacl.utils.rand_nonce() + + msg = 'Who are you who are so wise in the ways of science?' + box = libnacl.crypto_secretbox(msg, nonce, key) + + clear_msg = libnacl.crypto_secretbox_open(box, nonce, key) diff --git a/doc/topics/raw_sign.rst b/doc/topics/raw_sign.rst new file mode 100644 index 0000000..732b74b --- /dev/null +++ b/doc/topics/raw_sign.rst @@ -0,0 +1,57 @@ +====================== +Raw Message Signatures +====================== + +.. note:: + + While these routines are perfectly safe, higher level convenience + wrappers are under development to make these routines easier. + +Signing messages ensures that the message itself has not been tampered with. +The application of a signature to a message is something that is is +automatically applied when using the public key encryption and is not a +required step when sending encrypted messages. This document however is +intended to illustrate how to sign plain text messages. + +The nacl libs use a separate keypair for signing then is used for +public key encryption, it is a high performance key signing algorithm +called ed25519, more information on ed25519 can be found here: +http://ed25519.cr.yp.to/ + +The sign messages first generate a signing keypair, this constitutes the +signing key which needs to be kept secret, and the verify key which is +made available to message recipients. + +.. code-block:: python + + import libnacl + + vk, sk = libnacl.crypto_sign_keypair() + +With the signing keypair in hand a message can be signed: + +.. code-block:: python + + msg = 'And that, my liege, is how we know the Earth to be banana-shaped.' + signed = libnacl.crypto_sign(msg, sk) + +The signed message is really just the plain text of the message prepended with +the signature. The crypto_sign_open function will read the signed message +and return the original message without the signature: + +.. code-block:: python + + orig = libnacl.crypto_sign_open(signed, vk) + +Put all together: + +.. code-block:: python + + import libnacl + + vk, sk = libnacl.crypto_sign_keypair() + + msg = 'And that, my liege, is how we know the Earth to be banana-shaped.' + signed = libnacl.crypto_sign(msg, sk) + + orig = libnacl.crypto_sign_open(signed, vk) diff --git a/doc/topics/releases/1.0.0.rst b/doc/topics/releases/1.0.0.rst new file mode 100644 index 0000000..78b9013 --- /dev/null +++ b/doc/topics/releases/1.0.0.rst @@ -0,0 +1,13 @@ +=========================== +libnacl 1.0.0 Release Notes +=========================== + +This is the first stable release of libnacl, the python bindings for Daniel J. +Bernstein's nacl library via libsodium. + +NaCl Base Functions +=================== + +This release features direct access to the underlying functions from nacl +exposed via importing libnacl. These functions are fully documented and can +be safely used directly. diff --git a/doc/topics/releases/1.1.0.rst b/doc/topics/releases/1.1.0.rst new file mode 100644 index 0000000..507a2b9 --- /dev/null +++ b/doc/topics/releases/1.1.0.rst @@ -0,0 +1,21 @@ +=========================== +libnacl 1.1.0 Release Notes +=========================== + +This release introduces the addition of high level classes that make using +NaCl even easier. + +High level NaCl +=============== + +The addition of the high level classes give a more pythonic abstraction to +using the underlying NaCl cryptography. + +These classes can be found in libnacl.public, libnacl.sign and libnacl.secret. + +Easy Nonce Generation +===================== + +The new classes will automatically generate a nonce value per encrypted +message. The default nonce which is generated can be found in +`libnacl.utils.time_nonce`. diff --git a/doc/topics/releases/1.2.0.rst b/doc/topics/releases/1.2.0.rst new file mode 100644 index 0000000..d868dee --- /dev/null +++ b/doc/topics/releases/1.2.0.rst @@ -0,0 +1,22 @@ +=========================== +libnacl 1.2.0 Release Notes +=========================== + +This release introduces the DualKey class, secure key saving and loading, as +well as enhancements to the time_nonce function. + +Dual Key Class +============== + +Dual Keys are classes which can encrypt and sign data. These classes generate +and maintain both Curve25519 and Ed25519 keys, as well as all methods for both +encryption and signing. + +Time Nonce Improvements +======================= + +The original time nonce routine used the first 20 chars of the 24 char nonce +for the microsecond timestamp (based on salt's jid), leaving 4 chars for random +data. This new nonce uses far fewer chars for the timestamp by hex encoding the +float of microseconds into just 13 chars, leaving 11 chars of random data. This +makes the default nonce safer and more secure. diff --git a/doc/topics/releases/1.3.0.rst b/doc/topics/releases/1.3.0.rst new file mode 100644 index 0000000..e20f181 --- /dev/null +++ b/doc/topics/releases/1.3.0.rst @@ -0,0 +1,6 @@ +=========================== +libnacl 1.3.0 Release Notes +=========================== + +This release removes the time_nonce function and replaces it with the +rand_nonce function. diff --git a/doc/topics/releases/1.3.1.rst b/doc/topics/releases/1.3.1.rst new file mode 100644 index 0000000..1c131ea --- /dev/null +++ b/doc/topics/releases/1.3.1.rst @@ -0,0 +1,5 @@ +=========================== +libnacl 1.3.1 Release Notes +=========================== + +Bring back a safe time_nonce function. diff --git a/doc/topics/releases/1.3.2.rst b/doc/topics/releases/1.3.2.rst new file mode 100644 index 0000000..a15f087 --- /dev/null +++ b/doc/topics/releases/1.3.2.rst @@ -0,0 +1,5 @@ +=========================== +libnacl 1.3.2 Release Notes +=========================== + +Add detection of the libsodium.so.10 lib created by libsodium 0.6 diff --git a/doc/topics/releases/1.3.3.rst b/doc/topics/releases/1.3.3.rst new file mode 100644 index 0000000..80a38c6 --- /dev/null +++ b/doc/topics/releases/1.3.3.rst @@ -0,0 +1,6 @@ +=========================== +libnacl 1.3.3 Release Notes +=========================== + +Fix issue and add tests for bug where saving and loading a signing key caused +a stack trace, se issue #18 diff --git a/doc/topics/releases/1.3.4.rst b/doc/topics/releases/1.3.4.rst new file mode 100644 index 0000000..b51b932 --- /dev/null +++ b/doc/topics/releases/1.3.4.rst @@ -0,0 +1,7 @@ +=========================== +libnacl 1.3.4 Release Notes +=========================== + +* Change the default ctype values to be more accurate and efficient +* Update soname detection on Linux for libsodium 0.7.0 +* Make soname detection a little more future proof diff --git a/doc/topics/releases/1.4.0.rst b/doc/topics/releases/1.4.0.rst new file mode 100644 index 0000000..64ac4a2 --- /dev/null +++ b/doc/topics/releases/1.4.0.rst @@ -0,0 +1,14 @@ +=========================== +libnacl 1.4.0 Release Notes +=========================== + +Blake Hash Support +================== + +Initial support has been added for the blake2b hash algorithm + +Misc Fixes +========== + +* Fix issue with keyfile saves on windows +* Fix libsodium detection for Ubuntu manual installs and Windows dll detection diff --git a/doc/topics/releases/1.4.1.rst b/doc/topics/releases/1.4.1.rst new file mode 100644 index 0000000..dd7c8c3 --- /dev/null +++ b/doc/topics/releases/1.4.1.rst @@ -0,0 +1,9 @@ +=========================== +libnacl 1.4.1 Release Notes +=========================== + +Misc Fixes +========== + +* Fix for crypto_auth_verify and crypto_auth_onetimeverify +* Lint fixes and updates diff --git a/doc/topics/releases/1.4.2.rst b/doc/topics/releases/1.4.2.rst new file mode 100644 index 0000000..31d6856 --- /dev/null +++ b/doc/topics/releases/1.4.2.rst @@ -0,0 +1,8 @@ +=========================== +libnacl 1.4.2 Release Notes +=========================== + +SecretBox key save and load +=========================== + +* Add support to save and load SecretBox keys diff --git a/doc/topics/releases/1.4.3.rst b/doc/topics/releases/1.4.3.rst new file mode 100644 index 0000000..72b47c6 --- /dev/null +++ b/doc/topics/releases/1.4.3.rst @@ -0,0 +1,17 @@ +=========================== +libnacl 1.4.3 Release Notes +=========================== + +crypto_onetimeauth_verify fixes +=============================== + +* Fix a call to the crypto_onetimeauth_verify routine into the right libsodium system +* Add tests for crypto_onetimeauth_verify + +Improved support for MacOSX +=========================== + +* Improved the lookup procedure for finding libsodium on MacOSX + +Add support for reading file streams for key loading +==================================================== diff --git a/doc/topics/releases/1.4.4.rst b/doc/topics/releases/1.4.4.rst new file mode 100644 index 0000000..5917d5a --- /dev/null +++ b/doc/topics/releases/1.4.4.rst @@ -0,0 +1,30 @@ +=========================== +libnacl 1.4.4 Release Notes +=========================== + +Add pack_nonce options to secretbox +=================================== + +* libnacl secretbox has been packing the nonce in each message, the new pack_nonce + option allows for the nonce to be omitted which allows for more flexible options + +Add soversion 17 detection +========================== + +* Added explicit soversion support for libsodium 17 + +Fix crypto_onetimeauth tests +============================ + +* The crypto onetimeauth test issues have been resolved + +Remove tweetnacl Support +======================== + +* The tweetnacl support was never really tested, and since the tweetnacl api + is not complete we have removed support for it + +Add sodium_init calls +===================== + +* Added calls to sodium_init when the lib is loaded diff --git a/doc/topics/releases/1.4.5.rst b/doc/topics/releases/1.4.5.rst new file mode 100644 index 0000000..2ad540f --- /dev/null +++ b/doc/topics/releases/1.4.5.rst @@ -0,0 +1,14 @@ +=========================== +libnacl 1.4.5 Release Notes +=========================== + +Set low end libsodium version to 0.5 +==================================== + +* libnacl will only function with libsodium 0.5 and above + +Add soversion 18 detection +========================== + +* Added explicit soversion support for libsodium 18 + diff --git a/doc/topics/releases/1.5.0.rst b/doc/topics/releases/1.5.0.rst new file mode 100644 index 0000000..301837a --- /dev/null +++ b/doc/topics/releases/1.5.0.rst @@ -0,0 +1,19 @@ +=========================== +libnacl 1.5.0 Release Notes +=========================== + +Add Built In libsodium.so Support +================================= + +Added the ability to place a `libsodium.so` file in the libnacl +python directory as a last resort fallback. To use this feature +just copy your `libsodium.so` file to the same directory as the libnacl +`__init__.py` file. + +This was added to make total portability of the library easier. + +Add `bytes_eq` +============== + +Added the `bytes_eq` function to allow better byte comparison + diff --git a/doc/topics/releases/1.5.1.rst b/doc/topics/releases/1.5.1.rst new file mode 100644 index 0000000..454fbe3 --- /dev/null +++ b/doc/topics/releases/1.5.1.rst @@ -0,0 +1,17 @@ +=========================== +libnacl 1.5.1 Release Notes +=========================== + +Add Sealed Box Support +====================== + +A big thanks to Manatsawin Hanmongkolchai for adding in support for libsodium +`Sealed Boxes`! + +Change Exception on Encrypt/Decrypt Failure +=========================================== + +If encryption or decryption fails, `CryptError` is raised instead of +`ValueError`. This might be a breaking change for your application. See +`#91 <https://github.com/saltstack/libnacl/issues/91>`__ and +`#74 <https://github.com/saltstack/libnacl/issues/74>`__. diff --git a/doc/topics/releases/1.5.2.rst b/doc/topics/releases/1.5.2.rst new file mode 100644 index 0000000..6e2c142 --- /dev/null +++ b/doc/topics/releases/1.5.2.rst @@ -0,0 +1,11 @@ +=========================== +libnacl 1.5.2 Release Notes +=========================== + +Add Support for AEAD AES and chacha20poly1305 +============================================= + +Big thanks to Nicholas O'Brien for adding support for libsodium's AEAD +encryption systems. The raw functions are all available to access +libsodium directly along with the high level AEAD class that cleanly +follows libnacl's key management model. diff --git a/doc/topics/releases/1.6.0.rst b/doc/topics/releases/1.6.0.rst new file mode 100644 index 0000000..e6f35cd --- /dev/null +++ b/doc/topics/releases/1.6.0.rst @@ -0,0 +1,21 @@ +=========================== +libnacl 1.6.0 Release Notes +=========================== + +Add Bindings for More Libsodium Function +======================================== + +Add bindings for crypto_box_seed_keypair() and crypto_scalarmult_base() +Add bindings for crypto_box_easy() and crypto_box_open_easy() +Add bindings for crypto_box_easy_afternm() and crypto_box_open_easy_afternm() +Add bindings for crypto_sign_ed25519_keypair(), crypto_sign_ed25519_sk_to_pk() and crypto_sign_ed25519_sk_to_seed() +Add bindings for crypto_sign_detached() and crypto_sign_verify_detached() +Add bindings for crypto_sign_ed25519_pk_to_curve25519() and crypto_sign_ed25519_sk_to_curve25519() + +Please Note The Exception Change From the 1.5.1 Release +======================================================= + +If encryption or decryption fails, `CryptError` is raised instead of +`ValueError`. This might be a breaking change for your application. See +`#91 <https://github.com/saltstack/libnacl/issues/91>`__ and +`#74 <https://github.com/saltstack/libnacl/issues/74>`__. diff --git a/doc/topics/releases/1.6.1.rst b/doc/topics/releases/1.6.1.rst new file mode 100644 index 0000000..9ae5a43 --- /dev/null +++ b/doc/topics/releases/1.6.1.rst @@ -0,0 +1,8 @@ +=========================== +libnacl 1.6.1 Release Notes +=========================== + +Add support for libsodium 1.0.15 +================================ + +Make sure that libnacl runs correctly on the 1.0.15 release of libsodium diff --git a/doc/topics/releases/1.7.1.rst b/doc/topics/releases/1.7.1.rst new file mode 100644 index 0000000..2941198 --- /dev/null +++ b/doc/topics/releases/1.7.1.rst @@ -0,0 +1,21 @@ +=========================== +libnacl 1.7.1 Release Notes +=========================== + +This release fixes a few minor bugs, primarily in tests, and restores +functionality with older versions of libsodium. + +Compatibility With Older libsodium +================================== + +PR #118 fixes compatibility with Debian 8. + +Test Fixes +========== + +Some unreliability in tests were found by the Debian team. These +issues were fixed in PRs #115 and #116. + +Travis no longer supports the same pypy tests on all platforms, +these were removed in PR #119. + diff --git a/doc/topics/releases/1.7.rst b/doc/topics/releases/1.7.rst new file mode 100644 index 0000000..5ee5549 --- /dev/null +++ b/doc/topics/releases/1.7.rst @@ -0,0 +1,18 @@ +========================= +libnacl 1.7 Release Notes +========================= + +Bindings for kdf in libsodium +============================= + +Thanks to Michael Mendoza, PR #109 + +Added extra key validation +========================== + +Thanks to Kent Ross, PR #106 + +Add Crypto_box_easy +=================== + +Thanks to jheling PR #114 diff --git a/doc/topics/releases/index.rst b/doc/topics/releases/index.rst new file mode 100644 index 0000000..d664a47 --- /dev/null +++ b/doc/topics/releases/index.rst @@ -0,0 +1,9 @@ +============= +Release notes +============= + +.. toctree:: + :maxdepth: 1 + :glob: + + [0-9]* diff --git a/doc/topics/sealed.rst b/doc/topics/sealed.rst new file mode 100644 index 0000000..23d28c6 --- /dev/null +++ b/doc/topics/sealed.rst @@ -0,0 +1,38 @@ +===================== +Sealed Box +===================== + +Sealed box is a variant of :doc:`public key encryption scheme </topics/public>` +which only the receiver's public key is required. As such, the sender of the +message cannot be cryptographically authenticated. + +.. code-block:: python + + import libnacl.sealed + import libnacl.public + + # Define a message to send + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + + # Generate the key pair + keypair = libnacl.public.SecretKey() + + # Create the box + box = libnacl.sealed.SealedBox(keypair) + + # Encrypt messages + ctxt = box.encrypt(msg) + # Decrypt messages + bclear = box.decrypt(ctxt) + +Creating Box +====================== +SealedBox instances can be created by supplying a public and private key. The +private key is only required when decrypting. + +The public key can be supplied as: + +* Instance of :ref:`SecretKey <secretkey-object>`, which supply both the public + and private key. +* Instance of :ref:`PublicKey <publickey-object>` +* Raw binary representation diff --git a/doc/topics/secret.rst b/doc/topics/secret.rst new file mode 100644 index 0000000..3690704 --- /dev/null +++ b/doc/topics/secret.rst @@ -0,0 +1,34 @@ +===================== +Secret Key Encryption +===================== + +Secret key encryption is the method of using a single key for both encryption +and decryption of messages. One of the classic examples from history of secret +key, or symmetric, encryption is the Enigma machine. + +The SecretBox class in libnacl.secret makes this type of encryption very easy +to execute: + +.. code-block:: python + + msg = b'But then of course African swallows are not migratory.' + # Create a SecretBox object, if not passed in the secret key is + # Generated purely from random data + box = libnacl.secret.SecretBox() + # Messages can now be safely encrypted + ctxt = box.encrypt(msg) + # An additional box can be created from the original box secret key + box2 = libnacl.secret.SecretBox(box.sk) + # Messages can now be easily encrypted and decrypted + clear1 = box.decrypt(ctxt) + clear2 = box2.decrypt(ctxt) + ctxt2 = box2.encrypt(msg) + clear3 = box.decrypt(ctxt2) + +.. note:: + + Every encryption routine requires a nonce. The nonce is a 24 char string + that must never be used twice with the same keypair. If no nonce is passed + in then a nonce is generated based on random data. + If it is desired to generate a nonce manually this can be done by passing + it into the encrypt method. diff --git a/doc/topics/sign.rst b/doc/topics/sign.rst new file mode 100644 index 0000000..3b38407 --- /dev/null +++ b/doc/topics/sign.rst @@ -0,0 +1,43 @@ +============================== +Signing and Verifying Messages +============================== + +The nacl libs have the capability to sign and verify messages. Please be +advised that public key encrypted messages do not need to be signed, the +nacl box construct verifies the validity of the sender. + +To sign and verify messages use the Signer and Verifier classes: + +.. code-block:: python + + import libnacl.sign + + msg = (b'Well, that\'s no ordinary rabbit. That\'s the most foul, ' + b'cruel, and bad-tempered rodent you ever set eyes on.') + # Create a Signer Object, if the key seed value is not passed in the + # signing keys will be automatically generated + signer = libnacl.sign.Signer() + # Sign the message, the signed string is the message itself plus the + # signature + signed = signer.sign(msg) + # If only the signature is desired without the message: + signature = signer.signature(msg) + # To create a verifier pass in the verify key: + veri = libnacl.sign.Verifier(signer.hex_vk()) + # Verify the message! + verified = veri.verify(signed) + verified2 = veri.verify(signature + msg) + +Saving Keys to Disk +=================== + +All libnacl key objects can be safely saved to disk via the save method. This +method changes the umask before saving the key file to ensure that the saved +file can only be read by the user creating it and cannot be written to. + +.. code-block:: python + + import libnacl.sign + + signer = libnacl.sign.Signer() + signer.save('/etc/nacl/signer.key') diff --git a/doc/topics/utils.rst b/doc/topics/utils.rst new file mode 100644 index 0000000..a54eb07 --- /dev/null +++ b/doc/topics/utils.rst @@ -0,0 +1,50 @@ +================= +Utility Functions +================= + +The libnacl system comes with a number of utility functions, these functions +are made available to make some of the aspects of encryption and key management +easier. These range from nonce generation to loading saved keys. + +Loading Saved Keys +================== + +After keys are saved using the key save method reloading the keys is easy. The +`libnacl.utils.load_key` function will detect what type of key object saved +said key and then create the object from the key and return it. + +.. code-block:: python + + import libnacl.utils + + key_obj = libnacl.utils.load_key('/etc/keys/bob.key') + +The load_key and save routines also support inline key serialization. The +default is json but msgpack is also supported. + +Salsa Key +========= + +A simple function that will return a random byte string suitable for use in +SecretKey encryption. + +.. code-block:: python + + import libnacl.utils + + key = libnacl.utils.salsa_key() + +This routine is only required with the raw encryption functions, as the +`libnacl.secret.SecretBox` will generate the key automatically. + +Nonce Routines +============== + +A few functions are available to help with creating nonce values, these +routines are available because there is some debate about what the best approach +is. + +We recommend a pure random string for the nonce which is returned from +`rand_nonce`, but some have expressed a desire to create nonces which are +designed to avoid re-use by more than simply random data and therefore +the `time_nonce` function is also available. diff --git a/libnacl.egg-info/PKG-INFO b/libnacl.egg-info/PKG-INFO new file mode 100644 index 0000000..5b4fd06 --- /dev/null +++ b/libnacl.egg-info/PKG-INFO @@ -0,0 +1,25 @@ +Metadata-Version: 2.1 +Name: libnacl +Version: 1.8.0 +Summary: Python bindings for libsodium based on ctypes +Home-page: https://libnacl.readthedocs.org/ +Author: Thomas S Hatch +Author-email: thatch@saltstack.com +License: UNKNOWN +Platform: UNKNOWN +Classifier: Operating System :: OS Independent +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Topic :: Security :: Cryptography +License-File: LICENSE +License-File: AUTHORS + +UNKNOWN + diff --git a/libnacl.egg-info/SOURCES.txt b/libnacl.egg-info/SOURCES.txt new file mode 100644 index 0000000..2b20d14 --- /dev/null +++ b/libnacl.egg-info/SOURCES.txt @@ -0,0 +1,87 @@ +AUTHORS +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +doc/Makefile +doc/conf.py +doc/index.rst +doc/topics/dual.rst +doc/topics/public.rst +doc/topics/raw_generichash.rst +doc/topics/raw_hash.rst +doc/topics/raw_public.rst +doc/topics/raw_sealed.rst +doc/topics/raw_secret.rst +doc/topics/raw_sign.rst +doc/topics/sealed.rst +doc/topics/secret.rst +doc/topics/sign.rst +doc/topics/utils.rst +doc/topics/releases/1.0.0.rst +doc/topics/releases/1.1.0.rst +doc/topics/releases/1.2.0.rst +doc/topics/releases/1.3.0.rst +doc/topics/releases/1.3.1.rst +doc/topics/releases/1.3.2.rst +doc/topics/releases/1.3.3.rst +doc/topics/releases/1.3.4.rst +doc/topics/releases/1.4.0.rst +doc/topics/releases/1.4.1.rst +doc/topics/releases/1.4.2.rst +doc/topics/releases/1.4.3.rst +doc/topics/releases/1.4.4.rst +doc/topics/releases/1.4.5.rst +doc/topics/releases/1.5.0.rst +doc/topics/releases/1.5.1.rst +doc/topics/releases/1.5.2.rst +doc/topics/releases/1.6.0.rst +doc/topics/releases/1.6.1.rst +doc/topics/releases/1.7.1.rst +doc/topics/releases/1.7.rst +doc/topics/releases/index.rst +libnacl/__init__.py +libnacl/aead.py +libnacl/base.py +libnacl/blake.py +libnacl/dual.py +libnacl/encode.py +libnacl/public.py +libnacl/sealed.py +libnacl/secret.py +libnacl/secret_easy.py +libnacl/sign.py +libnacl/utils.py +libnacl/version.py +libnacl.egg-info/PKG-INFO +libnacl.egg-info/SOURCES.txt +libnacl.egg-info/dependency_links.txt +libnacl.egg-info/top_level.txt +pkg/rpm/python-libnacl.spec +pkg/suse/python-libnacl.changes +pkg/suse/python-libnacl.spec +tests/runtests.py +tests/unit/__init__.py +tests/unit/test_aead.py +tests/unit/test_auth_verify.py +tests/unit/test_blake.py +tests/unit/test_dual.py +tests/unit/test_public.py +tests/unit/test_raw_auth_sym.py +tests/unit/test_raw_auth_sym_easy.py +tests/unit/test_raw_generichash.py +tests/unit/test_raw_hash.py +tests/unit/test_raw_public.py +tests/unit/test_raw_random.py +tests/unit/test_raw_secret.py +tests/unit/test_raw_secret_easy.py +tests/unit/test_raw_sign.py +tests/unit/test_save.py +tests/unit/test_seal.py +tests/unit/test_secret.py +tests/unit/test_secret_easy.py +tests/unit/test_sign.py +tests/unit/test_stream.py +tests/unit/test_verify.py +tests/unit/test_version.py
\ No newline at end of file diff --git a/libnacl.egg-info/dependency_links.txt b/libnacl.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/libnacl.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/libnacl.egg-info/top_level.txt b/libnacl.egg-info/top_level.txt new file mode 100644 index 0000000..d238ddf --- /dev/null +++ b/libnacl.egg-info/top_level.txt @@ -0,0 +1 @@ +libnacl diff --git a/libnacl/__init__.py b/libnacl/__init__.py new file mode 100644 index 0000000..7649013 --- /dev/null +++ b/libnacl/__init__.py @@ -0,0 +1,1295 @@ +# -*- coding: utf-8 -*- +''' +Wrap libsodium routines +''' +# pylint: disable=C0103 +# Import python libs +import ctypes +import sys +import os + +__SONAMES = (23, 18, 17, 13, 10, 5, 4) + + +def _get_nacl(): + ''' + Locate the nacl c libs to use + ''' + # Import libsodium + if sys.platform.startswith('win'): + try: + return ctypes.cdll.LoadLibrary('libsodium') + except OSError: + pass + for soname_ver in __SONAMES: + try: + return ctypes.cdll.LoadLibrary( + 'libsodium-{0}'.format(soname_ver) + ) + except OSError: + pass + msg = 'Could not locate nacl lib, searched for libsodium' + raise OSError(msg) + elif sys.platform.startswith('darwin'): + try: + return ctypes.cdll.LoadLibrary('libsodium.dylib') + except OSError: + pass + try: + libidx = __file__.find('lib') + if libidx > 0: + libpath = __file__[0:libidx+3] + '/libsodium.dylib' + return ctypes.cdll.LoadLibrary(libpath) + except OSError: + msg = 'Could not locate nacl lib, searched for libsodium' + raise OSError(msg) + else: + try: + return ctypes.cdll.LoadLibrary('libsodium.so') + except OSError: + pass + try: + return ctypes.cdll.LoadLibrary('/usr/local/lib/libsodium.so') + except OSError: + pass + try: + libidx = __file__.find('lib') + if libidx > 0: + libpath = __file__[0:libidx+3] + '/libsodium.so' + return ctypes.cdll.LoadLibrary(libpath) + except OSError: + pass + + for soname_ver in __SONAMES: + try: + return ctypes.cdll.LoadLibrary( + 'libsodium.so.{0}'.format(soname_ver) + ) + except OSError: + pass + try: + # fall back to shipped libsodium, trust os version first + libpath = os.path.join(os.path.dirname(__file__), 'libsodium.so') + return ctypes.cdll.LoadLibrary(libpath) + except OSError: + pass + msg = 'Could not locate nacl lib, searched for libsodium.so, ' + for soname_ver in __SONAMES: + msg += 'libsodium.so.{0}, '.format(soname_ver) + raise OSError(msg) + +# Don't load libnacl if we are in sphinx +if not 'sphinx' in sys.argv[0]: + nacl = _get_nacl() + DOC_RUN = False +else: + nacl = None + DOC_RUN = True + + +# Define exceptions +class CryptError(Exception): + """ + Base Exception for cryptographic errors + """ + +if not DOC_RUN: + sodium_init = nacl.sodium_init + sodium_init.res_type = ctypes.c_int + if sodium_init() < 0: + raise RuntimeError('sodium_init() call failed!') + + # Define constants + try: + crypto_box_SEALBYTES = nacl.crypto_box_sealbytes() + HAS_SEAL = True + except AttributeError: + HAS_SEAL = False + try: + crypto_aead_aes256gcm_KEYBYTES = nacl.crypto_aead_aes256gcm_keybytes() + crypto_aead_aes256gcm_NPUBBYTES = nacl.crypto_aead_aes256gcm_npubbytes() + crypto_aead_aes256gcm_ABYTES = nacl.crypto_aead_aes256gcm_abytes() + HAS_AEAD_AES256GCM = bool(nacl.crypto_aead_aes256gcm_is_available()) + crypto_aead_chacha20poly1305_ietf_KEYBYTES = nacl.crypto_aead_chacha20poly1305_ietf_keybytes() + crypto_aead_chacha20poly1305_ietf_NPUBBYTES = nacl.crypto_aead_chacha20poly1305_ietf_npubbytes() + crypto_aead_chacha20poly1305_ietf_ABYTES = nacl.crypto_aead_chacha20poly1305_ietf_abytes() + HAS_AEAD_CHACHA20POLY1305_IETF = True + HAS_AEAD = True + except AttributeError: + HAS_AEAD_AES256GCM = False + HAS_AEAD_CHACHA20POLY1305_IETF = False + HAS_AEAD = False + + crypto_box_SECRETKEYBYTES = nacl.crypto_box_secretkeybytes() + crypto_box_SEEDBYTES = nacl.crypto_box_seedbytes() + crypto_box_PUBLICKEYBYTES = nacl.crypto_box_publickeybytes() + crypto_box_NONCEBYTES = nacl.crypto_box_noncebytes() + crypto_box_ZEROBYTES = nacl.crypto_box_zerobytes() + crypto_box_BOXZEROBYTES = nacl.crypto_box_boxzerobytes() + crypto_box_BEFORENMBYTES = nacl.crypto_box_beforenmbytes() + crypto_scalarmult_BYTES = nacl.crypto_scalarmult_bytes() + crypto_scalarmult_SCALARBYTES = nacl.crypto_scalarmult_scalarbytes() + crypto_sign_BYTES = nacl.crypto_sign_bytes() + crypto_sign_SEEDBYTES = nacl.crypto_sign_secretkeybytes() // 2 + crypto_sign_PUBLICKEYBYTES = nacl.crypto_sign_publickeybytes() + crypto_sign_SECRETKEYBYTES = nacl.crypto_sign_secretkeybytes() + crypto_sign_ed25519_PUBLICKEYBYTES = nacl.crypto_sign_ed25519_publickeybytes() + crypto_sign_ed25519_SECRETKEYBYTES = nacl.crypto_sign_ed25519_secretkeybytes() + crypto_box_MACBYTES = crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES + crypto_secretbox_KEYBYTES = nacl.crypto_secretbox_keybytes() + crypto_secretbox_NONCEBYTES = nacl.crypto_secretbox_noncebytes() + crypto_secretbox_ZEROBYTES = nacl.crypto_secretbox_zerobytes() + crypto_secretbox_BOXZEROBYTES = nacl.crypto_secretbox_boxzerobytes() + crypto_secretbox_MACBYTES = crypto_secretbox_ZEROBYTES - crypto_secretbox_BOXZEROBYTES + crypto_stream_KEYBYTES = nacl.crypto_stream_keybytes() + crypto_stream_NONCEBYTES = nacl.crypto_stream_noncebytes() + crypto_auth_BYTES = nacl.crypto_auth_bytes() + crypto_auth_KEYBYTES = nacl.crypto_auth_keybytes() + crypto_onetimeauth_BYTES = nacl.crypto_onetimeauth_bytes() + crypto_onetimeauth_KEYBYTES = nacl.crypto_onetimeauth_keybytes() + crypto_generichash_BYTES = nacl.crypto_generichash_bytes() + crypto_generichash_BYTES_MIN = nacl.crypto_generichash_bytes_min() + crypto_generichash_BYTES_MAX = nacl.crypto_generichash_bytes_max() + crypto_generichash_KEYBYTES = nacl.crypto_generichash_keybytes() + crypto_generichash_KEYBYTES_MIN = nacl.crypto_generichash_keybytes_min() + crypto_generichash_KEYBYTES_MAX = nacl.crypto_generichash_keybytes_max() + crypto_scalarmult_curve25519_BYTES = nacl.crypto_scalarmult_curve25519_bytes() + crypto_hash_BYTES = nacl.crypto_hash_sha512_bytes() + crypto_hash_sha256_BYTES = nacl.crypto_hash_sha256_bytes() + crypto_hash_sha512_BYTES = nacl.crypto_hash_sha512_bytes() + crypto_verify_16_BYTES = nacl.crypto_verify_16_bytes() + crypto_verify_32_BYTES = nacl.crypto_verify_32_bytes() + crypto_verify_64_BYTES = nacl.crypto_verify_64_bytes() + + try: + randombytes_SEEDBYTES = nacl.randombytes_seedbytes() + HAS_RAND_SEED = True + except AttributeError: + HAS_RAND_SEED = False + + try: + crypto_kdf_PRIMITIVE = nacl.crypto_kdf_primitive() + crypto_kdf_BYTES_MIN = nacl.crypto_kdf_bytes_min() + crypto_kdf_BYTES_MAX = nacl.crypto_kdf_bytes_max() + crypto_kdf_CONTEXTBYTES = nacl.crypto_kdf_contextbytes() + crypto_kdf_KEYBYTES = nacl.crypto_kdf_keybytes() + HAS_CRYPT_KDF = True + except AttributeError: + HAS_CRYPT_KDF = False + + try: + crypto_kx_PUBLICKEYBYTES = nacl.crypto_kx_publickeybytes() + crypto_kx_SECRETKEYBYTES = nacl.crypto_kx_secretkeybytes() + crypto_kx_SEEDBYTES = nacl.crypto_kx_seedbytes() + crypto_kx_SESSIONKEYBYTES = nacl.crypto_kx_sessionkeybytes() + crypto_kx_PRIMITIVE = nacl.crypto_kx_primitive() + HAS_CRYPT_KX = True + except AttributeError: + HAS_CRYPT_KX = False + + # pylint: enable=C0103 + +# Pubkey defs + + +def crypto_box_keypair(): + ''' + Generate and return a new keypair + + pk, sk = nacl.crypto_box_keypair() + ''' + pk = ctypes.create_string_buffer(crypto_box_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_box_SECRETKEYBYTES) + nacl.crypto_box_keypair(pk, sk) + return pk.raw, sk.raw + + +def crypto_box_seed_keypair(seed): + ''' + Generate and return a keypair from a key seed + ''' + if len(seed) != crypto_box_SEEDBYTES: + raise ValueError('Invalid key seed') + pk = ctypes.create_string_buffer(crypto_box_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_box_SECRETKEYBYTES) + nacl.crypto_box_seed_keypair(pk, sk, seed) + return pk.raw, sk.raw + + +def crypto_scalarmult_base(sk): + ''' + Compute and return the scalar product of a standard group element and the given integer. + + This can be used to derive a Curve25519 public key from a Curve25519 secret key, + such as for usage with crypto_box and crypto_box_seal. + ''' + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + pk = ctypes.create_string_buffer(crypto_box_PUBLICKEYBYTES) + if nacl.crypto_scalarmult_base(pk, sk): + raise CryptError('Failed to compute scalar product') + return pk.raw + + +def crypto_box(msg, nonce, pk, sk): + ''' + Using a public key and a secret key encrypt the given message. A nonce + must also be passed in, never reuse the nonce + + enc_msg = nacl.crypto_box('secret message', <unique nonce>, <public key string>, <secret key string>) + ''' + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + pad = b'\x00' * crypto_box_ZEROBYTES + msg + c = ctypes.create_string_buffer(len(pad)) + ret = nacl.crypto_box(c, pad, ctypes.c_ulonglong(len(pad)), nonce, pk, sk) + if ret: + raise CryptError('Unable to encrypt message') + return c.raw[crypto_box_BOXZEROBYTES:] + + +def crypto_box_open(ctxt, nonce, pk, sk): + ''' + Decrypts a message given the receiver's private key, and sender's public key + ''' + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + pad = b'\x00' * crypto_box_BOXZEROBYTES + ctxt + msg = ctypes.create_string_buffer(len(pad)) + ret = nacl.crypto_box_open( + msg, + pad, + ctypes.c_ulonglong(len(pad)), + nonce, + pk, + sk) + if ret: + raise CryptError('Unable to decrypt ciphertext') + return msg.raw[crypto_box_ZEROBYTES:] + + +def crypto_box_easy(msg, nonce, pk, sk): + ''' + Using a public key and a secret key encrypt the given message. A nonce + must also be passed in, never reuse the nonce + + enc_msg = nacl.crypto_box_easy('secret message', <unique nonce>, <public key string>, <secret key string>) + ''' + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + c = ctypes.create_string_buffer(len(msg) + crypto_box_MACBYTES) + ret = nacl.crypto_box(c, msg, ctypes.c_ulonglong(len(msg)), nonce, pk, sk) + if ret: + raise CryptError('Unable to encrypt message') + return c.raw + + +def crypto_box_open_easy(ctxt, nonce, pk, sk): + ''' + Decrypts a message given the receiver's private key, and sender's public key + ''' + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + msg = ctypes.create_string_buffer(len(ctxt) - crypto_box_MACBYTES) + ret = nacl.crypto_box_open( + msg, + ctxt, + ctypes.c_ulonglong(len(ctxt)), + nonce, + pk, + sk) + if ret: + raise CryptError('Unable to decrypt ciphertext') + return msg.raw[crypto_box_ZEROBYTES:] + + +def crypto_box_beforenm(pk, sk): + ''' + Partially performs the computation required for both encryption and decryption of data + ''' + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + k = ctypes.create_string_buffer(crypto_box_BEFORENMBYTES) + ret = nacl.crypto_box_beforenm(k, pk, sk) + if ret: + raise CryptError('Unable to compute shared key') + return k.raw + + +def crypto_box_afternm(msg, nonce, k): + ''' + Encrypts a given a message, using partial computed data + ''' + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError('Invalid shared key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + pad = b'\x00' * crypto_box_ZEROBYTES + msg + ctxt = ctypes.create_string_buffer(len(pad)) + ret = nacl.crypto_box_afternm(ctxt, pad, ctypes.c_ulonglong(len(pad)), nonce, k) + if ret: + raise CryptError('Unable to encrypt messsage') + return ctxt.raw[crypto_box_BOXZEROBYTES:] + + +def crypto_box_open_afternm(ctxt, nonce, k): + ''' + Decrypts a ciphertext ctxt given k + ''' + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError('Invalid shared key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + pad = b'\x00' * crypto_box_BOXZEROBYTES + ctxt + msg = ctypes.create_string_buffer(len(pad)) + ret = nacl.crypto_box_open_afternm( + msg, + pad, + ctypes.c_ulonglong(len(pad)), + nonce, + k) + if ret: + raise CryptError('unable to decrypt message') + return msg.raw[crypto_box_ZEROBYTES:] + + +def crypto_box_easy_afternm(msg, nonce, k): + ''' + Using a precalculated shared key, encrypt the given message. A nonce + must also be passed in, never reuse the nonce + + enc_msg = nacl.crypto_box_easy_afternm('secret message', <unique nonce>, <shared key string>) + ''' + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError('Invalid shared key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + ctxt = ctypes.create_string_buffer(len(msg) + crypto_box_MACBYTES) + ret = nacl.crypto_box_easy_afternm(ctxt, msg, ctypes.c_ulonglong(len(msg)), nonce, k) + if ret: + raise CryptError('Unable to encrypt messsage') + return ctxt.raw + + +def crypto_box_open_easy_afternm(ctxt, nonce, k): + ''' + Decrypts a ciphertext ctxt given k + ''' + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError('Invalid shared key') + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + msg = ctypes.create_string_buffer(len(ctxt) - crypto_box_MACBYTES) + ret = nacl.crypto_box_open_easy_afternm( + msg, + ctxt, + ctypes.c_ulonglong(len(ctxt)), + nonce, + k) + if ret: + raise CryptError('unable to decrypt message') + return msg.raw + + +def crypto_box_seal(msg, pk): + ''' + Using a public key to encrypt the given message. The identity of the sender cannot be verified. + + enc_msg = nacl.crypto_box_seal('secret message', <public key string>) + ''' + if not HAS_SEAL: + raise ValueError('Underlying Sodium library does not support sealed boxes') + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if not isinstance(msg, bytes): + raise TypeError('Message must be bytes') + + c = ctypes.create_string_buffer(len(msg) + crypto_box_SEALBYTES) + ret = nacl.crypto_box_seal(c, msg, ctypes.c_ulonglong(len(msg)), pk) + if ret: + raise CryptError('Unable to encrypt message') + return c.raw + + +def crypto_box_seal_open(ctxt, pk, sk): + ''' + Decrypts a message given the receiver's public and private key. + ''' + if not HAS_SEAL: + raise ValueError('Underlying Sodium library does not support sealed boxes') + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + if not isinstance(ctxt, bytes): + raise TypeError('Message must be bytes') + + c = ctypes.create_string_buffer(len(ctxt) - crypto_box_SEALBYTES) + ret = nacl.crypto_box_seal_open(c, ctxt, ctypes.c_ulonglong(len(ctxt)), pk, sk) + if ret: + raise CryptError('Unable to decrypt message') + return c.raw + +# Signing functions + + +def crypto_sign_keypair(): + ''' + Generates a signing/verification key pair + ''' + vk = ctypes.create_string_buffer(crypto_sign_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_sign_SECRETKEYBYTES) + ret = nacl.crypto_sign_keypair(vk, sk) + if ret: + raise ValueError('Failed to generate keypair') + return vk.raw, sk.raw + + +def crypto_sign_ed25519_keypair(): + ''' + Generates a signing/verification Ed25519 key pair + ''' + vk = ctypes.create_string_buffer(crypto_sign_ed25519_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_sign_ed25519_SECRETKEYBYTES) + ret = nacl.crypto_sign_ed25519_keypair(vk, sk) + if ret: + raise ValueError('Failed to generate keypair') + return vk.raw, sk.raw + + +def crypto_sign_ed25519_sk_to_pk(sk): + ''' + Extract the public key from the secret key + ''' + if len(sk) != crypto_sign_ed25519_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + + pk = ctypes.create_string_buffer(crypto_sign_PUBLICKEYBYTES) + ret = nacl.crypto_sign_ed25519_sk_to_pk(pk, sk) + if ret: + raise ValueError('Failed to generate public key') + return pk.raw + + +def crypto_sign_ed25519_sk_to_seed(sk): + ''' + Extract the seed from the secret key + ''' + if len(sk) != crypto_sign_ed25519_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + + seed = ctypes.create_string_buffer(crypto_sign_SEEDBYTES) + ret = nacl.crypto_sign_ed25519_sk_to_seed(seed, sk) + if ret: + raise ValueError('Failed to generate seed') + return seed.raw + + +def crypto_sign(msg, sk): + ''' + Sign the given message with the given signing key + ''' + if len(sk) != crypto_sign_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + + sig = ctypes.create_string_buffer(len(msg) + crypto_sign_BYTES) + slen = ctypes.pointer(ctypes.c_ulonglong()) + ret = nacl.crypto_sign( + sig, + slen, + msg, + ctypes.c_ulonglong(len(msg)), + sk) + if ret: + raise ValueError('Failed to sign message') + return sig.raw + + +def crypto_sign_detached(msg, sk): + ''' + Return signature for the given message with the given signing key + ''' + if len(sk) != crypto_sign_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + + sig = ctypes.create_string_buffer(crypto_sign_BYTES) + slen = ctypes.pointer(ctypes.c_ulonglong()) + ret = nacl.crypto_sign_detached( + sig, + slen, + msg, + ctypes.c_ulonglong(len(msg)), + sk) + if ret: + raise ValueError('Failed to sign message') + return sig.raw[:slen.contents.value] + + +def crypto_sign_seed_keypair(seed): + ''' + Computes and returns the secret and verify keys from the given seed + ''' + if len(seed) != crypto_sign_SEEDBYTES: + raise ValueError('Invalid Seed') + + sk = ctypes.create_string_buffer(crypto_sign_SECRETKEYBYTES) + vk = ctypes.create_string_buffer(crypto_sign_PUBLICKEYBYTES) + + ret = nacl.crypto_sign_seed_keypair(vk, sk, seed) + if ret: + raise CryptError('Failed to generate keypair from seed') + return (vk.raw, sk.raw) + + +def crypto_sign_open(sig, vk): + ''' + Verifies the signed message sig using the signer's verification key + ''' + if len(vk) != crypto_sign_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + + msg = ctypes.create_string_buffer(len(sig)) + msglen = ctypes.c_ulonglong() + msglenp = ctypes.pointer(msglen) + ret = nacl.crypto_sign_open( + msg, + msglenp, + sig, + ctypes.c_ulonglong(len(sig)), + vk) + if ret: + raise ValueError('Failed to validate message') + return msg.raw[:msglen.value] # pylint: disable=invalid-slice-index + + +def crypto_sign_verify_detached(sig, msg, vk): + ''' + Verifies that sig is a valid signature for the message msg using the signer's verification key + ''' + if len(sig) != crypto_sign_BYTES: + raise ValueError('Invalid signature') + if len(vk) != crypto_sign_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + + ret = nacl.crypto_sign_verify_detached( + sig, + msg, + ctypes.c_ulonglong(len(msg)), + vk) + if ret: + raise ValueError('Failed to validate message') + return msg + + +# Authenticated Symmetric Encryption + + +def crypto_secretbox(message, nonce, key): + """Encrypts and authenticates a message using the given secret key, and nonce + + Args: + message (bytes): a message to encrypt + nonce (bytes): nonce, does not have to be confidential must be + `crypto_secretbox_NONCEBYTES` in length + key (bytes): secret key, must be `crypto_secretbox_KEYBYTES` in + length + + Returns: + bytes: the ciphertext + + Raises: + ValueError: if arguments' length is wrong or the operation has failed. + """ + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + + pad = b'\x00' * crypto_secretbox_ZEROBYTES + message + ctxt = ctypes.create_string_buffer(len(pad)) + ret = nacl.crypto_secretbox( + ctxt, pad, ctypes.c_ulonglong(len(pad)), nonce, key) + if ret: + raise ValueError('Failed to encrypt message') + return ctxt.raw[crypto_secretbox_BOXZEROBYTES:] + + +def crypto_secretbox_open(ctxt, nonce, key): + """ + Decrypts a ciphertext ctxt given the receivers private key, and senders + public key + """ + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + + pad = b'\x00' * crypto_secretbox_BOXZEROBYTES + ctxt + msg = ctypes.create_string_buffer(len(pad)) + ret = nacl.crypto_secretbox_open( + msg, + pad, + ctypes.c_ulonglong(len(pad)), + nonce, + key) + if ret: + raise ValueError('Failed to decrypt message') + return msg.raw[crypto_secretbox_ZEROBYTES:] + +# Authenticated Symmetric Encryption improved version + + +def crypto_secretbox_easy(cmessage, nonce, key): + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + + + ctxt = ctypes.create_string_buffer(crypto_secretbox_MACBYTES + len(cmessage)) + ret = nacl.crypto_secretbox_easy(ctxt, cmessage, ctypes.c_ulonglong(len(cmessage)), nonce, key) + if ret: + raise ValueError('Failed to encrypt message') + return ctxt.raw[0:] + +def crypto_secretbox_open_easy(ctxt, nonce, key): + + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + + msg = ctypes.create_string_buffer(len(ctxt)) + ret = nacl.crypto_secretbox_open_easy(msg, ctxt, ctypes.c_ulonglong(len(ctxt)), nonce, key) + if ret: + raise ValueError('Failed to decrypt message') + return msg.raw[0:len(ctxt) - crypto_secretbox_MACBYTES] + +# Authenticated Symmetric Encryption with Additional Data + + +def crypto_aead_aes256gcm_encrypt(message, aad, nonce, key): + """Encrypts and authenticates a message with public additional data using the given secret key, and nonce + + Args: + message (bytes): a message to encrypt + aad (bytes): additional public data to authenticate + nonce (bytes): nonce, does not have to be confidential must be + `crypto_aead_aes256gcm_NPUBBYTES` in length + key (bytes): secret key, must be `crypto_aead_aes256gcm_KEYBYTES` in + length + + Returns: + bytes: the ciphertext + + Raises: + ValueError: if arguments' length is wrong or the operation has failed. + """ + if not HAS_AEAD_AES256GCM: + raise ValueError('Underlying Sodium library does not support AES256-GCM AEAD') + + if len(key) != crypto_aead_aes256gcm_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_aead_aes256gcm_NPUBBYTES: + raise ValueError('Invalid nonce') + + length = len(message) + crypto_aead_aes256gcm_ABYTES + clen = ctypes.c_ulonglong() + c = ctypes.create_string_buffer(length) + ret = nacl.crypto_aead_aes256gcm_encrypt( + c, ctypes.pointer(clen), + message, ctypes.c_ulonglong(len(message)), + aad, ctypes.c_ulonglong(len(aad)), + None, + nonce, key) + if ret: + raise ValueError('Failed to encrypt message') + return c.raw + + +def crypto_aead_chacha20poly1305_ietf_encrypt(message, aad, nonce, key): + """Encrypts and authenticates a message with public additional data using the given secret key, and nonce + + Args: + message (bytes): a message to encrypt + aad (bytes): additional public data to authenticate + nonce (bytes): nonce, does not have to be confidential must be + `crypto_aead_chacha20poly1305_ietf_NPUBBYTES` in length + key (bytes): secret key, must be `crypto_aead_chacha20poly1305_ietf_KEYBYTES` in + length + + Returns: + bytes: the ciphertext + + Raises: + ValueError: if arguments' length is wrong or the operation has failed. + """ + if not HAS_AEAD_CHACHA20POLY1305_IETF: + raise ValueError('Underlying Sodium library does not support IETF variant of ChaCha20Poly1305 AEAD') + + if len(key) != crypto_aead_chacha20poly1305_ietf_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_aead_chacha20poly1305_ietf_NPUBBYTES: + raise ValueError('Invalid nonce') + + length = len(message) + crypto_aead_chacha20poly1305_ietf_ABYTES + clen = ctypes.c_ulonglong() + c = ctypes.create_string_buffer(length) + ret = nacl.crypto_aead_chacha20poly1305_ietf_encrypt( + c, ctypes.pointer(clen), + message, ctypes.c_ulonglong(len(message)), + aad, ctypes.c_ulonglong(len(aad)), + None, + nonce, key) + if ret: + raise ValueError('Failed to encrypt message') + return c.raw + + +def crypto_aead_aes256gcm_decrypt(ctxt, aad, nonce, key): + """ + Decrypts a ciphertext ctxt given the key, nonce, and aad. If the aad + or ciphertext were altered then the decryption will fail. + """ + if not HAS_AEAD_AES256GCM: + raise ValueError('Underlying Sodium library does not support AES256-GCM AEAD') + + if len(key) != crypto_aead_aes256gcm_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_aead_aes256gcm_NPUBBYTES: + raise ValueError('Invalid nonce') + + length = len(ctxt)-crypto_aead_aes256gcm_ABYTES + mlen = ctypes.c_ulonglong() + m = ctypes.create_string_buffer(length) + + ret = nacl.crypto_aead_aes256gcm_decrypt( + m, ctypes.byref(mlen), + None, + ctxt, ctypes.c_ulonglong(len(ctxt)), + aad, ctypes.c_ulonglong(len(aad)), + nonce, key) + if ret: + raise ValueError('Failed to decrypt message') + return m.raw + + +def crypto_aead_chacha20poly1305_ietf_decrypt(ctxt, aad, nonce, key): + """ + Decrypts a ciphertext ctxt given the key, nonce, and aad. If the aad + or ciphertext were altered then the decryption will fail. + """ + if not HAS_AEAD_CHACHA20POLY1305_IETF: + raise ValueError('Underlying Sodium library does not support IETF variant of ChaCha20Poly1305 AEAD') + + if len(key) != crypto_aead_chacha20poly1305_ietf_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_aead_chacha20poly1305_ietf_NPUBBYTES: + raise ValueError('Invalid nonce') + + length = len(ctxt)-crypto_aead_chacha20poly1305_ietf_ABYTES + mlen = ctypes.c_ulonglong() + m = ctypes.create_string_buffer(length) + + ret = nacl.crypto_aead_chacha20poly1305_ietf_decrypt( + m, ctypes.byref(mlen), + None, + ctxt, ctypes.c_ulonglong(len(ctxt)), + aad, ctypes.c_ulonglong(len(aad)), + nonce, key) + if ret: + raise ValueError('Failed to decrypt message') + return m.raw + + +# Symmetric Encryption + + +def crypto_stream(slen, nonce, key): + ''' + Generates a stream using the given secret key and nonce + ''' + if len(key) != crypto_stream_KEYBYTES: + raise ValueError('Invalid secret key') + if len(nonce) != crypto_stream_NONCEBYTES: + raise ValueError('Invalid nonce') + + stream = ctypes.create_string_buffer(slen) + ret = nacl.crypto_stream(stream, ctypes.c_ulonglong(slen), nonce, key) + if ret: + raise ValueError('Failed to init stream') + return stream.raw + + +def crypto_stream_xor(msg, nonce, key): + ''' + Encrypts the given message using the given secret key and nonce + + The crypto_stream_xor function guarantees that the ciphertext is the + plaintext (xor) the output of crypto_stream. Consequently + crypto_stream_xor can also be used to decrypt + ''' + if len(key) != crypto_stream_KEYBYTES: + raise ValueError('Invalid secret key') + if len(nonce) != crypto_stream_NONCEBYTES: + raise ValueError('Invalid nonce') + + stream = ctypes.create_string_buffer(len(msg)) + ret = nacl.crypto_stream_xor( + stream, + msg, + ctypes.c_ulonglong(len(msg)), + nonce, + key) + if ret: + raise ValueError('Failed to init stream') + return stream.raw + + +# Authentication + + +def crypto_auth(msg, key): + ''' + Constructs a one time authentication token for the given message msg + using a given secret key + ''' + if len(key) != crypto_auth_KEYBYTES: + raise ValueError('Invalid secret key') + + tok = ctypes.create_string_buffer(crypto_auth_BYTES) + ret = nacl.crypto_auth(tok, msg, ctypes.c_ulonglong(len(msg)), key) + if ret: + raise ValueError('Failed to auth msg') + return tok.raw[:crypto_auth_BYTES] + + +def crypto_auth_verify(tok, msg, key): + ''' + Verifies that the given authentication token is correct for the given + message and key + ''' + if len(key) != crypto_auth_KEYBYTES: + raise ValueError('Invalid secret key') + if len(tok) != crypto_auth_BYTES: + raise ValueError('Invalid authenticator') + + ret = nacl.crypto_auth_verify(tok, msg, ctypes.c_ulonglong(len(msg)), key) + if ret: + raise ValueError('Failed to auth msg') + return msg + +# One time authentication + + +def crypto_onetimeauth_primitive(): + """ + Return the onetimeauth underlying primitive + + Returns: + str: always ``poly1305`` + """ + func = nacl.crypto_onetimeauth_primitive + func.restype = ctypes.c_char_p + return func().decode() + + +def crypto_onetimeauth(message, key): + """ + Constructs a one time authentication token for the given message using + a given secret key + + Args: + message (bytes): message to authenticate. + key (bytes): secret key - must be of crypto_onetimeauth_KEYBYTES length. + + Returns: + bytes: an authenticator, of crypto_onetimeauth_BYTES length. + + Raises: + ValueError: if arguments' length is wrong. + """ + if len(key) != crypto_onetimeauth_KEYBYTES: + raise ValueError('Invalid secret key') + + tok = ctypes.create_string_buffer(crypto_onetimeauth_BYTES) + # cannot fail + _ = nacl.crypto_onetimeauth( + tok, message, ctypes.c_ulonglong(len(message)), key) + + return tok.raw[:crypto_onetimeauth_BYTES] + + +def crypto_onetimeauth_verify(token, message, key): + """ + Verifies, in constant time, that ``token`` is a correct authenticator for + the message using the secret key. + + Args: + token (bytes): an authenticator of crypto_onetimeauth_BYTES length. + message (bytes): The message to authenticate. + key: key (bytes): secret key - must be of crypto_onetimeauth_KEYBYTES + length. + + Returns: + bytes: secret key - must be of crypto_onetimeauth_KEYBYTES length. + + Raises: + ValueError: if arguments' length is wrong or verification has failed. + """ + if len(key) != crypto_onetimeauth_KEYBYTES: + raise ValueError('Invalid secret key') + if len(token) != crypto_onetimeauth_BYTES: + raise ValueError('Invalid authenticator') + + ret = nacl.crypto_onetimeauth_verify( + token, message, ctypes.c_ulonglong(len(message)), key) + if ret: + raise ValueError('Failed to auth message') + return message + +# Hashing + + +def crypto_hash(msg): + ''' + Compute a hash of the given message + ''' + hbuf = ctypes.create_string_buffer(crypto_hash_BYTES) + nacl.crypto_hash(hbuf, msg, ctypes.c_ulonglong(len(msg))) + return hbuf.raw + + +def crypto_hash_sha256(msg): + ''' + Compute the sha256 hash of the given message + ''' + hbuf = ctypes.create_string_buffer(crypto_hash_sha256_BYTES) + nacl.crypto_hash_sha256(hbuf, msg, ctypes.c_ulonglong(len(msg))) + return hbuf.raw + + +def crypto_hash_sha512(msg): + ''' + Compute the sha512 hash of the given message + ''' + hbuf = ctypes.create_string_buffer(crypto_hash_sha512_BYTES) + nacl.crypto_hash_sha512(hbuf, msg, ctypes.c_ulonglong(len(msg))) + return hbuf.raw + +# Generic Hash + + +def crypto_generichash(msg, key=None): + ''' + Compute the blake2 hash of the given message with a given key + ''' + hbuf = ctypes.create_string_buffer(crypto_generichash_BYTES) + if key: + key_len = len(key) + else: + key_len = 0 + nacl.crypto_generichash( + hbuf, + ctypes.c_size_t(len(hbuf)), + msg, + ctypes.c_ulonglong(len(msg)), + key, + ctypes.c_size_t(key_len)) + return hbuf.raw + + +# String cmp + + +def crypto_verify_16(string1, string2): + ''' + Compares the first crypto_verify_16_BYTES of the given strings + + The time taken by the function is independent of the contents of string1 + and string2. In contrast, the standard C comparison function + memcmp(string1,string2,16) takes time that is dependent on the longest + matching prefix of string1 and string2. This often allows for easy + timing attacks. + ''' + a, b, c = (len(string1) >= 16), (len(string2) >= 16), (not nacl.crypto_verify_16(string1, string2)) + return a & b & c + + +def crypto_verify_32(string1, string2): + ''' + Compares the first crypto_verify_32_BYTES of the given strings + + The time taken by the function is independent of the contents of string1 + and string2. In contrast, the standard C comparison function + memcmp(string1,string2,32) takes time that is dependent on the longest + matching prefix of string1 and string2. This often allows for easy + timing attacks. + ''' + a, b, c = (len(string1) >= 32), (len(string2) >= 32), (not nacl.crypto_verify_32(string1, string2)) + return a & b & c + + +def crypto_verify_64(string1, string2): + ''' + Compares the first crypto_verify_64_BYTES of the given strings + + The time taken by the function is independent of the contents of string1 + and string2. In contrast, the standard C comparison function + memcmp(string1,string2,64) takes time that is dependent on the longest + matching prefix of string1 and string2. This often allows for easy + timing attacks. + ''' + a, b, c = (len(string1) >= 64), (len(string2) >= 64), (not nacl.crypto_verify_64(string1, string2)) + return a & b & c + + +def bytes_eq(a, b): + ''' + Compares two byte instances with one another. If `a` and `b` have + different lengths, return `False` immediately. Otherwise `a` and `b` + will be compared in constant time. + + Return `True` in case `a` and `b` are equal. Otherwise `False`. + + Raises :exc:`TypeError` in case `a` and `b` are not both of the type + :class:`bytes`. + ''' + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError('Both arguments must be bytes.') + + len_a = len(a) + len_b = len(b) + if len_a != len_b: + return False + + return nacl.sodium_memcmp(a, b, len_a) == 0 + +# Random byte generation + + +def randombytes(size): + ''' + Return a string of random bytes of the given size + ''' + buf = ctypes.create_string_buffer(size) + nacl.randombytes(buf, ctypes.c_ulonglong(size)) + return buf.raw + + +def randombytes_buf(size): + ''' + Return a string of random bytes of the given size + ''' + size = int(size) + buf = ctypes.create_string_buffer(size) + nacl.randombytes_buf(buf, size) + return buf.raw + +def randombytes_buf_deterministic(size, seed): + ''' + Returns a string of random byles of the given size for a given seed. + For a given seed, this function will always output the same sequence. + Size can be up to 2^70 (256 GB). + ''' + + if not HAS_RAND_SEED: + raise ValueError('Underlying Sodium library does not support randombytes_seedbytes') + if len(seed) != randombytes_SEEDBYTES: + raise ValueError('Invalid key seed') + + size = int(size) + buf = ctypes.create_string_buffer(size) + nacl.randombytes_buf_deterministic(buf, size, seed) + return buf.raw + +def randombytes_close(): + ''' + Close the file descriptor or the handle for the cryptographic service + provider + ''' + nacl.randombytes_close() + + +def randombytes_random(): + ''' + Return a random 32-bit unsigned value + ''' + return nacl.randombytes_random() + + +def randombytes_stir(): + ''' + Generate a new key for the pseudorandom number generator + + The file descriptor for the entropy source is kept open, so that the + generator can be reseeded even in a chroot() jail. + ''' + nacl.randombytes_stir() + + +def randombytes_uniform(upper_bound): + ''' + Return a value between 0 and upper_bound using a uniform distribution + ''' + return nacl.randombytes_uniform(upper_bound) + +# Key derivation API + +def crypto_kdf_keygen(): + ''' + Returns a string of random bytes to generate a master key + ''' + if not HAS_CRYPT_KDF: + raise ValueError('Underlying Sodium library does not support crypto_kdf_keybytes') + size = crypto_kdf_KEYBYTES + buf = ctypes.create_string_buffer(size) + nacl.crypto_kdf_keygen(buf) + return buf.raw + +def crypto_kdf_derive_from_key(subkey_size, subkey_id, context, master_key): + ''' + Returns a subkey generated from a master key for a given subkey_id. + For a given subkey_id, the subkey will always be the same string. + ''' + size = int(subkey_size) + buf = ctypes.create_string_buffer(size) + nacl.crypto_kdf_derive_from_key(buf, subkey_size, ctypes.c_ulonglong(subkey_id), context, master_key) + return buf.raw + +# Key Exchange API + +def crypto_kx_keypair(): + ''' + Generate and return a new keypair + ''' + if not HAS_CRYPT_KX: + raise ValueError('Underlying Sodium library does not support crypto_kx') + pk = ctypes.create_string_buffer(crypto_kx_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_kx_SECRETKEYBYTES) + nacl.crypto_kx_keypair(pk, sk) + return pk.raw, sk.raw + +def crypto_kx_seed_keypair(seed): + ''' + Generate and return a keypair from a key seed + ''' + if not HAS_CRYPT_KX: + raise ValueError('Underlying Sodium library does not support crypto_kx') + + if len(seed) != crypto_kx_SEEDBYTES: + raise ValueError('Invalid key seed') + pk = ctypes.create_string_buffer(crypto_kx_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_kx_SECRETKEYBYTES) + nacl.crypto_kx_seed_keypair(pk, sk, seed) + return pk.raw, sk.raw + +def crypto_kx_client_session_keys(client_pk, client_sk, server_pk): + ''' + Computes a pair of shared keys (rx and tx) using the client's public key client_pk, + the client's secret key client_sk and the server's public key server_pk. + Status returns 0 on success, or -1 if the server's public key is not acceptable. + ''' + if not HAS_CRYPT_KX: + raise ValueError('Underlying Sodium library does not support crypto_kx') + + rx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + tx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + status = nacl.crypto_kx_client_session_keys(rx, tx, client_pk, client_sk, server_pk) + return rx.raw, tx.raw, status + +def crypto_kx_server_session_keys(server_pk, server_sk, client_pk): + ''' + Computes a pair of shared keys (rx and tx) using the server's public key server_pk, + the server's secret key server_sk and the client's public key client_pk. + Status returns 0 on success, or -1 if the client's public key is not acceptable. + ''' + if not HAS_CRYPT_KX: + raise ValueError('Underlying Sodium library does not support crypto_kx') + + rx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + tx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + status = nacl.crypto_kx_server_session_keys(rx, tx, server_pk, server_sk, client_pk) + return rx.raw, tx.raw, status + + + +# Utility functions + +def sodium_library_version_major(): + ''' + Return the major version number + ''' + return nacl.sodium_library_version_major() + + +def sodium_library_version_minor(): + ''' + Return the minor version number + ''' + return nacl.sodium_library_version_minor() + + +def sodium_version_string(): + ''' + Return the version string + ''' + func = nacl.sodium_version_string + func.restype = ctypes.c_char_p + return func() + + +def crypto_sign_ed25519_pk_to_curve25519(ed25519_pk): + ''' + Convert an Ed25519 public key to a Curve25519 public key + ''' + if len(ed25519_pk) != crypto_sign_ed25519_PUBLICKEYBYTES: + raise ValueError('Invalid public key') + + curve25519_pk = ctypes.create_string_buffer(crypto_scalarmult_curve25519_BYTES) + ret = nacl.crypto_sign_ed25519_pk_to_curve25519(curve25519_pk, ed25519_pk) + if ret: + raise CryptError('Failed to generate Curve25519 public key') + return curve25519_pk.raw + + +def crypto_sign_ed25519_sk_to_curve25519(ed25519_sk): + ''' + Convert an Ed25519 secret key to a Curve25519 secret key + ''' + if len(ed25519_sk) != crypto_sign_ed25519_SECRETKEYBYTES: + raise ValueError('Invalid secret key') + + curve25519_sk = ctypes.create_string_buffer(crypto_scalarmult_curve25519_BYTES) + ret = nacl.crypto_sign_ed25519_sk_to_curve25519(curve25519_sk, ed25519_sk) + if ret: + raise CryptError('Failed to generate Curve25519 secret key') + return curve25519_sk.raw + + diff --git a/libnacl/aead.py b/libnacl/aead.py new file mode 100644 index 0000000..db53b13 --- /dev/null +++ b/libnacl/aead.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +''' +Utilities to make secret box encryption simple +''' +# Import libnacl +import libnacl +import libnacl.utils +import libnacl.base + + +class AEAD(libnacl.base.BaseKey): + ''' + Manage AEAD encryption using the IETF ChaCha20-Poly1305(default) or AES-GCM algorithm + ''' + + def __init__(self, key=None): + if key is None: + key = libnacl.utils.aead_key() + if len(key) != libnacl.crypto_aead_chacha20poly1305_ietf_KEYBYTES: # same size for both + raise ValueError('Invalid key') + self.sk = key + self.usingAES = False + + def useAESGCM(self): + self.usingAES = True + return self + + def encrypt(self, msg, aad, nonce=None, pack_nonce_aad=True): + ''' + Encrypt the given message. If a nonce is not given it will be + generated via the rand_nonce function + ''' + if nonce is None: + nonce = libnacl.utils.rand_aead_nonce() + if len(nonce) != libnacl.crypto_aead_aes256gcm_NPUBBYTES: + raise ValueError('Invalid nonce') + if self.usingAES: + ctxt = libnacl.crypto_aead_aes256gcm_encrypt(msg, aad, nonce, self.sk) + else: + ctxt = libnacl.crypto_aead_chacha20poly1305_ietf_encrypt(msg, aad, nonce, self.sk) + + if pack_nonce_aad: + return aad + nonce + ctxt + else: + return aad, nonce, ctxt + + def decrypt(self, ctxt, aadLen): + ''' + Decrypt the given message, if no nonce or aad are given they will be + extracted from the message + ''' + aad = ctxt[:aadLen] + nonce = ctxt[aadLen:aadLen+libnacl.crypto_aead_aes256gcm_NPUBBYTES] + ctxt = ctxt[aadLen+libnacl.crypto_aead_aes256gcm_NPUBBYTES:] + if len(nonce) != libnacl.crypto_aead_aes256gcm_NPUBBYTES: + raise ValueError('Invalid nonce') + if self.usingAES: + return libnacl.crypto_aead_aes256gcm_decrypt(ctxt, aad, nonce, self.sk) + return libnacl.crypto_aead_chacha20poly1305_ietf_decrypt(ctxt, aad, nonce, self.sk) + + def decrypt_unpacked(self, aad, nonce, ctxt): + ''' + Decrypt the given message, if no nonce or aad are given they will be + extracted from the message + ''' + if len(nonce) != libnacl.crypto_aead_aes256gcm_NPUBBYTES: + raise ValueError('Invalid nonce') + if self.usingAES: + return libnacl.crypto_aead_aes256gcm_decrypt(ctxt, aad, nonce, self.sk) + return libnacl.crypto_aead_chacha20poly1305_ietf_decrypt(ctxt, aad, nonce, self.sk) diff --git a/libnacl/base.py b/libnacl/base.py new file mode 100644 index 0000000..b115a47 --- /dev/null +++ b/libnacl/base.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +''' +Implement the base key object for other keys to inherit convenience functions +''' +# Import libnacl libs +import libnacl.encode + +# Import python libs +import os +import stat + +class BaseKey(object): + ''' + Include methods for key management convenience + ''' + def hex_sk(self): + if hasattr(self, 'sk'): + return libnacl.encode.hex_encode(self.sk) + else: + return '' + + def hex_pk(self): + if hasattr(self, 'pk'): + return libnacl.encode.hex_encode(self.pk) + + def hex_vk(self): + if hasattr(self, 'vk'): + return libnacl.encode.hex_encode(self.vk) + + def hex_seed(self): + if hasattr(self, 'seed'): + return libnacl.encode.hex_encode(self.seed) + + def for_json(self): + ''' + Return a dictionary of the secret values we need to store. + ''' + pre = {} + sk = self.hex_sk() + pk = self.hex_pk() + vk = self.hex_vk() + seed = self.hex_seed() + if sk: + pre['priv'] = sk.decode('utf-8') + if pk: + pre['pub'] = pk.decode('utf-8') + if vk: + pre['verify'] = vk.decode('utf-8') + if seed: + pre['sign'] = seed.decode('utf-8') + + return pre + + def save(self, path, serial='json'): + ''' + Safely save keys with perms of 0400 + ''' + pre = self.for_json() + + if serial == 'msgpack': + import msgpack + packaged = msgpack.dumps(pre) + elif serial == 'json': + import json + packaged = json.dumps(pre) + + perm_other = stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH + perm_group = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP + + cumask = os.umask(perm_other | perm_group) + with open(path, 'w+') as fp_: + fp_.write(packaged) + os.umask(cumask) diff --git a/libnacl/blake.py b/libnacl/blake.py new file mode 100644 index 0000000..8da7d69 --- /dev/null +++ b/libnacl/blake.py @@ -0,0 +1,44 @@ +''' +Mimic very closely the python hashlib classes for blake2b + +NOTE: + This class does not yet implement streaming the msg into the + hash function via the update method +''' + +# Import python libs +import binascii + +# Import libnacl libs +import libnacl + + +class Blake2b(object): + ''' + Manage a Blake2b hash + ''' + def __init__(self, msg, key=None): + self.msg = msg + self.key = key + self.raw_digest = libnacl.crypto_generichash(msg, key) + self.digest_size = len(self.raw_digest) + + def digest(self): + ''' + Return the digest of the string + ''' + return self.raw_digest + + def hexdigest(self): + ''' + Return the hex digest of the string + ''' + return binascii.hexlify(self.raw_digest) + + +def blake2b(msg, key=None): + ''' + Create and return a Blake2b object to mimic the behavior of the python + hashlib functions + ''' + return Blake2b(msg, key) diff --git a/libnacl/dual.py b/libnacl/dual.py new file mode 100644 index 0000000..c48fb67 --- /dev/null +++ b/libnacl/dual.py @@ -0,0 +1,34 @@ +''' +The dual key system allows for the creation of keypairs that contain both +cryptographic and signing keys +''' +# import libnacl libs +import libnacl +import libnacl.base +import libnacl.public +import libnacl.sign + + +class DualSecret(libnacl.base.BaseKey): + ''' + Manage crypt and sign keys in one object + ''' + def __init__(self, crypt=None, sign=None): + self.crypt = libnacl.public.SecretKey(crypt) + self.signer = libnacl.sign.Signer(sign) + self.sk = self.crypt.sk + self.seed = self.signer.seed + self.pk = self.crypt.pk + self.vk = self.signer.vk + + def sign(self, msg): + ''' + Sign the given message + ''' + return self.signer.sign(msg) + + def signature(self, msg): + ''' + Return just the signature for the message + ''' + return self.signer.signature(msg) diff --git a/libnacl/encode.py b/libnacl/encode.py new file mode 100644 index 0000000..efbfe99 --- /dev/null +++ b/libnacl/encode.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +''' +Build in routines and classes to simplify encoding routines +''' +# Import python libs +import base64 +import binascii + + +def hex_encode(data): + ''' + Hex encode data + ''' + return binascii.hexlify(data) + + +def hex_decode(data): + ''' + Hex decode data + ''' + return binascii.unhexlify(data) + + +def base16_encode(data): + ''' + Base32 encode data + ''' + return base64.b16encode(data) + + +def base16_decode(data): + ''' + Base16 decode data + ''' + return base64.b16decode(data) + + +def base32_encode(data): + ''' + Base16 encode data + ''' + return base64.b32encode(data) + + +def base32_decode(data): + ''' + Base32 decode data + ''' + return base64.b32decode(data) + + +def base64_encode(data): + ''' + Base16 encode data + ''' + return base64.b64encode(data) + + +def base64_decode(data): + ''' + Base32 decode data + ''' + return base64.b64decode(data) diff --git a/libnacl/public.py b/libnacl/public.py new file mode 100644 index 0000000..bac8609 --- /dev/null +++ b/libnacl/public.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +''' +High level classes and routines around public key encryption and decryption +''' +# import libnacl libs +import libnacl +import libnacl.utils +import libnacl.encode +import libnacl.dual +import libnacl.base + + +class PublicKey(libnacl.base.BaseKey): + ''' + This class is used to manage public keys + ''' + def __init__(self, pk): + if len(pk) == libnacl.crypto_box_PUBLICKEYBYTES: + self.pk = pk + else: + raise ValueError('Passed in invalid public key') + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.pk == other.pk + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(self.pk) + + +class SecretKey(libnacl.base.BaseKey): + ''' + This class is used to manage keypairs + ''' + def __init__(self, sk=None): + ''' + If a secret key is not passed in then it will be generated + ''' + if sk is None: + self.pk, self.sk = libnacl.crypto_box_keypair() + elif len(sk) == libnacl.crypto_box_SECRETKEYBYTES: + self.sk = sk + self.pk = libnacl.crypto_scalarmult_base(sk) + else: + raise ValueError('Passed in invalid secret key') + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.sk == other.sk and self.pk == other.pk + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.sk, self.pk)) + + +class Box(object): + ''' + TheBox class is used to create cryptographic boxes and unpack + cryptographic boxes + ''' + def __init__(self, sk, pk): + if isinstance(sk, (SecretKey, libnacl.dual.DualSecret)): + sk = sk.sk + if isinstance(pk, (SecretKey, libnacl.dual.DualSecret)): + raise ValueError('Passed in secret key as public key') + if isinstance(pk, PublicKey): + pk = pk.pk + if pk and sk: + self._k = libnacl.crypto_box_beforenm(pk, sk) + + def encrypt(self, msg, nonce=None, pack_nonce=True): + ''' + Encrypt the given message with the given nonce, if the nonce is not + provided it will be generated from the libnacl.utils.rand_nonce + function + ''' + if nonce is None: + nonce = libnacl.utils.rand_nonce() + elif len(nonce) != libnacl.crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce size') + ctxt = libnacl.crypto_box_afternm(msg, nonce, self._k) + if pack_nonce: + return nonce + ctxt + else: + return nonce, ctxt + + def decrypt(self, ctxt, nonce=None): + ''' + Decrypt the given message, if a nonce is passed in attempt to decrypt + it with the given nonce, otherwise assum that the nonce is attached + to the message + ''' + if nonce is None: + nonce = ctxt[:libnacl.crypto_box_NONCEBYTES] + ctxt = ctxt[libnacl.crypto_box_NONCEBYTES:] + elif len(nonce) != libnacl.crypto_box_NONCEBYTES: + raise ValueError('Invalid nonce') + msg = libnacl.crypto_box_open_afternm(ctxt, nonce, self._k) + return msg diff --git a/libnacl/sealed.py b/libnacl/sealed.py new file mode 100644 index 0000000..7ce75f6 --- /dev/null +++ b/libnacl/sealed.py @@ -0,0 +1,33 @@ +import libnacl +import libnacl.public +import libnacl.dual + +class SealedBox(object): + ''' + Sealed box is a variant of Box that does not authenticate sender. + ''' + + def __init__(self, pk, sk=None): + self.pk = pk + self.sk = sk + + if isinstance(pk, (libnacl.public.SecretKey, libnacl.dual.DualSecret)): + self.pk = pk.pk + self.sk = pk.sk + + if isinstance(pk, libnacl.public.PublicKey): + self.pk = pk.pk + + def encrypt(self, msg): + ''' + Encrypt the given message using the receiver's public key + ''' + return libnacl.crypto_box_seal(msg, self.pk) + + def decrypt(self, msg): + ''' + Decrypt the given message using the receiver's public and private key + ''' + if not self.sk: + raise ValueError('Secret key is not set') + return libnacl.crypto_box_seal_open(msg, self.pk, self.sk) diff --git a/libnacl/secret.py b/libnacl/secret.py new file mode 100644 index 0000000..278f4ee --- /dev/null +++ b/libnacl/secret.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +''' +Utilities to make secret box encryption simple +''' +# Import libnacl +import libnacl +import libnacl.utils +import libnacl.base + + +class SecretBox(libnacl.base.BaseKey): + ''' + Manage symetric encryption using the salsa20 algorithm + ''' + def __init__(self, key=None): + if key is None: + key = libnacl.utils.salsa_key() + if len(key) != libnacl.crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + self.sk = key + + def encrypt(self, msg, nonce=None, pack_nonce=True): + ''' + Encrypt the given message. If a nonce is not given it will be + generated via the rand_nonce function + ''' + if nonce is None: + nonce = libnacl.utils.rand_nonce() + if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce size') + ctxt = libnacl.crypto_secretbox(msg, nonce, self.sk) + if pack_nonce: + return nonce + ctxt + else: + return nonce, ctxt + + def decrypt(self, ctxt, nonce=None): + ''' + Decrypt the given message, if no nonce is given the nonce will be + extracted from the message + ''' + if nonce is None: + nonce = ctxt[:libnacl.crypto_secretbox_NONCEBYTES] + ctxt = ctxt[libnacl.crypto_secretbox_NONCEBYTES:] + if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + return libnacl.crypto_secretbox_open(ctxt, nonce, self.sk) diff --git a/libnacl/secret_easy.py b/libnacl/secret_easy.py new file mode 100644 index 0000000..15dea1b --- /dev/null +++ b/libnacl/secret_easy.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +''' +Utilities to make secret box easy encryption simple +''' +# Import libnacl +import libnacl +import libnacl.utils +import libnacl.base + + +class SecretBoxEasy(libnacl.base.BaseKey): + ''' + Manage symetric encryption using the salsa20 algorithm + ''' + def __init__(self, key=None): + if key is None: + key = libnacl.utils.salsa_key() + if len(key) != libnacl.crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + self.sk = key + + def encrypt(self, msg, nonce=None, pack_nonce=True): + ''' + Encrypt the given message. If a nonce is not given it will be + generated via the rand_nonce function + ''' + if nonce is None: + nonce = libnacl.utils.rand_nonce() + if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce size') + ctxt = libnacl.crypto_secretbox_easy(msg, nonce, self.sk) + if pack_nonce: + return nonce + ctxt + else: + return nonce, ctxt + + def decrypt(self, ctxt, nonce=None): + ''' + Decrypt the given message, if no nonce is given the nonce will be + extracted from the message + ''' + if nonce is None: + nonce = ctxt[:libnacl.crypto_secretbox_NONCEBYTES] + ctxt = ctxt[libnacl.crypto_secretbox_NONCEBYTES:] + if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + return libnacl.crypto_secretbox_open_easy(ctxt, nonce, self.sk) diff --git a/libnacl/sign.py b/libnacl/sign.py new file mode 100644 index 0000000..9dbd83f --- /dev/null +++ b/libnacl/sign.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +''' +High level routines to maintain signing keys and to sign and verify messages +''' +# Import libancl libs +import libnacl +import libnacl.base +import libnacl.encode + + +class Signer(libnacl.base.BaseKey): + ''' + The tools needed to sign messages + ''' + def __init__(self, seed=None): + ''' + Create a signing key, if not seed it supplied a keypair is generated + ''' + if seed: + if len(seed) != libnacl.crypto_sign_SEEDBYTES: + raise ValueError('Invalid seed bytes') + self.vk, self.sk = libnacl.crypto_sign_seed_keypair(seed) + else: + seed = libnacl.randombytes(libnacl.crypto_sign_SEEDBYTES) + self.vk, self.sk = libnacl.crypto_sign_seed_keypair(seed) + self.seed = seed + + def sign(self, msg): + ''' + Sign the given message with this key + ''' + return libnacl.crypto_sign(msg, self.sk) + + def signature(self, msg): + ''' + Return just the signature for the message + ''' + return libnacl.crypto_sign(msg, self.sk)[:libnacl.crypto_sign_BYTES] + + +class Verifier(libnacl.base.BaseKey): + ''' + Verify signed messages + ''' + def __init__(self, vk_hex): + ''' + Create a verification key from a hex encoded vkey + ''' + self.vk = libnacl.encode.hex_decode(vk_hex) + + def verify(self, msg): + ''' + Verify the message with tis key + ''' + return libnacl.crypto_sign_open(msg, self.vk) diff --git a/libnacl/utils.py b/libnacl/utils.py new file mode 100644 index 0000000..e06e078 --- /dev/null +++ b/libnacl/utils.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +import struct +import sys +import time + +# Import nacl libs +import libnacl +import libnacl.encode +import libnacl.secret +import libnacl.public +import libnacl.sign +import libnacl.dual + + +def load_key(path_or_file, serial='json'): + ''' + Read in a key from a file and return the applicable key object based on + the contents of the file + ''' + if hasattr(path_or_file, 'read'): + stream = path_or_file + else: + if serial == 'json': + stream = open(path_or_file, 'r') + else: + stream = open(path_or_file, 'rb') + + try: + if serial == 'msgpack': + import msgpack + key_data = msgpack.load(stream) + elif serial == 'json': + import json + if sys.version_info[0] >= 3: + key_data = json.loads(stream.read()) + else: + key_data = json.loads(stream.read(), encoding='UTF-8') + finally: + if stream != path_or_file: + stream.close() + + if 'priv' in key_data and 'sign' in key_data and 'pub' in key_data: + return libnacl.dual.DualSecret( + libnacl.encode.hex_decode(key_data['priv']), + libnacl.encode.hex_decode(key_data['sign'])) + elif 'priv' in key_data and 'pub' in key_data: + return libnacl.public.SecretKey( + libnacl.encode.hex_decode(key_data['priv'])) + elif 'sign' in key_data: + return libnacl.sign.Signer( + libnacl.encode.hex_decode(key_data['sign'])) + elif 'pub' in key_data: + return libnacl.public.PublicKey( + libnacl.encode.hex_decode(key_data['pub'])) + elif 'verify' in key_data: + return libnacl.sign.Verifier(key_data['verify']) + elif 'priv' in key_data: + return libnacl.secret.SecretBox( + libnacl.encode.hex_decode(key_data['priv'])) + raise ValueError('Found no key data') + + +def salsa_key(): + ''' + Generates a salsa2020 key + ''' + return libnacl.randombytes(libnacl.crypto_secretbox_KEYBYTES) + + +def aead_key(): + ''' + Generates an AEAD key (both implementations use the same size) + ''' + return libnacl.randombytes(libnacl.crypto_aead_aes256gcm_KEYBYTES) + + +def rand_aead_nonce(): + ''' + Generates and returns a random bytestring of the size defined in libsodium + as crypto_aead_aes256gcm_NPUBBYTES and crypto_aead_chacha20poly1305_ietf_NPUBBYTES + ''' + return libnacl.randombytes(libnacl.crypto_aead_aes256gcm_NPUBBYTES) + + +def rand_nonce(): + ''' + Generates and returns a random bytestring of the size defined in libsodium + as crypto_box_NONCEBYTES + ''' + return libnacl.randombytes(libnacl.crypto_box_NONCEBYTES) + + +def time_nonce(): + ''' + Generates and returns a nonce as in rand_nonce() but using a timestamp for the first 8 bytes. + + This function now exists mostly for backwards compatibility, as rand_nonce() is usually preferred. + ''' + nonce = rand_nonce() + return (struct.pack('=d', time.time()) + nonce)[:len(nonce)] diff --git a/libnacl/version.py b/libnacl/version.py new file mode 100644 index 0000000..baca2f5 --- /dev/null +++ b/libnacl/version.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +version = "1.8.0" diff --git a/pkg/rpm/python-libnacl.spec b/pkg/rpm/python-libnacl.spec new file mode 100644 index 0000000..6b991c4 --- /dev/null +++ b/pkg/rpm/python-libnacl.spec @@ -0,0 +1,153 @@ +%if 0%{?fedora} > 12 || 0%{?rhel} > 6 +%global with_python3 1 +%endif + +%if 0%{?rhel} == 5 +%global pybasever 2.6 +%endif + +%{!?__python2: %global __python2 /usr/bin/python%{?pybasever}} +%{!?python2_sitearch: %global python2_sitearch %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} +%{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} + +%global srcname libnacl + +Name: python-%{srcname} +Version: 1.4.3 +Release: 1%{?dist} +Summary: Python bindings for libsodium based on ctypes + +Group: Development/Libraries +License: ASL 2.0 +URL: https://github.com/saltstack/libnacl +Source0: https://pypi.python.org/packages/source/l/%{srcname}/%{srcname}-%{version}.tar.gz + +BuildRoot: %{_tmppath}/%{srcname}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildArch: noarch + +BuildRequires: libsodium +Requires: libsodium >= 0.5.0 + +%if ! (0%{?rhel} == 5) +BuildRequires: python +BuildRequires: python-devel +BuildRequires: python-setuptools +%endif + +%if 0%{?with_python3} +BuildRequires: python3-devel +BuildRequires: python3-setuptools +%endif + +%description +This library is used to gain direct access to the functions exposed by Daniel +J. Bernstein's nacl library via libsodium. It has been constructed +to maintain extensive documentation on how to use nacl as well as being +completely portable. The file in libnacl/__init__.py can be pulled out and +placed directly in any project to give a single file binding to all of nacl. + +This is the Python 2 build of the module. + +%if 0%{?with_python3} +%package -n python3-%{srcname} +Summary: Python bindings for libsodium based on ctypes +Group: Development/Libraries +Requires: libsodium + +%description -n python3-%{srcname} +This library is used to gain direct access to the functions exposed by Daniel +J. Bernstein's nacl library via libsodium. It has been constructed +to maintain extensive documentation on how to use nacl as well as being +completely portable. The file in libnacl/__init__.py can be pulled out and +placed directly in any project to give a single file binding to all of nacl. + +This is the Python 3 build of the module. +%endif + +%if 0%{?rhel} == 5 +%package -n python26-%{srcname} +Summary: Python bindings for libsodium based on ctypes +Group: Development/Libraries +BuildRequires: python26 +BuildRequires: libsodium +BuildRequires: python26-devel +Requires: python26 +Requires: libsodium + +%description -n python26-%{srcname} +This library is used to gain direct access to the functions exposed by Daniel +J. Bernstein's nacl library via libsodium. It has been constructed +to maintain extensive documentation on how to use nacl as well as being +completely portable. The file in libnacl/__init__.py can be pulled out and +placed directly in any project to give a single file binding to all of nacl. + +This is the Python 2 build of the module. +%endif + +%prep +%setup -q -n %{srcname}-%{version} + +%if 0%{?with_python3} +rm -rf %{py3dir} +cp -a . %{py3dir} +%endif + +%build +%{__python2} setup.py build + +%if 0%{?with_python3} +pushd %{py3dir} +%{__python3} setup.py build +popd +%endif + +%install +rm -rf %{buildroot} +%{__python2} setup.py install --skip-build --root %{buildroot} + +%if 0%{?with_python3} +pushd %{py3dir} +%{__python3} setup.py install --skip-build --root %{buildroot} +popd +%endif + +%clean +rm -rf %{buildroot} + +%if 0%{?rhel} == 5 +%files -n python26-%{srcname} +%defattr(-,root,root,-) +%{python2_sitelib}/* +%else +%files +%defattr(-,root,root,-) +%{python2_sitelib}/* +%endif + +%if 0%{?with_python3} +%files -n python3-%{srcname} +%defattr(-,root,root,-) +%{python3_sitelib}/* +%endif + +%changelog +* Thu Sep 4 2014 Erik Johnson <erik@saltstack.com> - 1.3.5-1 +- Updated to 1.3.5 + +* Fri Aug 22 2014 Erik Johnson <erik@saltstack.com> - 1.3.3-1 +- Updated to 1.3.3 + +* Fri Aug 8 2014 Erik Johnson <erik@saltstack.com> - 1.3.2-1 +- Updated to 1.3.2 + +* Fri Aug 8 2014 Erik Johnson <erik@saltstack.com> - 1.3.1-1 +- Updated to 1.3.1 + +* Thu Aug 7 2014 Erik Johnson <erik@saltstack.com> - 1.3.0-1 +- Updated to 1.3.0 + +* Fri Jun 20 2014 Erik Johnson <erik@saltstack.com> - 1.1.0-1 +- Updated to 1.1.0 + +* Fri Jun 20 2014 Erik Johnson <erik@saltstack.com> - 1.0.0-1 +- Initial build diff --git a/pkg/suse/python-libnacl.changes b/pkg/suse/python-libnacl.changes new file mode 100644 index 0000000..4740656 --- /dev/null +++ b/pkg/suse/python-libnacl.changes @@ -0,0 +1,15 @@ +------------------------------------------------------------------- +Wed Jul 2 18:28:08 UTC 2014 - aboe76@gmail.com + +- Updated to 1.1.0 + +------------------------------------------------------------------- +Fri Jun 20 15:10:52 UTC 2014 - aboe76@gmail.com + +- Simplified BuildRequirements to libsodium-devel + +------------------------------------------------------------------- +Mon Jun 9 10:53:12 UTC 2014 - aboe76@gmail.com + +- initial package + diff --git a/pkg/suse/python-libnacl.spec b/pkg/suse/python-libnacl.spec new file mode 100644 index 0000000..2dc9810 --- /dev/null +++ b/pkg/suse/python-libnacl.spec @@ -0,0 +1,59 @@ +# +# spec file for package python-libnacl +# +# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +Name: python-libnacl +Version: 1.4.3 +Release: 0 +License: Apache-2.0 +Summary: Python bindings for libsodium based on ctypes +Url: https://github.com/saltstack/libnacl +Group: Development/Languages/Python +Source0: https://pypi.python.org/packages/source/l/libnacl/libnacl-%{version}.tar.gz +BuildRoot: %{_tmppath}/libnacl-%{version}-build + +BuildRequires: python-setuptools +BuildRequires: python-devel +BuildRequires: libsodium-devel +BuildRequires: fdupes + +BuildRoot: %{_tmppath}/%{name}-%{version}-build +%if 0%{?suse_version} && 0%{?suse_version} <= 1110 +%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} +%else +BuildArch: noarch +%endif + +%description +This library is used to gain direct access to the functions exposed by Daniel J. Bernstein's nacl library via libsodium. +It has been constructed to maintain extensive documentation on how to use nacl as well as being completely portable. The file +in libnacl/__init__.py can be pulled out and placed directly in any project to give a single file binding to all of nacl. + +%prep +%setup -q -n libnacl-%{version} + +%build +python setup.py build + +%install +python setup.py install --prefix=%{_prefix} --root=%{buildroot} --optimize=1 +%fdupes %{buildroot}%{_prefix} + +%files +%defattr(-,root,root) +%{python_sitelib}/* + +%changelog
\ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..adf5ed7 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,7 @@ +[bdist_wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..79dda9c --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from setuptools import setup + +NAME = "libnacl" +DESC = "Python bindings for libsodium based on ctypes" + +# Version info -- read without importing +_locals = {} +with open("libnacl/version.py") as fp: + exec(fp.read(), None, _locals) +VERSION = _locals["version"] + +setup( + name=NAME, + version=VERSION, + description=DESC, + author="Thomas S Hatch", + author_email="thatch@saltstack.com", + url="https://libnacl.readthedocs.org/", + classifiers=[ + "Operating System :: OS Independent", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Security :: Cryptography", + ], + packages=["libnacl"], +) diff --git a/tests/runtests.py b/tests/runtests.py new file mode 100644 index 0000000..447c0b3 --- /dev/null +++ b/tests/runtests.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import python libs +import os +import sys +import unittest + +NACL_ROOT = os.path.abspath(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))) +UNIT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), 'unit')) + +sys.path.insert(0, NACL_ROOT) + + +def run_suite(path=UNIT_ROOT): + ''' + Execute the unttest suite + ''' + loader = unittest.TestLoader() + tests = loader.discover(path) + unittest.TextTestRunner(verbosity=2).run(tests) + + + +if __name__ == '__main__': + run_suite() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/unit/test_aead.py b/tests/unit/test_aead.py new file mode 100644 index 0000000..b6dfa05 --- /dev/null +++ b/tests/unit/test_aead.py @@ -0,0 +1,41 @@ +# Import libnacl libs +import libnacl.aead +# Import python libs +import unittest + +class TestAEAD(unittest.TestCase): + ''' + ''' + @unittest.skipUnless(libnacl.HAS_AEAD_AES256GCM, 'AES256-GCM AEAD not available') + def test_gcm_aead(self): + msg = b"You've got two empty halves of coconuts and your bangin' 'em together." + aad = b'\x00\x11\x22\x33' + box = libnacl.aead.AEAD().useAESGCM() + ctxt = box.encrypt(msg, aad) + self.assertNotEqual(msg, ctxt) + + box2 = libnacl.aead.AEAD(box.sk).useAESGCM() + clear1 = box.decrypt(ctxt, len(aad)) + self.assertEqual(msg, clear1) + clear2 = box2.decrypt(ctxt, len(aad)) + self.assertEqual(clear1, clear2) + ctxt2 = box2.encrypt(msg, aad) + clear3 = box.decrypt(ctxt2, len(aad)) + self.assertEqual(clear3, msg) + + @unittest.skipUnless(libnacl.HAS_AEAD_CHACHA20POLY1305_IETF, 'IETF variant of ChaCha20Poly1305 AEAD not available') + def test_ietf_aead(self): + msg = b"Our King? Well i didn't vote for you!!" + aad = b'\x00\x11\x22\x33' + box = libnacl.aead.AEAD() + ctxt = box.encrypt(msg, aad) + self.assertNotEqual(msg, ctxt) + + box2 = libnacl.aead.AEAD(box.sk) + clear1 = box.decrypt(ctxt, len(aad)) + self.assertEqual(msg, clear1) + clear2 = box2.decrypt(ctxt, len(aad)) + self.assertEqual(clear1, clear2) + ctxt2 = box2.encrypt(msg, aad) + clear3 = box.decrypt(ctxt2, len(aad)) + self.assertEqual(clear3, msg) diff --git a/tests/unit/test_auth_verify.py b/tests/unit/test_auth_verify.py new file mode 100644 index 0000000..919011b --- /dev/null +++ b/tests/unit/test_auth_verify.py @@ -0,0 +1,102 @@ +# Import nacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestAuthVerify(unittest.TestCase): + ''' + Test onetimeauth functions + ''' + def test_auth_verify(self): + msg = b'Anybody can invent a cryptosystem he cannot break himself. Except Bruce Schneier.' + key1 = libnacl.utils.salsa_key() + key2 = libnacl.utils.salsa_key() + + sig1 = libnacl.crypto_auth(msg, key1) + sig2 = libnacl.crypto_auth(msg, key2) + + self.assertTrue(libnacl.crypto_auth_verify(sig1, msg, key1)) + self.assertTrue(libnacl.crypto_auth_verify(sig2, msg, key2)) + with self.assertRaises(ValueError) as context: + libnacl.crypto_auth_verify(sig1, msg, key2) + self.assertTrue('Failed to auth msg' in context.exception.args) + + with self.assertRaises(ValueError) as context: + libnacl.crypto_auth_verify(sig2, msg, key1) + self.assertTrue('Failed to auth msg' in context.exception.args) + + def test_onetimeauth_verify(self): + self.assertEqual("poly1305", libnacl.crypto_onetimeauth_primitive()) + + msg = b'Anybody can invent a cryptosystem he cannot break himself. Except Bruce Schneier.' + key1 = libnacl.randombytes(libnacl.crypto_onetimeauth_KEYBYTES) + key2 = libnacl.randombytes(libnacl.crypto_onetimeauth_KEYBYTES) + + sig1 = libnacl.crypto_onetimeauth(msg, key1) + sig2 = libnacl.crypto_onetimeauth(msg, key2) + + with self.assertRaises(ValueError): + libnacl.crypto_onetimeauth(msg, b'too_short') + + with self.assertRaises(ValueError): + libnacl.crypto_onetimeauth_verify(sig1, msg, b'too_short') + + with self.assertRaises(ValueError): + libnacl.crypto_onetimeauth_verify(b'too_short', msg, key1) + + self.assertTrue(libnacl.crypto_onetimeauth_verify(sig1, msg, key1)) + self.assertTrue(libnacl.crypto_onetimeauth_verify(sig2, msg, key2)) + with self.assertRaises(ValueError) as context: + libnacl.crypto_onetimeauth_verify(sig1, msg, key2) + self.assertTrue('Failed to auth message' in context.exception.args) + + with self.assertRaises(ValueError) as context: + libnacl.crypto_onetimeauth_verify(sig2, msg, key1) + self.assertTrue('Failed to auth message' in context.exception.args) + + def test_auth_rejects_wrong_lengths(self): + msg = b'Time is an illusion. Lunchtime doubly so.' + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_auth(msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + def test_auth_verify_rejects_wrong_key_lengths(self): + msg = b"I'd take the awe of understanding over the awe of ignorance any day." + good_key = b'This valid key is 32 bytes long.' + good_token = b'This token is likewise also 32B.' + + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_auth_verify(good_token, msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + for bad_token in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_auth_verify(bad_token, msg, good_key) + self.assertEqual(context.exception.args, ('Invalid authenticator',)) + + def test_onetimeauth_rejects_wrong_lengths(self): + msg = b"Are the most dangerous creatures the ones that use doors or the ones that don't?" + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_onetimeauth(msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + def test_onetimeauth_verify_rejects_wrong_key_lengths(self): + msg = b"Of all the dogs I've known in my life, I've never seen a better driver." + good_key = b'This valid key is 32 bytes long.' + good_token = b'1time tokens=16B' + + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_onetimeauth_verify(good_token, msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + for bad_token in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_onetimeauth_verify(bad_token, msg, good_key) + self.assertEqual(context.exception.args, ('Invalid authenticator',)) diff --git a/tests/unit/test_blake.py b/tests/unit/test_blake.py new file mode 100644 index 0000000..5dfff1d --- /dev/null +++ b/tests/unit/test_blake.py @@ -0,0 +1,39 @@ +# Import nacl libs +import libnacl.blake + +# Import python libs +import unittest + + +class TestBlake(unittest.TestCase): + ''' + Test sign functions + ''' + def test_keyless_blake(self): + msg1 = b'Are you suggesting coconuts migrate?' + msg2 = b'Not at all, they could be carried.' + chash1 = libnacl.crypto_generichash(msg1) + chash2 = libnacl.crypto_generichash(msg2) + self.assertNotEqual(msg1, chash1) + self.assertNotEqual(msg2, chash2) + self.assertNotEqual(chash2, chash1) + + def test_key_blake(self): + msg1 = b'Are you suggesting coconuts migrate?' + msg2 = b'Not at all, they could be carried.' + key1 = libnacl.utils.rand_nonce() + key2 = libnacl.utils.rand_nonce() + khash1_1 = libnacl.blake.Blake2b(msg1, key1).digest() + khash1_1_2 = libnacl.blake.Blake2b(msg1, key1).digest() + khash1_2 = libnacl.blake.Blake2b(msg1, key2).digest() + khash2_1 = libnacl.blake.blake2b(msg2, key1).digest() + khash2_2 = libnacl.blake.blake2b(msg2, key2).digest() + self.assertNotEqual(msg1, khash1_1) + self.assertNotEqual(msg1, khash1_2) + self.assertNotEqual(msg2, khash2_1) + self.assertNotEqual(msg2, khash2_2) + self.assertNotEqual(khash1_1, khash1_2) + self.assertNotEqual(khash2_1, khash2_2) + self.assertNotEqual(khash1_1, khash2_1) + self.assertNotEqual(khash1_2, khash2_2) + self.assertEqual(khash1_1, khash1_1_2) diff --git a/tests/unit/test_dual.py b/tests/unit/test_dual.py new file mode 100644 index 0000000..d300c9e --- /dev/null +++ b/tests/unit/test_dual.py @@ -0,0 +1,54 @@ +# Import libnacl libs +import libnacl.public +import libnacl.dual + +# Import python libs +import unittest + +class TestDual(unittest.TestCase): + ''' + ''' + def test_secretkey(self): + ''' + ''' + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + bob = libnacl.dual.DualSecret() + alice = libnacl.dual.DualSecret() + bob_box = libnacl.public.Box(bob.sk, alice.pk) + alice_box = libnacl.public.Box(alice.sk, bob.pk) + bob_ctxt = bob_box.encrypt(msg) + self.assertNotEqual(msg, bob_ctxt) + bclear = alice_box.decrypt(bob_ctxt) + self.assertEqual(msg, bclear) + alice_ctxt = alice_box.encrypt(msg) + self.assertNotEqual(msg, alice_ctxt) + aclear = alice_box.decrypt(alice_ctxt) + self.assertEqual(msg, aclear) + self.assertNotEqual(bob_ctxt, alice_ctxt) + + def test_publickey(self): + ''' + ''' + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + bob = libnacl.dual.DualSecret() + alice = libnacl.dual.DualSecret() + alice_pk = libnacl.public.PublicKey(alice.pk) + bob_box = libnacl.public.Box(bob.sk, alice_pk) + alice_box = libnacl.public.Box(alice.sk, bob.pk) + bob_ctxt = bob_box.encrypt(msg) + self.assertNotEqual(msg, bob_ctxt) + bclear = alice_box.decrypt(bob_ctxt) + self.assertEqual(msg, bclear) + + def test_sign(self): + msg = (b'Well, that\'s no ordinary rabbit. That\'s the most foul, ' + b'cruel, and bad-tempered rodent you ever set eyes on.') + signer = libnacl.dual.DualSecret() + signed = signer.sign(msg) + signature = signer.signature(msg) + self.assertNotEqual(msg, signed) + veri = libnacl.sign.Verifier(signer.hex_vk()) + verified = veri.verify(signed) + verified2 = veri.verify(signature + msg) + self.assertEqual(verified, msg) + self.assertEqual(verified2, msg) diff --git a/tests/unit/test_public.py b/tests/unit/test_public.py new file mode 100644 index 0000000..61de5a9 --- /dev/null +++ b/tests/unit/test_public.py @@ -0,0 +1,41 @@ +# Import libnacl libs +import libnacl.public + +# Import python libs +import unittest + +class TestPublic(unittest.TestCase): + ''' + ''' + def test_secretkey(self): + ''' + ''' + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + bob = libnacl.public.SecretKey() + alice = libnacl.public.SecretKey() + bob_box = libnacl.public.Box(bob.sk, alice.pk) + alice_box = libnacl.public.Box(alice.sk, bob.pk) + bob_ctxt = bob_box.encrypt(msg) + self.assertNotEqual(msg, bob_ctxt) + bclear = alice_box.decrypt(bob_ctxt) + self.assertEqual(msg, bclear) + alice_ctxt = alice_box.encrypt(msg) + self.assertNotEqual(msg, alice_ctxt) + aclear = alice_box.decrypt(alice_ctxt) + self.assertEqual(msg, aclear) + self.assertNotEqual(bob_ctxt, alice_ctxt) + + def test_publickey(self): + ''' + ''' + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + bob = libnacl.public.SecretKey() + alice = libnacl.public.SecretKey() + alice_pk = libnacl.public.PublicKey(alice.pk) + bob_box = libnacl.public.Box(bob.sk, alice_pk) + alice_box = libnacl.public.Box(alice.sk, bob.pk) + bob_ctxt = bob_box.encrypt(msg) + self.assertNotEqual(msg, bob_ctxt) + bclear = alice_box.decrypt(bob_ctxt) + self.assertEqual(msg, bclear) + diff --git a/tests/unit/test_raw_auth_sym.py b/tests/unit/test_raw_auth_sym.py new file mode 100644 index 0000000..8737f1a --- /dev/null +++ b/tests/unit/test_raw_auth_sym.py @@ -0,0 +1,20 @@ +# Import nacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSecretBox(unittest.TestCase): + ''' + Test sign functions + ''' + def test_secret_box(self): + msg = b'Are you suggesting coconuts migrate?' + sk1 = libnacl.utils.salsa_key() + nonce1 = libnacl.utils.rand_nonce() + enc_msg = libnacl.crypto_secretbox(msg, nonce1, sk1) + self.assertNotEqual(msg, enc_msg) + clear_msg = libnacl.crypto_secretbox_open(enc_msg, nonce1, sk1) + self.assertEqual(msg, clear_msg) diff --git a/tests/unit/test_raw_auth_sym_easy.py b/tests/unit/test_raw_auth_sym_easy.py new file mode 100644 index 0000000..945e6b0 --- /dev/null +++ b/tests/unit/test_raw_auth_sym_easy.py @@ -0,0 +1,20 @@ +# Import nacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSecretBox(unittest.TestCase): + ''' + Test sign functions + ''' + def test_secret_box_easy(self): + msg = b'Are you suggesting coconuts migrate?' + sk1 = libnacl.utils.salsa_key() + nonce1 = libnacl.utils.rand_nonce() + enc_msg = libnacl.crypto_secretbox_easy(msg, nonce1, sk1) + self.assertNotEqual(msg, enc_msg) + clear_msg = libnacl.crypto_secretbox_open_easy(enc_msg, nonce1, sk1) + self.assertEqual(msg, clear_msg) diff --git a/tests/unit/test_raw_generichash.py b/tests/unit/test_raw_generichash.py new file mode 100644 index 0000000..befca93 --- /dev/null +++ b/tests/unit/test_raw_generichash.py @@ -0,0 +1,39 @@ +# Import nacl libs +import libnacl + +# Import python libs +import unittest + + +class TestGenericHash(unittest.TestCase): + ''' + Test sign functions + ''' + def test_keyless_generichash(self): + msg1 = b'Are you suggesting coconuts migrate?' + msg2 = b'Not at all, they could be carried.' + chash1 = libnacl.crypto_generichash(msg1) + chash2 = libnacl.crypto_generichash(msg2) + self.assertNotEqual(msg1, chash1) + self.assertNotEqual(msg2, chash2) + self.assertNotEqual(chash2, chash1) + + def test_key_generichash(self): + msg1 = b'Are you suggesting coconuts migrate?' + msg2 = b'Not at all, they could be carried.' + key1 = libnacl.utils.rand_nonce() + key2 = libnacl.utils.rand_nonce() + khash1_1 = libnacl.crypto_generichash(msg1, key1) + khash1_1_2 = libnacl.crypto_generichash(msg1, key1) + khash1_2 = libnacl.crypto_generichash(msg1, key2) + khash2_1 = libnacl.crypto_generichash(msg2, key1) + khash2_2 = libnacl.crypto_generichash(msg2, key2) + self.assertNotEqual(msg1, khash1_1) + self.assertNotEqual(msg1, khash1_2) + self.assertNotEqual(msg2, khash2_1) + self.assertNotEqual(msg2, khash2_2) + self.assertNotEqual(khash1_1, khash1_2) + self.assertNotEqual(khash2_1, khash2_2) + self.assertNotEqual(khash1_1, khash2_1) + self.assertNotEqual(khash1_2, khash2_2) + self.assertEqual(khash1_1, khash1_1_2) diff --git a/tests/unit/test_raw_hash.py b/tests/unit/test_raw_hash.py new file mode 100644 index 0000000..a6dd50d --- /dev/null +++ b/tests/unit/test_raw_hash.py @@ -0,0 +1,29 @@ +# Import nacl libs +import libnacl +from hashlib import sha256, sha512 + +# Import python libs +import unittest + + +class TestHash(unittest.TestCase): + """ + Test sign functions + """ + def test_hash(self): + msg1 = b'Are you suggesting coconuts migrate?' + msg2 = b'Not at all, they could be carried.' + chash1 = libnacl.crypto_hash(msg1) + chash2 = libnacl.crypto_hash(msg2) + self.assertNotEqual(msg1, chash1) + self.assertNotEqual(msg2, chash2) + self.assertNotEqual(chash2, chash1) + + ref256 = sha256(msg1) + self.assertEqual(ref256.digest_size, libnacl.crypto_hash_sha256_BYTES) + self.assertEqual(ref256.digest(), libnacl.crypto_hash_sha256(msg1)) + + ref512 = sha512(msg1) + self.assertEqual(ref512.digest_size, libnacl.crypto_hash_sha512_BYTES) + self.assertEqual(ref512.digest(), libnacl.crypto_hash_sha512(msg1)) + diff --git a/tests/unit/test_raw_public.py b/tests/unit/test_raw_public.py new file mode 100644 index 0000000..85fd63f --- /dev/null +++ b/tests/unit/test_raw_public.py @@ -0,0 +1,91 @@ +# Import libnacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestPublic(unittest.TestCase): + ''' + Test public functions + ''' + def test_gen(self): + pk1, sk1 = libnacl.crypto_box_keypair() + pk2, sk2 = libnacl.crypto_box_keypair() + pk3, sk3 = libnacl.crypto_box_keypair() + self.assertEqual(len(pk1), libnacl.crypto_box_PUBLICKEYBYTES) + self.assertEqual(len(sk1), libnacl.crypto_box_PUBLICKEYBYTES) + self.assertEqual(len(pk2), libnacl.crypto_box_PUBLICKEYBYTES) + self.assertEqual(len(sk2), libnacl.crypto_box_PUBLICKEYBYTES) + self.assertEqual(len(pk3), libnacl.crypto_box_PUBLICKEYBYTES) + self.assertEqual(len(sk3), libnacl.crypto_box_PUBLICKEYBYTES) + self.assertNotEqual(pk1, sk1) + self.assertNotEqual(pk2, sk2) + self.assertNotEqual(pk3, sk3) + self.assertNotEqual(pk1, pk2) + self.assertNotEqual(pk1, pk3) + self.assertNotEqual(sk1, sk2) + self.assertNotEqual(sk2, sk3) + + def test_box(self): + msg = b'Are you suggesting coconuts migrate?' + # run 1 + nonce1 = libnacl.utils.rand_nonce() + pk1, sk1 = libnacl.crypto_box_keypair() + pk2, sk2 = libnacl.crypto_box_keypair() + enc_msg = libnacl.crypto_box(msg, nonce1, pk2, sk1) + self.assertNotEqual(msg, enc_msg) + clear_msg = libnacl.crypto_box_open(enc_msg, nonce1, pk1, sk2) + self.assertEqual(clear_msg, msg) + # run 2 + nonce2 = libnacl.utils.rand_nonce() + pk3, sk3 = libnacl.crypto_box_keypair() + pk4, sk4 = libnacl.crypto_box_keypair() + enc_msg2 = libnacl.crypto_box(msg, nonce2, pk4, sk3) + self.assertNotEqual(msg, enc_msg2) + clear_msg2 = libnacl.crypto_box_open(enc_msg2, nonce2, pk3, sk4) + self.assertEqual(clear_msg2, msg) + # Check bits + self.assertNotEqual(nonce1, nonce2) + self.assertNotEqual(enc_msg, enc_msg2) + + def test_boxnm(self): + msg = b'Are you suggesting coconuts migrate?' + # run 1 + nonce1 = libnacl.utils.rand_nonce() + pk1, sk1 = libnacl.crypto_box_keypair() + pk2, sk2 = libnacl.crypto_box_keypair() + k1 = libnacl.crypto_box_beforenm(pk2, sk1) + k2 = libnacl.crypto_box_beforenm(pk1, sk2) + enc_msg = libnacl.crypto_box_afternm(msg, nonce1, k1) + self.assertNotEqual(msg, enc_msg) + clear_msg = libnacl.crypto_box_open_afternm(enc_msg, nonce1, k2) + self.assertEqual(clear_msg, msg) + + def test_box_seal(self): + msg = b'Are you suggesting coconuts migrate?' + # run 1 + pk, sk = libnacl.crypto_box_keypair() + enc_msg = libnacl.crypto_box_seal(msg, pk) + self.assertNotEqual(msg, enc_msg) + clear_msg = libnacl.crypto_box_seal_open(enc_msg, pk, sk) + self.assertEqual(clear_msg, msg) + # run 2 + pk2, sk2 = libnacl.crypto_box_keypair() + enc_msg2 = libnacl.crypto_box_seal(msg, pk2) + self.assertNotEqual(msg, enc_msg2) + clear_msg2 = libnacl.crypto_box_seal_open(enc_msg2, pk2, sk2) + self.assertEqual(clear_msg2, msg) + # Check bits + self.assertNotEqual(enc_msg, enc_msg2) + + def test_scalarmult_rejects_wrong_length(self): + good_key = b'This valid key is 32 bytes long.' + + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_scalarmult_base(bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + self.assertEqual(libnacl.crypto_box_PUBLICKEYBYTES, len(libnacl.crypto_scalarmult_base(good_key))) diff --git a/tests/unit/test_raw_random.py b/tests/unit/test_raw_random.py new file mode 100644 index 0000000..8821731 --- /dev/null +++ b/tests/unit/test_raw_random.py @@ -0,0 +1,104 @@ +""" +Basic tests for randombytes_* functions +""" + +import libnacl +import unittest + + +class TestRandomBytes(unittest.TestCase): + def test_randombytes_random(self): + self.assertIsInstance(libnacl.randombytes_random(), int) + + def test_randombytes_uniform(self): + self.assertIsInstance(libnacl.randombytes_uniform(200), int) + + freq = {libnacl.randombytes_uniform(256): 1 for _ in range(65536)} + + self.assertEqual(256, len(freq)) + self.assertTrue(all(freq.values())) + + def test_randombytes(self): + 'copied from libsodium default/randombytes.c' + + data = libnacl.randombytes(65536) + + freq = {x: 1 for x in data} + + self.assertEqual(256, len(freq)) + self.assertTrue(all(freq.values())) + + def test_randombytes_buf_deterministic(self): + + seed = libnacl.randombytes_buf(32) + seed2 = libnacl.randombytes_buf(32) + data = libnacl.randombytes_buf_deterministic(32, seed) + data2 = libnacl.randombytes_buf_deterministic(32, seed) + data3 = libnacl.randombytes_buf_deterministic(32, seed2) + + self.assertEqual(32, len(data)) + self.assertEqual(32, len(data)) + self.assertEqual(32, len(data)) + self.assertEqual(data, data2) + self.assertNotEqual(data, data3) + + def test_crypto_kdf_keygen(self): + + master_key = libnacl.crypto_kdf_keygen() + + freq = {x: 1 for x in master_key} + + self.assertEqual(32, len(master_key)) + self.assertTrue(all(freq.values())) + + def test_crypto_kdf_derive_from_key(self): + + master_key = libnacl.crypto_kdf_keygen() + subkey = libnacl.crypto_kdf_derive_from_key(16, 1, "Examples", master_key) + subkey2 = libnacl.crypto_kdf_derive_from_key(16, 1, "Examples", master_key) + subkey3 = libnacl.crypto_kdf_derive_from_key(16, 2, "Examples", master_key) + + self.assertEqual(16, len(subkey)) + self.assertEqual(16, len(subkey2)) + self.assertEqual(16, len(subkey3)) + self.assertEqual(subkey, subkey2) + self.assertNotEqual(subkey, subkey3) + + def test_crypto_kx_keypair(self): + pk, sk = libnacl.crypto_kx_keypair() + self.assertEqual(32, len(pk)) + self.assertEqual(32, len(sk)) + + def test_crypto_kx_seed_keypair(self): + seed = libnacl.randombytes_buf(32) + seed2 = libnacl.randombytes_buf(32) + pk, sk = libnacl.crypto_kx_seed_keypair(seed) + pk2, sk2 = libnacl.crypto_kx_seed_keypair(seed) + pk3, sk3 = libnacl.crypto_kx_seed_keypair(seed2) + + self.assertEqual(pk, pk2) + self.assertNotEqual(pk, pk3) + self.assertEqual(sk, sk2) + self.assertNotEqual(sk, sk3) + + def test_crypto_kx_client_session_keys(self): + client_pk, client_sk = libnacl.crypto_kx_keypair() + server_pk, server_sk = libnacl.crypto_kx_keypair() + rx, tx, status = libnacl.crypto_kx_client_session_keys(client_pk, client_sk, server_pk) + rx2, tx2, status = libnacl.crypto_kx_client_session_keys(client_pk, client_sk, server_pk) + + self.assertEqual(32, len(rx)) + self.assertEqual(32, len(tx)) + self.assertEqual(rx, rx2) + self.assertEqual(tx, tx2) + + def test_crypto_kx_server_session_keys(self): + client_pk, client_sk = libnacl.crypto_kx_keypair() + server_pk, server_sk = libnacl.crypto_kx_keypair() + rx, tx, status = libnacl.crypto_kx_server_session_keys(client_pk, client_sk, server_pk) + rx2, tx2, status = libnacl.crypto_kx_server_session_keys(client_pk, client_sk, server_pk) + + self.assertEqual(32, len(rx)) + self.assertEqual(32, len(tx)) + self.assertEqual(rx, rx2) + self.assertEqual(tx, tx2) diff --git a/tests/unit/test_raw_secret.py b/tests/unit/test_raw_secret.py new file mode 100644 index 0000000..1ca0296 --- /dev/null +++ b/tests/unit/test_raw_secret.py @@ -0,0 +1,33 @@ +# Import libnacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSecret(unittest.TestCase): + """ + Test secret functions + """ + def test_secretbox(self): + msg = b'Are you suggesting coconuts migrate?' + + nonce = libnacl.utils.rand_nonce() + key = libnacl.utils.salsa_key() + + c = libnacl.crypto_secretbox(msg, nonce, key) + m = libnacl.crypto_secretbox_open(c, nonce, key) + self.assertEqual(msg, m) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox(msg, b'too_short', key) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox(msg, nonce, b'too_short') + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_open(c, b'too_short', key) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_open(c, nonce, b'too_short') diff --git a/tests/unit/test_raw_secret_easy.py b/tests/unit/test_raw_secret_easy.py new file mode 100644 index 0000000..77e0966 --- /dev/null +++ b/tests/unit/test_raw_secret_easy.py @@ -0,0 +1,33 @@ +# Import libnacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSecret(unittest.TestCase): + """ + Test secret functions + """ + def test_secretbox_easy(self): + msg = b'Are you suggesting coconuts migrate?' + + nonce = libnacl.utils.rand_nonce() + key = libnacl.utils.salsa_key() + + c = libnacl.crypto_secretbox_easy(msg, nonce, key) + m = libnacl.crypto_secretbox_open_easy(c, nonce, key) + self.assertEqual(msg, m) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_easy(msg, b'too_short', key) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_easy(msg, nonce, b'too_short') + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_open_easy(c, b'too_short', key) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_open_easy(c, nonce, b'too_short') diff --git a/tests/unit/test_raw_sign.py b/tests/unit/test_raw_sign.py new file mode 100644 index 0000000..db1100d --- /dev/null +++ b/tests/unit/test_raw_sign.py @@ -0,0 +1,38 @@ +# Import libnacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSign(unittest.TestCase): + ''' + Test sign functions + ''' + def test_gen(self): + vk1, sk1 = libnacl.crypto_sign_keypair() + vk2, sk2 = libnacl.crypto_sign_keypair() + vk3, sk3 = libnacl.crypto_sign_keypair() + self.assertEqual(len(vk1), libnacl.crypto_sign_PUBLICKEYBYTES) + self.assertEqual(len(sk1), libnacl.crypto_sign_SECRETKEYBYTES) + self.assertEqual(len(vk2), libnacl.crypto_sign_PUBLICKEYBYTES) + self.assertEqual(len(sk2), libnacl.crypto_sign_SECRETKEYBYTES) + self.assertEqual(len(vk3), libnacl.crypto_sign_PUBLICKEYBYTES) + self.assertEqual(len(sk3), libnacl.crypto_sign_SECRETKEYBYTES) + self.assertNotEqual(vk1, sk1) + self.assertNotEqual(vk2, sk2) + self.assertNotEqual(vk3, sk3) + self.assertNotEqual(vk1, vk2) + self.assertNotEqual(vk1, vk3) + self.assertNotEqual(sk1, sk2) + self.assertNotEqual(sk2, sk3) + + def test_box(self): + msg = b'Are you suggesting coconuts migrate?' + # run 1 + vk1, sk1 = libnacl.crypto_sign_keypair() + sig = libnacl.crypto_sign(msg, sk1) + self.assertEqual(msg, sig[libnacl.crypto_sign_BYTES:]) + sig_msg = libnacl.crypto_sign_open(sig, vk1) + self.assertEqual(msg, sig_msg) diff --git a/tests/unit/test_save.py b/tests/unit/test_save.py new file mode 100644 index 0000000..ca98522 --- /dev/null +++ b/tests/unit/test_save.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# Import libnacl libs +import libnacl.dual +import libnacl.secret +import libnacl.sign +import libnacl.utils + +# Import pythonlibs +import os +import stat +import unittest +import tempfile +import sys + +class TestSave(unittest.TestCase): + ''' + ''' + def test_save_load(self): + msg = b'then leap out of the rabbit, taking the French by surprise' + bob = libnacl.dual.DualSecret() + alice = libnacl.dual.DualSecret() + fh_, bob_path = tempfile.mkstemp() + os.close(fh_) + fh_, alice_path = tempfile.mkstemp() + os.close(fh_) + bob.save(bob_path) + alice.save(alice_path) + bob_box = libnacl.public.Box(bob, alice.pk) + alice_box = libnacl.public.Box(alice, bob.pk) + bob_enc = bob_box.encrypt(msg) + alice_enc = alice_box.encrypt(msg) + bob_load = libnacl.utils.load_key(bob_path) + alice_load = libnacl.utils.load_key(alice_path) + bob_load_box = libnacl.public.Box(bob_load, alice_load.pk) + alice_load_box = libnacl.public.Box(alice_load, bob_load.pk) + self.assertEqual(bob.sk, bob_load.sk) + self.assertEqual(bob.pk, bob_load.pk) + self.assertEqual(bob.vk, bob_load.vk) + self.assertEqual(bob.seed, bob_load.seed) + self.assertEqual(alice.sk, alice_load.sk) + self.assertEqual(alice.pk, alice_load.pk) + self.assertEqual(alice.vk, alice_load.vk) + self.assertEqual(alice.seed, alice_load.seed) + bob_dec = alice_load_box.decrypt(bob_enc) + alice_dec = bob_load_box.decrypt(alice_enc) + self.assertEqual(bob_dec, msg) + self.assertEqual(alice_dec, msg) + + bob2 = libnacl.utils.load_key(bob_path) + self.assertEqual(bob.sk, bob2.sk) + self.assertEqual(bob.pk, bob2.pk) + self.assertEqual(bob.vk, bob2.vk) + + os.remove(bob_path) + os.remove(alice_path) + + def test_save_load_secret(self): + msg = b'then leap out of the rabbit, taking the French by surprise' + box = libnacl.secret.SecretBox() + fh_, box_path = tempfile.mkstemp() + os.close(fh_) + box.save(box_path) + lbox = libnacl.utils.load_key(box_path) + ctxt = box.encrypt(msg) + out_msg = lbox.decrypt(ctxt) + self.assertEqual(msg, out_msg) + + def test_save_load_sign(self): + msg = b'then leap out of the rabbit, taking the French by surprise' + signer = libnacl.sign.Signer() + fh_, sign_path = tempfile.mkstemp() + os.close(fh_) + signer.save(sign_path) + signer_load = libnacl.utils.load_key(sign_path) + signed1 = signer.sign(msg) + signed2 = signer_load.sign(msg) + self.assertEqual(signed1, signed2) + os.remove(sign_path) + + def test_save_perms(self): + bob = libnacl.dual.DualSecret() + fh_, bob_path = tempfile.mkstemp() + os.close(fh_) + bob.save(bob_path) + stats = os.stat(bob_path) + expected_perms = 0o100600 if sys.platform != 'win32' else 0o100666 + self.assertEqual(stats[stat.ST_MODE], expected_perms) + os.remove(bob_path) diff --git a/tests/unit/test_seal.py b/tests/unit/test_seal.py new file mode 100644 index 0000000..0f68c13 --- /dev/null +++ b/tests/unit/test_seal.py @@ -0,0 +1,36 @@ +# Import libnacl libs +import libnacl.sealed +import libnacl.public + +# Import python libs +import unittest + +class TestSealed(unittest.TestCase): + ''' + ''' + def test_secretkey(self): + ''' + ''' + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + key = libnacl.public.SecretKey() + box = libnacl.sealed.SealedBox(key) + ctxt = box.encrypt(msg) + self.assertNotEqual(msg, ctxt) + bclear = box.decrypt(ctxt) + self.assertEqual(msg, bclear) + + def test_publickey_only(self): + ''' + ''' + msg = b'You\'ve got two empty halves of coconut and you\'re bangin\' \'em together.' + key = libnacl.public.SecretKey() + key_public = libnacl.public.PublicKey(key.pk) + + box = libnacl.sealed.SealedBox(key_public) + ctxt = box.encrypt(msg) + self.assertNotEqual(msg, ctxt) + + decrypting_box = libnacl.sealed.SealedBox(key) + bclear = decrypting_box.decrypt(ctxt) + self.assertEqual(msg, bclear) + diff --git a/tests/unit/test_secret.py b/tests/unit/test_secret.py new file mode 100644 index 0000000..3e49cc2 --- /dev/null +++ b/tests/unit/test_secret.py @@ -0,0 +1,22 @@ +# Import libnacl libs +import libnacl.secret +# Import python libs +import unittest + +class TestSecret(unittest.TestCase): + ''' + ''' + def test_secret(self): + msg = b'But then of course African swallows are not migratory.' + box = libnacl.secret.SecretBox() + ctxt = box.encrypt(msg) + self.assertNotEqual(msg, ctxt) + box2 = libnacl.secret.SecretBox(box.sk) + clear1 = box.decrypt(ctxt) + self.assertEqual(msg, clear1) + clear2 = box2.decrypt(ctxt) + self.assertEqual(clear1, clear2) + ctxt2 = box2.encrypt(msg) + clear3 = box.decrypt(ctxt2) + self.assertEqual(clear3, msg) + diff --git a/tests/unit/test_secret_easy.py b/tests/unit/test_secret_easy.py new file mode 100644 index 0000000..f77fdfc --- /dev/null +++ b/tests/unit/test_secret_easy.py @@ -0,0 +1,22 @@ +# Import libnacl libs +import libnacl.secret_easy +# Import python libs +import unittest + +class TestSecretEasy(unittest.TestCase): + ''' + ''' + def test_secret(self): + msg = b'But then of course African swallows are not migratory.' + box = libnacl.secret_easy.SecretBoxEasy() + ctxt = box.encrypt(msg) + self.assertNotEqual(msg, ctxt) + box2 = libnacl.secret_easy.SecretBoxEasy(box.sk) + clear1 = box.decrypt(ctxt) + self.assertEqual(msg, clear1) + clear2 = box2.decrypt(ctxt) + self.assertEqual(clear1, clear2) + ctxt2 = box2.encrypt(msg) + clear3 = box.decrypt(ctxt2) + self.assertEqual(clear3, msg) + diff --git a/tests/unit/test_sign.py b/tests/unit/test_sign.py new file mode 100644 index 0000000..b639c95 --- /dev/null +++ b/tests/unit/test_sign.py @@ -0,0 +1,93 @@ +# Import libnacl libs +import libnacl.sign + +# Import pythonlibs +import unittest + + +class TestSigning(unittest.TestCase): + ''' + ''' + def test_sign(self): + msg = (b'Well, that\'s no ordinary rabbit. That\'s the most foul, ' + b'cruel, and bad-tempered rodent you ever set eyes on.') + signer = libnacl.sign.Signer() + signed = signer.sign(msg) + signature = signer.signature(msg) + self.assertNotEqual(msg, signed) + veri = libnacl.sign.Verifier(signer.hex_vk()) + verified = veri.verify(signed) + verified2 = veri.verify(signature + msg) + self.assertEqual(verified, msg) + self.assertEqual(verified2, msg) + + def test_key_decomposition(self): + prv_key = b'The two halves are understood to' + pub_key = b'be essentially arbitrary values.' + secret_key = prv_key + pub_key + # The following functions should simply decompose a secret key + # without performing real computation. libsodium understands secret + # keys to be (private seed bytes || derived public key bytes). + self.assertEqual(prv_key, libnacl.crypto_sign_ed25519_sk_to_seed(secret_key)) + self.assertEqual(pub_key, libnacl.crypto_sign_ed25519_sk_to_pk(secret_key)) + + def test_key_decomposition_rejects_wrong_key_lengths(self): + """ + Too few bytes in a key passed through to libsodium will lead to bytes past the end + of the string being read. We should be guarding against this dangerous case. + """ + for test_func in (libnacl.crypto_sign_ed25519_sk_to_seed, libnacl.crypto_sign_ed25519_sk_to_pk): + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + test_func(bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + def test_sign_rejects_wrong_key_lengths(self): + """ + Too few bytes in a key passed through to libsodium will lead to bytes past the end + of the string being read. We should be guarding against this dangerous case. + """ + msg = b'The message does not matter.' + for test_func in (libnacl.crypto_sign, libnacl.crypto_sign_detached): + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + test_func(msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + def test_open_rejects_wrong_key_lengths(self): + """ + Too few bytes in a key passed through to libsodium will lead to bytes past the end + of the string being read. We should be guarding against this dangerous case. + """ + msg = b'The message does not matter.' + good_key = b'This valid key is 32 bytes long.' + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_sign_open(msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid public key',)) + + with self.assertRaises(ValueError) as context: + libnacl.crypto_sign_open(msg, good_key) + self.assertEqual(context.exception.args, ('Failed to validate message',)) + + def test_verify_detached_rejects_wrong_key_lengths(self): + """ + Too few bytes in a key passed through to libsodium will lead to bytes past the end + of the string being read. We should be guarding against this dangerous case. + """ + msg = b'The message does not matter.' + good_signature = b'This is a valid signature; it is 64 bytes long, no more, no less' + good_key = b'This valid key is 32 bytes long.' + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_sign_verify_detached(good_signature, msg, bad_key) + self.assertEqual(context.exception.args, ('Invalid public key',)) + + for bad_signature in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_sign_verify_detached(bad_signature, msg, good_key) + self.assertEqual(context.exception.args, ('Invalid signature',)) + + with self.assertRaises(ValueError) as context: + libnacl.crypto_sign_verify_detached(good_signature, msg, good_key) + self.assertEqual(context.exception.args, ('Failed to validate message',)) diff --git a/tests/unit/test_stream.py b/tests/unit/test_stream.py new file mode 100644 index 0000000..3629e9e --- /dev/null +++ b/tests/unit/test_stream.py @@ -0,0 +1,43 @@ +# Import libnacl libs +import libnacl.sign + +# Import pythonlibs +import unittest + + +class TestStream(unittest.TestCase): + def test_stream_rejects_wrong_lengths(self): + """ + Too few bytes in a key or nonce passed through to libsodium will lead to bytes past the end + of the string being read. We should be guarding against this dangerous case. + """ + msg_len = 100 # whatever + good_nonce= b'Nonces must be 24 bytes.' + good_key = b'This valid key is 32 bytes long.' + for bad_nonce in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_stream(msg_len, bad_nonce, good_key) + self.assertEqual(context.exception.args, ('Invalid nonce',)) + + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_stream(msg_len, good_nonce, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) + + def test_stream_xor_rejects_wrong_lengths(self): + """ + Too few bytes in a key or nonce passed through to libsodium will lead to bytes past the end + of the string being read. We should be guarding against this dangerous case. + """ + msg = b'The message does not matter.' + good_nonce = b'Nonces must be 24 bytes.' + good_key = b'This valid key is 32 bytes long.' + for bad_nonce in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_stream_xor(msg, bad_nonce, good_key) + self.assertEqual(context.exception.args, ('Invalid nonce',)) + + for bad_key in (b'too short', b'too long' * 100): + with self.assertRaises(ValueError) as context: + libnacl.crypto_stream_xor(msg, good_nonce, bad_key) + self.assertEqual(context.exception.args, ('Invalid secret key',)) diff --git a/tests/unit/test_verify.py b/tests/unit/test_verify.py new file mode 100644 index 0000000..ea872f8 --- /dev/null +++ b/tests/unit/test_verify.py @@ -0,0 +1,76 @@ +"""
+Basic tests for verify functions
+"""
+
+import libnacl
+import unittest
+
+
+# These are copied from libsodium test suite
+class TestVerify(unittest.TestCase):
+ def test_verify16(self):
+ v16 = libnacl.randombytes_buf(16)
+ v16x = v16[:]
+ self.assertTrue(libnacl.crypto_verify_16(v16, v16x))
+ self.assertTrue(libnacl.bytes_eq(v16, v16x))
+ v16x = bytearray(v16x)
+ i = libnacl.randombytes_random() & 15
+ v16x[i] = (v16x[i] + 1) % 256
+ v16x = bytes(v16x)
+ self.assertFalse(libnacl.crypto_verify_16(v16, v16x))
+ self.assertFalse(libnacl.bytes_eq(v16, v16x))
+
+ self.assertEqual(libnacl.crypto_verify_16_BYTES, 16)
+
+ def test_verify32(self):
+ v32 = libnacl.randombytes_buf(32)
+ v32x = v32[:]
+ self.assertTrue(libnacl.crypto_verify_32(v32, v32x))
+ self.assertTrue(libnacl.bytes_eq(v32, v32x))
+ v32x = bytearray(v32x)
+ i = libnacl.randombytes_random() & 31
+ v32x[i] = (v32x[i] + 1) % 256
+ v32x = bytes(v32x)
+ self.assertFalse(libnacl.crypto_verify_32(v32, v32x))
+ self.assertFalse(libnacl.bytes_eq(v32, v32x))
+
+ self.assertEqual(libnacl.crypto_verify_32_BYTES, 32)
+
+ def test_verify64(self):
+ v64 = libnacl.randombytes_buf(64)
+ v64x = v64[:]
+ self.assertTrue(libnacl.crypto_verify_64(v64, v64x))
+ self.assertTrue(libnacl.bytes_eq(v64, v64x))
+ v64x = bytearray(v64x)
+ i = libnacl.randombytes_random() & 63
+ v64x[i] = (v64x[i] + 1) % 256
+ v64x = bytes(v64x)
+ self.assertFalse(libnacl.crypto_verify_64(v64, v64x))
+ self.assertFalse(libnacl.bytes_eq(v64, v64x))
+
+ self.assertEqual(libnacl.crypto_verify_64_BYTES, 64)
+
+
+class TestVerifyBytesEq(unittest.TestCase):
+ def test_equal(self):
+ a = libnacl.randombytes_buf(122)
+ b = a[:]
+ self.assertTrue(libnacl.bytes_eq(a, b))
+
+ def test_different(self):
+ a = libnacl.randombytes_buf(122)
+ b = bytearray(a)
+ b[87] = (b[87] + 1) % 256
+ b = bytes(b)
+ self.assertFalse(libnacl.bytes_eq(a, b))
+
+ def test_invalid_type(self):
+ a = libnacl.randombytes_buf(122)
+ b = bytearray(a)
+ with self.assertRaises(TypeError):
+ libnacl.bytes_eq(a, b)
+
+ def test_different_length(self):
+ a = libnacl.randombytes_buf(122)
+ b = a[:-1]
+ self.assertFalse(libnacl.bytes_eq(a, b))
diff --git a/tests/unit/test_version.py b/tests/unit/test_version.py new file mode 100644 index 0000000..781bde7 --- /dev/null +++ b/tests/unit/test_version.py @@ -0,0 +1,22 @@ +""" +Basic tests for version functions +""" + +import libnacl +import unittest + + +# These are copied from libsodium test suite +class TestSodiumVersion(unittest.TestCase): + def test_version_string(self): + self.assertIsNotNone(libnacl.sodium_version_string()) + + def test_library_version_major(self): + # Using assertTrue to keep tests "uniform" and keep compatibility with + # Python 2.6 + self.assertTrue(libnacl.sodium_library_version_major() > 0) + + def test_library_version_minor(self): + # Using assertTrue to keep tests "uniform" and keep compatibility with + # Python 2.6 (assertGreaterEqual appeared in Python 2.7 only) + self.assertTrue(libnacl.sodium_library_version_minor() >= 0) |