commit e27a6fed945b6d8b69f89141580cf2f5e0f4cc05 Author: Kyle Isom Date: Thu Dec 17 01:36:09 2015 -0800 Initial import. Still needs documentation. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5e54054 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 K. Isom + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..1d595f5 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,27 @@ +ACLOCAL_AMFLAGS = -I m4 +SUBDIRS = doc src + +TESTS = src/emsha_core_test \ + src/emsha_sha256_test \ + src/emsha_hmac_test \ + src/emsha_mem_test \ + src/emsha_static_mem_test + +dist_data_DATA = LICENSE \ + README.rst +dist_noinst_DATA = autobuild \ + doc/sphinx/source \ + doc/sphinx/Makefile + +.PHONY: valgrind-check +valgrind-check: + cd src && make $@ + +.PHONY: cloc-report +cloc-report: + cd src && make $@ + +.PHONY: coverity-scan +coverity-scan: + cd src && make $@ + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..d4c56ac --- /dev/null +++ b/README.rst @@ -0,0 +1,61 @@ +libemsha +======== + +This library is an MIT-licensed HMAC-SHA-256 C++11 library designed +for embedded systems. It is built following the JPL `Power of Ten +`_ rules. It was written in +response to a need for a standalone HMAC-SHA-256 package that could run +on several platforms. + + +------------------------------- +Getting and Building the Source +------------------------------- + +The source code is available via `Github +`_; each version should be git tagged. :: + + git clone https://github.com/kisom/libemsha + git clone git@github.com:kisom/libemsha + +The current release is `1.0.0 `_. + +The project is built using Autotools and ``make``. + +When building from a git checkout, the `autobuild` script will bootstrap +the project from the autotools sources (e.g. via ``autoreconf -i``), +run ``configure`` (by default to use clang), and attempt to build the library +and run the unit tests. + +Once the autotools infrastructure has been bootstrapped, the following +should work: :: + + ./configure && make && make check && make install + +There are three flags to ``configure`` that might be useful: + ++ ``--disable-hexstring`` disables the provided ``hexstring`` function; + while this might be useful in many cases, it also adds extra size to + the code. + ++ ``--disable-hexlut`` disables the larger lookup table used by + ``hexstring``, which can save around a kilobyte of program space. If + the ``hexstring`` function is disabled, this option has no effect. + ++ ``--disable-selftest`` disables the internal self-tests, which can + reclaim some additional program space. + + +------------- +Documentation +------------- + +Documentation is currently done with `Sphinx `_. +See ``doc/sphinx/``. + + +See also +-------- + ++ `FIPS 180-4, FIPS 198-1 `_ + diff --git a/TODO.rst b/TODO.rst new file mode 100644 index 0000000..bb015f6 --- /dev/null +++ b/TODO.rst @@ -0,0 +1,13 @@ +==== +TODO +==== + +[ ] Documentation for ``HMAC`` + +[ ] Documentation for miscellaneous functions + +[ ] Travis with static analysers + + [ ] Coverity? + [ ] cppcheck + diff --git a/autobuild b/autobuild new file mode 100755 index 0000000..35236a0 --- /dev/null +++ b/autobuild @@ -0,0 +1,6 @@ +#!/bin/sh + +CXX=g++ +command -v clang 2>&1 > /dev/null && CXX=clang++ +[ -d m4 ] || mkdir m4 +autoreconf -i && ./configure CXX=$CXX && make && make check diff --git a/cleanroom b/cleanroom new file mode 100755 index 0000000..8d9e5db --- /dev/null +++ b/cleanroom @@ -0,0 +1,37 @@ +#!/bin/sh + +echo ' ____ _ _ _ ____ _____ ____ ' +echo ' | _ \ / \ | \ | |/ ___| ____| _ \ ' +echo ' | | | |/ _ \ | \| | | _| _| | |_) |' +echo ' | |_| / ___ \| |\ | |_| | |___| _ < ' +echo ' |____/_/ \_\_| \_|\____|_____|_| \_\' +echo '' + +echo "[!] This script will destroy anything not tracked by git." +echo "[!] Waiting 5 seconds before running. Press ^C to abort." + +echo -n "5" +sleep 1 +echo -n " 4" +sleep 1 +echo -n " 3" +sleep 1 +echo -n " 2" +sleep 1 +echo -n " 1" +sleep 1 +echo " nuking from orbit!" +sleep 1 + +echo "" +git clean -fxd + +echo "" +echo "-------------------------------------------------------------" +echo "If you wish to make an apple pie from scratch, you must first" +echo "create the universe." +echo " -- Carl Fucking Sagan" +echo "-------------------------------------------------------------" +echo "" +./autobuild + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..7b2b651 --- /dev/null +++ b/configure.ac @@ -0,0 +1,58 @@ +AC_PREREQ([2.69]) +AC_INIT([libemsha], + [1.0.0-RC1], + [coder@kyleisom.net], + [libemsha], + [https://kyleisom.net/projects/libemsha/]) +AM_INIT_AUTOMAKE([1.14 foreign]) + +AC_CONFIG_SRCDIR([src/emsha/sha256.hh]) +AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile doc/sphinx/source/conf.py]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_CHECK_HEADERS + +LT_INIT +AC_PROG_CXX +AC_PROG_INSTALL +AC_PROG_CC_C_O + +AC_CHECK_PROGS([SPHINX], [sphinx-build]) +if test -z "$SPHINX"; +then + AC_MSG_WARN([Sphinx not found - continuing without Sphinx support]) +fi + +AC_ARG_ENABLE([hexstring], + AS_HELP_STRING([--disable-hexstring], [Don't build with support for hex string output (default enabled)]), + [:], [enable_hexstring=check]) + +AC_ARG_ENABLE([hexlut], + AS_HELP_STRING([--disable-hexlut], [Don't build with the larger LUT for building hex strings (saves ~1KB of memory). This has no effect if hex strings are disabled.]), + [:], [enable_hexlut=check]) + +AC_ARG_ENABLE([selftest], + AS_HELP_STRING([--disable-selftest], [Don't build with support for internal self-tests (saves some memory that is used by the test vectors.)]), + [:], [enable_selftest=check]) + +# The default for the ARG_ENABLE options is to have them enabled. +AS_IF([test "x$enable_hexstring" == "xno"], [ + AC_MSG_NOTICE([disabling hexstrings.]) + AC_DEFINE(EMSHA_NO_HEXSTRING) +]) + +AS_IF([test "x$enable_hexlut" == "xno"], [ + AC_MSG_NOTICE([disabling the large hexstring LUT.]) + AC_DEFINE(EMSHA_NO_HEXLUT) +]) + +AS_IF([test "x$enable_selftest" == "xno"], [ + AC_MSG_NOTICE([disabling the internal self tests.]) + AC_DEFINE(EMSHA_NO_SELFTEST) +]) + + +AM_CONDITIONAL([HAVE_SPHINX], + [test -n "$SPHINX"]) + +AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..45592e5 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,5 @@ +if HAVE_SPHINX + +sphinx-html: + cd sphinx/ && make html +endif diff --git a/doc/sphinx/Makefile b/doc/sphinx/Makefile new file mode 100644 index 0000000..dd28a95 --- /dev/null +++ b/doc/sphinx/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) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.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 ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " 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/PACKAGE_NAME.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PACKAGE_NAME.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/PACKAGE_NAME" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PACKAGE_NAME" + @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/sphinx/build/.gitkeep b/doc/sphinx/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/doc/sphinx/source/_static/.gitkeep b/doc/sphinx/source/_static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/doc/sphinx/source/_templates/.gitkeep b/doc/sphinx/source/_templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/doc/sphinx/source/building.rst b/doc/sphinx/source/building.rst new file mode 100644 index 0000000..41d538c --- /dev/null +++ b/doc/sphinx/source/building.rst @@ -0,0 +1,37 @@ +------------------------------- +Getting and Building the Source +------------------------------- + +The source code is available via `Github +`_; each version should be git tagged. :: + + git clone https://github.com/kisom/libemsha + git clone git@github.com:kisom/libemsha + +The current release is `1.0.0 `_. + +The project is built using Autotools and ``make``. + +When building from a git checkout, the `autobuild` script will bootstrap +the project from the autotools sources (e.g. via ``autoreconf -i``), +run ``configurei`` (by default to use clang), and attempt to build the library +and run the unit tests. + +Once the autotools infrastructure has been bootstrapped, the following +should work: :: + + ./configure && make && make check && make install + +There are three flags to ``configure`` that might be useful: + ++ ``--disable-hexstring`` disables the provided ``hexstring`` function; + while this might be useful in many cases, it also adds extra size to + the code. + ++ ``--disable-hexlut`` disables the larger lookup table used by + ``hexstring``, which can save around a kilobyte of program space. If + the ``hexstring`` function is disabled, this option has no effect. + ++ ``--disable-selftest`` disables the internal self-tests, which can + reclaim some additional program space. + diff --git a/doc/sphinx/source/conf.py.in b/doc/sphinx/source/conf.py.in new file mode 100644 index 0000000..07e2dc7 --- /dev/null +++ b/doc/sphinx/source/conf.py.in @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +# +# @PACKAGE_NAME@ documentation build configuration file, created by +# sphinx-quickstart on Tue Dec 15 23:35:10 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +import sphinx_rtd_theme + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', +] + +# 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'@PACKAGE_NAME@' +copyright = u'2015, @PACKAGE_AUTHOR@' + +# 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. +version = '@PACKAGE_VERSION@' +# The full version, including alpha/beta/rc tags. +release = '@PACKAGE_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 = [] + +# 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 + +highlight_language = 'c++' + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v 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 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 = 'PACKAGE_NAMEdoc' + + +# -- 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', 'PACKAGE_NAME.tex', u'@PACKAGE\\_NAME@ Documentation', + u'@PACKAGE\\_AUTHOR@', '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', 'package_name', u'@PACKAGE_NAME@ Documentation', + [u'@PACKAGE_AUTHOR@'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'PACKAGE_NAME', u'@PACKAGE_NAME@ Documentation', + u'@PACKAGE_AUTHOR@', 'PACKAGE_NAME', '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'@PACKAGE_NAME@' +epub_author = u'@PACKAGE_AUTHOR@' +epub_publisher = u'@PACKAGE_AUTHOR@' +epub_copyright = u'2015, @PACKAGE_AUTHOR@' + +# The basename for the epub file. It defaults to the project name. +#epub_basename = u'@PACKAGE_NAME@' + +# 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 = ['search.html'] + +# 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/sphinx/source/hash.rst b/doc/sphinx/source/hash.rst new file mode 100644 index 0000000..0c77c72 --- /dev/null +++ b/doc/sphinx/source/hash.rst @@ -0,0 +1,74 @@ +------------------ +The Hash interface +------------------ + +.. cpp:class:: emsha::Hash + + The ``Hash`` class contains a top-level interface for the objects in + this library. + +In general, a `Hash` is used along the lines of: :: + + emsha::EMSHA_RESULT + hash_single_pass(uint8_t *m, uint32_t ml, uint8_t *digest) + { + // Depending on the implementation, the constructor may need + // arguments. + emsha::Hash h; + emsha::EMSHA_RESULT res; + + res = h.write(m, ml); + if (emsha::EMSHA_ROK != res) { + return res; + } + + // digest will contain the output of the Hash, and the + // caller MUST ensure that there is enough space in + // the buffer. + return h.result(d); + } + +Methods +^^^^^^^ + +.. cpp:function:: emsha::EMSHA_RESULT reset(void) + + reset should bring the Hash back into its initial state. That is, + the idea is that:: + + hash->reset(); + hash->update(...); // possibly many of these... + hash->result(...); // should always return the same hash. + + is idempotent, assuming the inputs to ``update`` and ``result`` + are constant. The implications of this for a given concrete class + should be described in that class's documentation, but in general, + it has the effect of preserving any initial state while removing any + data written to the Hash via the update method. + +.. cpp:function:: emsha::EMSHA_RESULT update(const uint8_t *m, uint32_t ml) + + ``update`` is used to write message data into + the Hash. + +.. cpp:function:: emsha::EMSHA_RESULT finalize(uint8_t *d) + + ``finalize`` should carry out any final operations on the `Hash`; + after a call to finalize, no more data can be written. Additionally, + it transfers out the resulting hash into its argument. + + Note that this library does not allocate memory, and therefore the + caller *must* ensure that ``d`` is a valid buffer containing at least + ``this->size()`` bytes. + +.. cpp:function:: emsha::EMSHA_RESULT result(uint8_t *d) + + ``result`` is used to transfer out the hash to the argument. This implies + that the `Hash` must keep enough state for repeated calls to ``result`` + to work. + +.. cpp:function:: uint32_t size(void) + + ``size`` should return the output size of the `Hash`; this is, how large + the buffers written to by ``result`` should be. + diff --git a/doc/sphinx/source/hmac.rst b/doc/sphinx/source/hmac.rst new file mode 100644 index 0000000..32aee82 --- /dev/null +++ b/doc/sphinx/source/hmac.rst @@ -0,0 +1,3 @@ +-------------- +The HMAC class +-------------- diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst new file mode 100644 index 0000000..a322d9f --- /dev/null +++ b/doc/sphinx/source/index.rst @@ -0,0 +1,34 @@ +.. libemsha documentation master file, created by + sphinx-quickstart on Tue Dec 15 23:35:10 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +libemsha +======== + +This library is an MIT-licensed compact HMAC-SHA-256 C++11 library +designed for embedded systems. It is built following the JPL `Power of +Ten `_ rules. + +Contents: + +.. toctree:: + :maxdepth: 2 + + building + intro + hash + sha256 + hmac + misc + + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/sphinx/source/intro.rst b/doc/sphinx/source/intro.rst new file mode 100644 index 0000000..fe2ab97 --- /dev/null +++ b/doc/sphinx/source/intro.rst @@ -0,0 +1,46 @@ +------------- +Introduction +------------- + +.. cpp:namespace:: emsha + +The package provides a pair of classes, :cpp:class:`SHA256` and +:cpp:class:`HMAC`, that both satisfy a common interface :cpp:class:`Hash`. All +functionality provided by this library is found under the ``emsha`` namespace. + + +``EMSHA_RESULT`` +^^^^^^^^^^^^^^^^^ + +The ``EMSHA_RESULT`` enum is used to convey the result of an +operation. The possible values are: + +.. cpp:enum:: _EMSHA_RESULT_ : uint8_t + +:: + + // All operations have completed successfully so far. + EMSHA_ROK = 0, + + // A self test or unit test failed. + EMSHA_TEST_FAILURE = 1, + + // A null pointer was passed in as a buffer where it + // shouldn't have been. + EMSHA_NULLPTR = 2, + + // The Hash is in an invalid state. + EMSHA_INVALID_STATE = 3, + + // The input to SHA256::update is too large. + SHA256_INPUT_TOO_LONG = 4, + + // The self tests have been disabled, but a self test + // function was called. + EMSHA_SELFTEST_DISABLED = 5 + +As a convenience, the following ``typedef`` is also provided. + + ``typedef enum _EMSHA_RESULT_`` :cpp:type:`EMSHA_RESULT` + + diff --git a/doc/sphinx/source/misc.rst b/doc/sphinx/source/misc.rst new file mode 100644 index 0000000..d0b5d1b --- /dev/null +++ b/doc/sphinx/source/misc.rst @@ -0,0 +1,4 @@ +----------------------- +Miscellaneous functions +----------------------- + diff --git a/doc/sphinx/source/sha256.rst b/doc/sphinx/source/sha256.rst new file mode 100644 index 0000000..6d5f2b5 --- /dev/null +++ b/doc/sphinx/source/sha256.rst @@ -0,0 +1,111 @@ +----------------- +The SHA256 class +----------------- + +.. cpp:class:: emsha::SHA256 + + SHA256 is an implementation of the :cpp:class:`emsha::Hash` + interface. + +.. cpp:function:: SHA256::SHA256() + + A SHA256 context does not need any special construction. It can be + declared and immediately start being used. + + +.. cpp:function:: SHA256::~SHA256() + + The SHA256 destructor will clear out its internal message buffer; + all of the members are local and not resource handles, so cleanup + is minimal. + + +.. cpp:function:: emsha::EMSHA_RESULT SHA256::reset(void) + + reset clears the internal state of the `SHA256` context and returns + it to its initial state. It should always return ``EMSHA_ROK``. + +.. cpp:function:: emsha::EMSHA_RESULT SHA256::update(const uint8_t *m, uint32_t ml) + + update writes data into the context. While there is an upper limit + on the size of data that SHA-256 can operate on, this package is + designed for small systems that will not approach that level of + data (which is on the order of 2 exabytes), so it is not thought to + be a concern. + + **Inputs**: + + + ``m``: a byte array containing the message to be written. It must + not be NULL (unless the message length is zero). + + + ``ml``: the message length, in bytes. + + **Return values**: + + * ``EMSHA_NULLPTR`` is returned if ``m`` is NULL and ``ml`` is nonzero. + + * ``EMSHA_INVALID_STATE`` is returned if the `update` is called + after a call to `finalize`. + + * ``SHA256_INPUT_TOO_LONG`` is returned if too much data has been + written to the context. + + + ``EMSHA_ROK`` is returned if the data was successfully added to + the SHA-256 context. + + +.. cpp:function:: emsha::EMSHA_RESULT SHA256::finalize(uint8_t *d) + + ``finalize`` completes the digest. Once this method is called, the + context cannot be updated unless the context is reset. + + **Inputs**: + + * d: a byte buffer that must be at least ``SHA256.size()`` in + length. + + **Outputs**: + + * ``EMSHA_NULLPTR`` is returned if ``d`` is the null pointer. + + * ``EMSHA_INVALID_STATE`` is returned if the SHA-256 context is in + an invalid state, such as if there were errors in previous + updates. + + * ``EMSHA_ROK`` is returned if the context was successfully + finalised and the digest copied to ``d``. + + +.. cpp:function:: emsha::EMSHA_RESULT SHA256::result(uint8_t *d) + + ``result`` copies the result from the SHA-256 context into the + buffer pointed to by ``d``, running finalize if needed. Once + called, the context cannot be updated until the context is reset. + + **Inputs**: + + * ``d``: a byte buffer that must be at least ``SHA256.size()`` in + length. + + **Outputs**: + + * ``EMSHA_NULLPTR`` is returned if ``d`` is the null pointer. + + * ``EMSHA_INVALID_STATE`` is returned if the SHA-256 context is in + an invalid state, such as if there were errors in previous + updates. + + * ``EMSHA_ROK`` is returned if the context was successfully + finalised and the digest copied to ``d``. + +.. cpp:function:: uint32_t SHA256::size(void) + + ``size`` returns the output size of SHA256, e.g. + the size that the buffers passed to ``finalize`` + and ``result`` should be. + + **Outputs**: + + * a ``uint32_t`` representing the expected size of buffers passed + to ``result`` and ``finalize``. + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..76cf9a3 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,82 @@ +AM_CPPFLAGS = -Wall -Wextra -pedantic -Wshadow -Wpointer-arith -Wcast-align +AM_CPPFLAGS += -Wwrite-strings -Wmissing-declarations -Wno-long-long -Werror +AM_CPPFLAGS += -Wunused-variable -std=c++11 -D_XOPEN_SOURCE -Os -I. +AM_CPPFLAGS += -fno-elide-constructors -Weffc++ +TEST_UTILS = test_utils.hh test_utils.cc +CLOC_PSOURCES = emsha.cc sha256.cc hmac.cc internal.hh \ + emsha/emsha.hh emsha/hmac.hh emsha/sha256.hh +CLOC_TSOURCES = test_emsha.cc test_hmac.cc test_mem.cc test_sha256.cc \ + test_utils.cc test_utils.hh + +lib_LTLIBRARIES = libemsha.la +nobase_include_HEADERS = emsha/sha256.hh emsha/hmac.hh emsha/emsha.hh +libemsha_la_SOURCES = emsha.cc sha256.cc hmac.cc internal.hh +libemsha_li_CPPFLAGS = $(AM_CPPFLAGS) -Winline + +check_PROGRAMS = emsha_sha256_test emsha_hmac_test \ + emsha_core_test emsha_mem_test \ + emsha_static_mem_test \ + emsha_static_sha_test +check_CPPFLAGS = $(AM_CPPFLAGS) -Wnoinline + +# emsha_sha256_test runs through some SHA-256 test vectors, ensuring +# that the library's behaviour is correct. +emsha_sha256_test_SOURCES = test_sha256.cc $(TEST_UTILS) +emsha_sha256_test_LDADD = libemsha.la + +# emsha_hmac_test runs through a set of HMAC-SHA-256 test vectors, +# ensuring that the library's behaviour is correct. +emsha_hmac_test_SOURCES = test_hmac.cc $(TEST_UTILS) +emsha_hmac_test_LDADD = libemsha.la + +# emsha_core_test validates some of the additional functions provided +# by the emsha library. +emsha_core_test_SOURCES = test_emsha.cc $(TEST_UTILS) +emsha_core_test_LDADD = libemsha.la + +# emsha_mem_test is used for testing with valgrind; it aims to introduce +# no heap allocations via the test harness so that memory usage inside +# the library may be more accurately checked. +emsha_mem_test_SOURCES = test_mem.cc $(TEST_UTILS) +emsha_mem_test_LDADD = libemsha.la + +# emsha_static_mem_test is a statically compiled (e.g. not libtool'd) +# test program whose stack can be more accurately measured. +emsha_static_mem_test_SOURCES = test_mem.cc emsha.cc sha256.cc hmac.cc $(TEST_UTILS) +emsha_static_mem_test_CPPFLAGS = $(AM_CPPFLAGS) -static +emsha_static_mem_test_LDFLAGS = $(AM_LDFLAGS) -static + +# emsha_static_mem_test is a statically compiled (e.g. not libtool'd) +# test program whose stack can be more accurately measured. +emsha_static_sha_test_SOURCES = test_sha256.cc emsha.cc sha256.cc hmac.cc $(TEST_UTILS) +emsha_static_sha_test_CPPFLAGS = $(AM_CPPFLAGS) -static +emsha_static_sha_test_LDFLAGS = $(AM_LDFLAGS) -static + + +.PHONY: valgrind-check +valgrind-check: emsha_static_mem_test + valgrind --tool=massif -v emsha_mem_test ms_print + +.PHONY: cloc-report +cloc-report: + @echo "=== Library Sources ===" + @cloc $(CLOC_PSOURCES) + @echo + @echo "=== Test Sources ===" + @cloc $(CLOC_TSOURCES) + +.PHONY: coverity-scan +coverity-scan: clean + cov-build --dir cov-int make all check + tar czf $(PACKAGE_NAME)-$(PACKAGE_VERSION)_coverity.tar.gz cov-int + rm -rf cov-int + +.PHONY: scanners clang-scanner cppcheck-scanner +scanners: clang-scanner cppcheck-scanner +clang-scanner: + clang++ $(AM_CPPFLAGS) --analyze $(CLOC_PSOURCES) + +cppcheck-scanner: + cppcheck --quiet --enable=all -I ./ $(CLOC_PSOURCES) + + diff --git a/src/emsha.cc b/src/emsha.cc new file mode 100644 index 0000000..c5f955a --- /dev/null +++ b/src/emsha.cc @@ -0,0 +1,146 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include +#include +#include +#include + +#include + + +using std::uint8_t; +using std::uint32_t; +using std::cout; +using std::endl; + + +namespace emsha { + + +Hash::~Hash() +{ + // Nothing to see here. +} + + +bool +hash_equal(const uint8_t *a, const uint8_t *b) +{ + uint8_t res = 0; + + EMSHA_CHECK(a != NULL, false); + EMSHA_CHECK(b != NULL, false); + + for (uint32_t i = 0; i < SHA256_HASH_SIZE; i++) { + res = a[i] ^ b[i]; + } + + return res == 0; +} + + +#ifndef EMSHA_NO_HEXSTRING +#ifndef EMSHA_NO_HEXLUT +// If using a lookup table is permitted, then the faster way to do this +// is to use one. +static void +write_hex_char(uint8_t *dest, uint8_t src) +{ + static constexpr uint8_t lut[256][3] = { + "00", "01", "02", "03", "04", "05", "06", "07", + "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", + "10", "11", "12", "13", "14", "15", "16", "17", + "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", + "20", "21", "22", "23", "24", "25", "26", "27", + "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", + "30", "31", "32", "33", "34", "35", "36", "37", + "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", + "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", + "50", "51", "52", "53", "54", "55", "56", "57", + "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", + "60", "61", "62", "63", "64", "65", "66", "67", + "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", + "70", "71", "72", "73", "74", "75", "76", "77", + "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", + "80", "81", "82", "83", "84", "85", "86", "87", + "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", + "90", "91", "92", "93", "94", "95", "96", "97", + "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", + "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", + "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", + "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", + "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", + "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", + "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", + "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", + "d8", "d9", "da", "db", "dc", "dd", "de", "df", + "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", + "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", + "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff" + }; + + *dest = lut[src][0]; + *(dest + 1) = lut[src][1]; +} + +#else // #ifndef EMSHA_NO_HEXLUT +// If the full lookup table can't be used, e.g. because MSP430-level +// memory constraints, we'll work around this using a small (16-byte) +// lookup table and some bit shifting. On platforms where even this is +// too much, the hexstring functionality will just be disabled. +static void +write_hex_char(uint8_t *dest, uint8_t src) +{ + static constexpr uint8_t lut[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + *dest = lut[((src & 0xF0) >> 4)]; + *(dest + 1) = lut[(src & 0xF)]; +} + +#endif // #ifndef EMSHA_NO_HEXLUT + + +void +hexstring(uint8_t *dest, uint8_t *src, uint32_t srclen) +{ + uint8_t *dp = dest; + + for (uint32_t i = 0; i < srclen; i++) { + write_hex_char(dp, src[i]); + dp += 2; + } +} + + +#endif // #ifndef EMSHA_NO_HEXSTRING + + +} // end of namespace emsha diff --git a/src/emsha/emsha.hh b/src/emsha/emsha.hh new file mode 100644 index 0000000..fec1033 --- /dev/null +++ b/src/emsha/emsha.hh @@ -0,0 +1,156 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#ifndef __EMSHA_EMSHA_HH +#define __EMSHA_EMSHA_HH + + +#include + + +namespace emsha { + + +// EMSHA_CHECK is used for sanity checks in certain parts of the code. +#ifdef NDEBUG + // If asserts are turned off, expand the check to an if + // statement that will return with retval if the condition + // isn't met. + #define EMSHA_CHECK(condition, retval) if (!(condition)) { return (retval); } +#else + // If asserts are turned on, the check is expanded to an + // assertion that the condition holds. In this case, retval is + // not used. + #define EMSHA_CHECK(condition, retval) (assert((condition))) +#endif + + + // SHA256_HASH_SIZE is the output length of SHA-256 in bytes. + const std::uint32_t SHA256_HASH_SIZE = 32; + + + // The EMSHA_RESULT type is used to indicate whether an + // operation succeeded, and if not, what the general fault type + // was. + typedef enum _EMSHA_RESULT_: std::uint8_t { + // All operations have completed successfully so far. + EMSHA_ROK = 0, + + // A self test or unit test failed. + EMSHA_TEST_FAILURE = 1, + + // A null pointer was passed in as a buffer where it shouldn't + // have been. + EMSHA_NULLPTR = 2, + + // The Hash is in an invalid state. + EMSHA_INVALID_STATE = 3, + + // The input to SHA256::update is too large. + SHA256_INPUT_TOO_LONG = 4, + + // The self tests have been disabled, but a self-test function + // was called. + EMSHA_SELFTEST_DISABLED = 5 + } EMSHA_RESULT; + + + // A Hash is generalised superclass supporting concrete classes + // that produce digests of data. + class Hash { + public: + virtual ~Hash() =0; + + // reset should bring the Hash back into its initial + // state. That is, the idea is that + // + // hash->reset(); hash->update(...)...; + // hash->result(...); + // + // is idempotent, assuming the inputs to update and + // result are constant. The implications of this for a + // given concrete class should be described in that + // class's documentation, but in general, it has the + // effect of preserving any initial state while removing + // any data written to the Hash via the update method. + virtual EMSHA_RESULT reset(void) =0; + + // update is used to write message data into the Hash. + virtual EMSHA_RESULT update(const std::uint8_t *m, + std::uint32_t ml) =0; + + // finalize should carry out any final operations on the + // Hash; after a call to finalize, no more data can be + // written. Additionally, it transfers out the + // resulting hash into its argument. + virtual EMSHA_RESULT finalize(std::uint8_t *d) =0; + + // result is used to transfer out the hash to the + // argument. This implies that the Hash must keep enough + // state for repeated calls to result to work. + virtual EMSHA_RESULT result(std::uint8_t *d) =0; + + // size should return the output size of the Hash; this + // is, how large the buffers written to by result should + // be. + virtual std::uint32_t size(void) =0; + }; + + // hash_equal provides a constant time function for comparing two + // hashes. The caller *must* ensure that both a and b are the same + // size. The recommended approach is to use fixed-size buffers of + // emsha::SHA256_HASH_SIZE length: + // + // uint8_t expected[emsha::SHA256_HASH_SIZE]; + // uint8_t actual[emsha::SHA256_HASH_SIZE]; + // + // // Fill in expected and actual using the Hash operations. + // + // if (hash_equal(expected, actual)) { + // proceed(); + // } + // + // Inputs: + // a, b: byte arrays that MUST contain at least + // emsha::SHA256_HASH_SIZE bytes. Only the first + // emsha::SHA256_HASH_SIZE bytes will be compared. + // + // Outputs: + // true iff both byte arrays match + // + // false if the arrays do not match + // + bool hash_equal(const std::uint8_t *a, const std::uint8_t *b); + +#ifndef EMSHA_NO_HEXSTRING + // hexstring writes a hex-encoded version of the src byte + // array into dest. The caller *must* ensure that dest is + // srclen * 2 bytes or longer. + void hexstring(std::uint8_t *dest, std::uint8_t *src, std::uint32_t srclen); +#endif // EMSHA_NO_HEXSTRING +} // end of namespace emsha + + +#endif // __EMSHA_EMSHA_HH diff --git a/src/emsha/hmac.hh b/src/emsha/hmac.hh new file mode 100644 index 0000000..9e68236 --- /dev/null +++ b/src/emsha/hmac.hh @@ -0,0 +1,197 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#ifndef __EMSHA_HMAC_HH +#define __EMSHA_HMAC_HH + + +#include + +#include +#include + + +namespace emsha { + + const uint32_t HMAC_KEY_LENGTH = SHA256_MB_SIZE; + + // HMAC is a keyed hash that can be used to produce an + // authenticated hash of some data. The HMAC is built on (and + // uses internally) the SHA-256 class; it's helpful to note that + // faults that occur in the SHA-256 code will be propagated up + // as the return value from many of the HMAC functions. + class HMAC : Hash { + public: + // An HMAC is constructed with a key and the + // length of the key. This key is stored in + // the HMAC context, and is wiped by the HMAC + // destructor. + // + // Inputs: + // k: the HMAC key. + // kl: the length of the HMAC key. + // + HMAC(const uint8_t *k, uint32_t kl); + + // reset clears any data written to the HMAC; + // this is equivalent to constructing a new HMAC, + // but it preserves the keys. + // + // Outputs: + // EMSHA_ROK is returned if the reset occurred + // without (detected) fault. + // + // If a fault occurs with the underlying SHA-256 + // context, the error code is returned. + // + EMSHA_RESULT reset(void); + + // update writes data into the context. While there is + // an upper limit on the size of data that the + // underlying hash can operate on, this package is + // designed for small systems that will not approach + // that level of data (which is on the order of 2 + // exabytes), so it is not thought to be a concern. + // + // Inputs: + // m: a byte array containing the message to be + // written. It must not be NULL (unless the message + // length is zero). + // + // ml: the message length, in bytes. + // + // Outputs: + // EMSHA_NULLPTR is returned if m is NULL and ml is + // nonzero. + // + // EMSHA_INVALID_STATE is returned if the update + // is called after a call to finalize. + // + // SHA256_INPUT_TOO_LONG is returned if too much + // data has been written to the context. + // + // EMSHA_ROK is returned if the data was + // successfully written into the HMAC context. + // + EMSHA_RESULT update(const uint8_t *, uint32_t); + + // finalize completes the HMAC computation. Once this + // method is called, the context cannot be updated + // unless the context is reset. + // + // Inputs: + // d: a byte buffer that must be at least + // HMAC.size() in length. + // + // Outputs: + // EMSHA_NULLPTR is returned if d is the null + // pointer. + // + // EMSHA_INVALID_STATE is returned if the HMAC + // context is in an invalid state, such as if there + // were errors in previous updates. + // + // EMSHA_ROK is returned if the context was + // successfully finalised and the digest copied to + // d. + // + EMSHA_RESULT finalize(uint8_t *); + + // result copies the result from the HMAC context into + // the buffer pointed to by d, running finalize if + // needed. Once called, the context cannot be updated + // until the context is reset. + // + // Inputs: + // d: a byte buffer that must be at least + // HMAC.size() in length. + // + // Outputs: + // EMSHA_NULLPTR is returned if d is the null + // pointer. + // + // EMSHA_INVALID_STATE is returned if the HMAC + // context is in an invalid state, such as if there + // were errors in previous updates. + // + // EMSHA_ROK is returned if the context was + // successfully finalised and the digest copied to + // d. + // + EMSHA_RESULT result(uint8_t *); + + + // size returns the output size of HMAC-SHA-256, e.g. + // the size that the buffers passed to finalize and + // result should be. + // + // Outputs: + // A uint32_t representing the expected size + // of buffers passed to result and finalize. + uint32_t size(void) { return SHA256_HASH_SIZE; } + + // When an HMAC context is destroyed, it is reset and + // the key material is zeroised using the STL fill + // function. + ~HMAC(void); + private: + uint8_t hstate; + SHA256 ctx; + uint8_t k[HMAC_KEY_LENGTH]; + uint8_t buf[SHA256_HASH_SIZE]; + + inline EMSHA_RESULT + final_result(uint8_t *); + }; + + // compute_hmac performs a single-pass HMAC computation over + // a message. + // + // Inputs: + // k: a byte buffer containing the HMAC key. + // + // kl: the length of the HMAC key. + // + // m: the message data over which the HMAC is to be computed. + // + // ml: the length of the message. + // + // d: a byte buffer that will be used to store the resulting + // HMAC. It should be SHA256_HASH_SIZE bytes in size. + // + // Outputs: + // This function handles setting up the HMAC context with + // the given key, calling update with the message data, and + // then calling finalize to place the result in the output + // buffer. Any of the faults that can occur in these functions + // can be returned here, or EMSHA_ROK if the HMAC was + // successfully computed. + EMSHA_RESULT compute_hmac(const uint8_t *k, uint32_t kl, + const uint8_t *m, uint32_t ml, + uint8_t *d); +} // end of namespace emsha + + +#endif // __EMSHA_HMAC_HH diff --git a/src/emsha/sha256.hh b/src/emsha/sha256.hh new file mode 100644 index 0000000..e5b668e --- /dev/null +++ b/src/emsha/sha256.hh @@ -0,0 +1,211 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#ifndef __EMSHA_SHA256_HH +#define __EMSHA_SHA256_HH + + +#include + +#include + + +namespace emsha { + + + // SHA256_MB_SIZE is the size of a message block. + const uint32_t SHA256_MB_SIZE = 64; + + class SHA256 : Hash { + public: + // A SHA256 context does not need any special + // construction. It can be declared and + // immediately start being used. + SHA256(); + + // The SHA256 destructor will clear out its internal + // message buffer; all of the members are local + // and not resource handles, so cleanup is minimal. + ~SHA256(); + + // reset clears the internal state of the SHA256 + // context and returns it to its initial state. + // It should always return EMSHA_ROK. + EMSHA_RESULT reset(void); + + // update writes data into the context. While + // there is an upper limit on the size of data + // that SHA-256 can operate on, this package is + // designed for small systems that will not + // approach that level of data (which is on the + // order of 2 exabytes), so it is not thought + // to be a concern. + // + // Inputs: + // m: a byte array containing the message to + // be written. It must not be NULL (unless + // the message length is zero). + // + // ml: the message length, in bytes. + // + // Outputs: + // EMSHA_NULLPTR is returned if m is NULL + // and ml is nonzero. + // + // EMSHA_INVALID_STATE is returned if the + // update is called after a call to + // finalize. + // + // SHA256_INPUT_TOO_LONG is returned if too + // much data has been written to the + // context. + // + // EMSHA_ROK is returned if the data was + // successfully added to the SHA-256 + // context. + // + EMSHA_RESULT update(const uint8_t *m, uint32_t ml); + + // finalize completes the digest. Once this + // method is called, the context cannot be + // updated unless the context is reset. + // + // Inputs: + // d: a byte buffer that must be at least + // SHA256.size() in length. + // + // Outputs: + // EMSHA_NULLPTR is returned if d is the + // null pointer. + // + // EMSHA_INVALID_STATE is returned if the + // SHA-256 context is in an invalid state, + // such as if there were errors in previous + // updates. + // + // EMSHA_ROK is returned if the context was + // successfully finalised and the digest + // copied to d. + // + EMSHA_RESULT finalize(uint8_t *d); + + // result copies the result from the SHA-256 + // context into the buffer pointed to by d, + // running finalize if needed. Once called, + // the context cannot be updated until the + // context is reset. + // + // Inputs: + // d: a byte buffer that must be at least + // SHA256.size() in length. + // + // Outputs: + // EMSHA_NULLPTR is returned if d is the + // null pointer. + // + // EMSHA_INVALID_STATE is returned if the + // SHA-256 context is in an invalid state, + // such as if there were errors in previous + // updates. + // + // EMSHA_ROK is returned if the context was + // successfully finalised and the digest + // copied to d. + // + EMSHA_RESULT result(uint8_t *d); + + // size returns the output size of SHA256, e.g. + // the size that the buffers passed to finalize + // and result should be. + // + // Outputs: + // a uint32_t representing the expected size + // of buffers passed to result and finalize. + uint32_t size(void) { return SHA256_HASH_SIZE; } + private: + // mlen stores the current message length. + uint64_t mlen; + + // The intermediate hash is 8x 32-bit blocks. + uint32_t i_hash[8]; + + // hstatus is the hash status, and hcomplete indicates + // whether the hash has been finalised. + EMSHA_RESULT hstatus; + uint8_t hcomplete; + + // mb is the message block, and mbi is the message + // block index. + uint8_t mbi; + uint8_t mb[SHA256_MB_SIZE]; + + inline EMSHA_RESULT add_length(uint32_t); + inline void update_message_block(void); + inline void pad_message(uint8_t); + }; // end class SHA256 + + // sha256_digest performs a single pass hashing of the message + // passed in. + // + // Inputs: + // m: byte buffer containing the message to hash. + // + // ml: the length of m. + // + // d: byte buffer that will be used to store the resulting + // hash; it should have at least emsha::SHA256_HASH_SIZE + // bytes available. + // + // Outputs: + // This function handles setting up a SHA256 context, calling + // update using the message data, and then calling finalize. Any + // of the errors that can occur in those functions can be + // returned here, or EMSHA_ROK if the digest was computed + // successfully. + // + EMSHA_RESULT sha256_digest(const uint8_t *m, uint32_t ml, uint8_t *d); + + // sha256_self_test runs through two test cases to ensure that the + // SHA-256 functions are working correctly. + // + // Outputs: + // EMSHA_ROK is returned if the self tests pass. + // + // EMSHA_SELFTEST_DISABLED is returned if the self tests + // have been disabled (e.g., libemsha was compiled with the + // EMSHA_NO_SELFTEST #define). + // + // If a fault occurred inside the SHA-256 code, the error + // code from one of the update, finalize, result, or reset + // methods is returned. + // + // If the fault is that the output does not match the test + // vector, EMSHA_TEST_FAILURE is returned. + // + EMSHA_RESULT sha256_self_test(void); +} // end of namespace emsha + + +#endif // __EMSHA_SHA256_HH diff --git a/src/hmac.cc b/src/hmac.cc new file mode 100644 index 0000000..96d9477 --- /dev/null +++ b/src/hmac.cc @@ -0,0 +1,251 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include +#include +#include + + +#include +#include +#include + + +namespace emsha { + +// These constants are used to keep track of the state of the HMAC. + +// HMAC is in a clean-slate state following a call to reset(). +constexpr uint8_t HMAC_INIT = 0; + +// The ipad constants have been XOR'd into the key and written to the +// SHA-256 context. +constexpr uint8_t HMAC_IPAD = 1; + +// The opad constants have been XOR'd into the key and written to the +// SHA-256 context. +constexpr uint8_t HMAC_OPAD = 2; + +// HMAC has been finalised +constexpr uint8_t HMAC_FIN = 3; + +// HMAC is in an invalid state. +constexpr uint8_t HMAC_INVALID = 4; + + +static constexpr uint8_t ipad = 0x36; +static constexpr uint8_t opad = 0x5c; + + +HMAC::HMAC(const uint8_t *ik, uint32_t ikl) + :hstate(), ctx() +{ + this->hstate = HMAC_INIT; + std::fill(this->k, this->k + emsha::HMAC_KEY_LENGTH, 0); + + if (ikl < HMAC_KEY_LENGTH) { + std::copy(ik, ik + ikl, this->k); + while (ikl < HMAC_KEY_LENGTH) { + this->k[ikl++] = 0; + } + } else if (ikl > HMAC_KEY_LENGTH) { + this->ctx.update(ik, ikl); + this->ctx.result(this->k); + this->ctx.reset(); + } else { + std::copy(ik, ik + ikl, this->k); + } + + this->reset(); +} + + +/* + * A custom destructor is needed to ensure that the key material is wiped. + */ +HMAC::~HMAC() +{ + this->reset(); + std::fill(this->k, this->k + HMAC_KEY_LENGTH, 0); +} + + +EMSHA_RESULT +HMAC::reset() +{ + EMSHA_RESULT res; + + // Following a reset, both SHA-256 contexts and result buffer should be + // zero'd out for a clean slate. The HMAC state should be reset + // accordingly. + this->ctx.reset(); + std::fill(this->buf, this->buf + SHA256_HASH_SIZE, 0); + + // Set up the k0 ⊕ ipad construction, and write it into the + // SHA-256 context. + uint8_t key[HMAC_KEY_LENGTH]; + for (uint32_t i = 0; i < HMAC_KEY_LENGTH; i++) { + key[i] = this->k[i] ^ ipad; + } + + res = this->ctx.update(key, HMAC_KEY_LENGTH); + if (EMSHA_ROK != res) { + this->hstate = HMAC_INVALID; + return res; + } + + // This key is considered sensitive material and should be wiped. + std::fill(key, key + HMAC_KEY_LENGTH, 0); + + this->hstate = HMAC_IPAD; + return EMSHA_ROK; +} + + +EMSHA_RESULT +HMAC::update(const uint8_t *m, uint32_t ml) +{ + EMSHA_RESULT res; + SHA256& hctx = this->ctx; + + EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHA_INVALID_STATE); + + // Write the message to the SHA-256 context. + res = hctx.update(m, ml); + if (EMSHA_ROK != res) { + this->hstate = HMAC_INVALID; + return res; + } + assert(HMAC_IPAD == this->hstate); + + return EMSHA_ROK; +} + + +inline EMSHA_RESULT +HMAC::final_result(uint8_t *d) +{ + if (nullptr == d) { + return EMSHA_NULLPTR; + } + + // If the HMAC has already been finalised, skip straight to + // copying the result. + if (HMAC_FIN == this->hstate) { + std::copy(this->buf, this->buf + SHA256_HASH_SIZE, d); + return EMSHA_ROK; + } + + EMSHA_CHECK(HMAC_IPAD == this->hstate, EMSHA_INVALID_STATE); + + EMSHA_RESULT res; + + // Use the result buffer as an intermediate buffer to store the result + // of the inner hash. + res = this->ctx.result(this->buf); + if (EMSHA_ROK != res) { + this->hstate = HMAC_INVALID; + return EMSHA_INVALID_STATE; + } + assert(HMAC_IPAD == this->hstate); + + // The SHA-256 context needs to be reset so that it may be + // re-used for the outer digest. + this->ctx.reset(); + + // Set up the k0 ⊕ opad construction, and write it into the + // SHA-256 context. + uint8_t key[HMAC_KEY_LENGTH]; + for (uint32_t i = 0; i < HMAC_KEY_LENGTH; i++) { + key[i] = this->k[i] ^ opad; + } + + res = this->ctx.update(key, HMAC_KEY_LENGTH); + if (EMSHA_ROK != res) { + this->hstate = HMAC_INVALID; + return res; + } + this->hstate = HMAC_OPAD; + + // This key is considered sensitive material and should be wiped. + std::fill(key, key + HMAC_KEY_LENGTH, 0); + + // Write the inner hash result into the outer hash. + res = this->ctx.update(this->buf, SHA256_HASH_SIZE); + if (EMSHA_ROK != res) { + this->hstate = HMAC_INVALID; + return res; + } + + // Write the outer hash result into the working buffer. + res = this->ctx.finalize(this->buf); + if (EMSHA_ROK != res) { + this->hstate = HMAC_INVALID; + return res; + } + assert(HMAC_OPAD == this->hstate); + + std::copy(this->buf, this->buf + SHA256_HASH_SIZE, d); + this->hstate = HMAC_FIN; + return EMSHA_ROK; +} + + +EMSHA_RESULT +HMAC::finalize(uint8_t *d) +{ + return this->final_result(d); +} + + +EMSHA_RESULT +HMAC::result(uint8_t *d) +{ + return this->final_result(d); +} + + +EMSHA_RESULT +compute_hmac(const uint8_t *k, uint32_t kl, const uint8_t *m, uint32_t ml, + uint8_t *d) +{ + EMSHA_RESULT res; + HMAC h(k, kl); + + res = h.update(m, ml); + if (EMSHA_ROK != res) { + return res; + } + + res = h.result(d); + if (EMSHA_ROK != res) { + return res; + } + + return res; +} + + +} // end of namespace emsha diff --git a/src/internal.hh b/src/internal.hh new file mode 100644 index 0000000..9709695 --- /dev/null +++ b/src/internal.hh @@ -0,0 +1,92 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#ifndef __EMSHA_INTERNAL_HH +#define __EMSHA_INTERNAL_HH + + +#include + +using std::uint8_t; +using std::uint32_t; + + +namespace emsha { + + +static inline uint32_t +rotr32(uint32_t x, uint8_t n) +{ + return ((x >> n) | (x << (32 - n))); +} + + +static inline uint32_t +sha_ch(uint32_t x, uint32_t y, uint32_t z) +{ + return ((x & y) ^ ((~x) & z)); +} + + +static inline uint32_t +sha_maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) ^ (x & z) ^ (y & z); +} + + +static inline uint32_t +sha_Sigma0(uint32_t x) +{ + return rotr32(x, 2) ^ rotr32(x, 13) ^ rotr32(x, 22); +} + + +static inline uint32_t +sha_Sigma1(uint32_t x) +{ + return rotr32(x, 6) ^ rotr32(x, 11) ^ rotr32(x, 25); +} + + +static inline uint32_t +sha_sigma0(uint32_t x) +{ + return rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3); +} + + +static inline uint32_t +sha_sigma1(uint32_t x) +{ + return rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10); +} + + + +} // end of namespace emsha + + +#endif // __EMSHA_INTERNAL_HH diff --git a/src/sha256.cc b/src/sha256.cc new file mode 100644 index 0000000..645a2e0 --- /dev/null +++ b/src/sha256.cc @@ -0,0 +1,459 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include +#include +#include + +#include +#include +#include "internal.hh" + + +namespace emsha { + + +/* + * SHA-256 constants, from FIPS 180-4 page 11. + */ +static constexpr uint32_t SHA256_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + + +/* + * SHA-256 initialisation vector, from FIPS 180-4 page 15. + */ +static constexpr uint32_t EMSHA_256_H0[] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + + +EMSHA_RESULT +sha256_digest(const uint8_t *m, uint32_t ml, uint8_t *d) +{ + SHA256 h; + EMSHA_RESULT ret; + + if (EMSHA_ROK != (ret = h.update(m, ml))) { + return ret; + } + + return h.finalize(d); +} + + +SHA256::SHA256() + :mlen(), hstatus(), hcomplete(), mbi() +{ + this->reset(); +} + + +SHA256::~SHA256() +{ + memset(this->mb, 0, SHA256_MB_SIZE); +} + + +inline EMSHA_RESULT +SHA256::add_length(uint32_t l) +{ + uint32_t tmp = this->mlen + l; + + if (tmp < this->mlen) { + return SHA256_INPUT_TOO_LONG; + } + this->mlen = tmp; + assert(this->mlen > 0); + + return EMSHA_ROK; +} + + +EMSHA_RESULT +SHA256::reset() +{ + // The message block is set to the initial hash vector. + this->i_hash[0] = EMSHA_256_H0[0]; + this->i_hash[1] = EMSHA_256_H0[1]; + this->i_hash[2] = EMSHA_256_H0[2]; + this->i_hash[3] = EMSHA_256_H0[3]; + this->i_hash[4] = EMSHA_256_H0[4]; + this->i_hash[5] = EMSHA_256_H0[5]; + this->i_hash[6] = EMSHA_256_H0[6]; + this->i_hash[7] = EMSHA_256_H0[7]; + + this->mbi = 0; + this->hstatus = EMSHA_ROK; + this->hcomplete = 0; + this->mlen = 0; + memset(this->mb, 0, SHA256_MB_SIZE); + + return this->hstatus; +} + + +// Read 32 bits from the byte buffer chunk as an unsigned 32-bit integer. +static inline uint32_t +chunk_to_uint32(uint8_t *chunk) +{ + return ((*chunk) << 24) | + ((*(chunk + 1)) << 16) | + ((*(chunk + 2)) << 8) | + (*(chunk + 3)); +} + + +// Copy an unsigned 32-bit integer into the start of the byte buffer chunk. +static inline void +uint32_to_chunk(uint32_t x, uint8_t *chunk) +{ + chunk[0] = (x & 0xff000000) >> 24; + chunk[1] = (x & 0x00ff0000) >> 16; + chunk[2] = (x & 0x0000ff00) >> 8; + chunk[3] = (x & 0x000000ff); +} + + +// FIPS 180-4, page 22. +void +SHA256::update_message_block() +{ + uint32_t w[64]; + uint32_t i = 0; + uint32_t chunk = 0; + uint32_t a, b, c, d, e, f, g, h; + + while (i < 16) { + w[i++] = chunk_to_uint32(this->mb + chunk); + chunk += 4; + } + this->mbi = 0; + + for (i = 16; i < 64; i++) { + w[i] = sha_sigma1(w[i - 2]) + w[i - 7] + + sha_sigma0(w[i - 15]) + w[i - 16]; + } + + a = this->i_hash[0]; + b = this->i_hash[1]; + c = this->i_hash[2]; + d = this->i_hash[3]; + e = this->i_hash[4]; + f = this->i_hash[5]; + g = this->i_hash[6]; + h = this->i_hash[7]; + + for (i = 0; i < 64; i++) { + uint32_t t1, t2; + t1 = h + sha_Sigma1(e) + sha_ch(e, f, g) + SHA256_K[i] + w[i]; + t2 = sha_Sigma0(a) + sha_maj(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + this->i_hash[0] += a; + this->i_hash[1] += b; + this->i_hash[2] += c; + this->i_hash[3] += d; + this->i_hash[4] += e; + this->i_hash[5] += f; + this->i_hash[6] += g; + this->i_hash[7] += h; +} + + +EMSHA_RESULT +SHA256::update(const uint8_t *m, uint32_t ml) +{ + // Checking invariants: + // If the message length is zero, there's nothing to be done. + if (0 == ml) return EMSHA_ROK; + + // The message passed in cannot be the null pointer if the + // message length is greater than 0. + if (nullptr == m) return EMSHA_NULLPTR; + + // If the SHA256 object is in a bad state, don't proceed. + if (EMSHA_ROK != this->hstatus) return this->hstatus; + + // If the hash has been finalised, don't proceed. + if (0 != this->hcomplete) return EMSHA_INVALID_STATE; + // Invariants satisfied by here. + + for (uint32_t i = 0; i < ml; i++) { + this->mb[this->mbi] = *(m + i); + mbi++; + + if (EMSHA_ROK == this->add_length(8)) { + if (SHA256_MB_SIZE == this->mbi) { + this->update_message_block(); + + // Assumption: following the message block + // write, the context should still be in a good + // state. + assert(EMSHA_ROK == this->hstatus); + } + } + } + + return this->hstatus; +} + + +inline void +SHA256::pad_message(uint8_t pc) +{ + // Assumption: the context is not in a corrupted state. + assert(EMSHA_ROK == this->hstatus); + + if (this->mbi < (SHA256_MB_SIZE - 8)) { + this->mb[this->mbi++] = pc; + } else { + bool pc_add = false; + + if (this->mbi < SHA256_MB_SIZE - 1) { + this->mb[this->mbi++] = pc; + pc_add = true; + } + + while (this->mbi < SHA256_MB_SIZE) { + this->mb[this->mbi++] = 0; + } + + this->update_message_block(); + if (!pc_add) { + this->mb[this->mbi++] = pc; + } + + // Assumption: updating the message block has not left the + // context in a corrupted state. + assert(EMSHA_ROK == this->hstatus); + } + + while (this->mbi < (SHA256_MB_SIZE - 8)) { + this->mb[this->mbi++] = 0; + } + + // lstart marks the starting point for the length packing. + uint32_t lstart = SHA256_MB_SIZE - 8; + + this->mb[lstart] = (uint8_t)(this->mlen >> 56); + this->mb[lstart+1] = + (uint8_t)((this->mlen & 0x00ff000000000000L) >> 48); + this->mb[lstart+2] = + (uint8_t)((this->mlen & 0x0000ff0000000000L) >> 40); + this->mb[lstart+3] = + (uint8_t)((this->mlen & 0x000000ff00000000L) >> 32); + this->mb[lstart+4] = + (uint8_t)((this->mlen & 0x00000000ff000000L) >> 24); + this->mb[lstart+5] = + (uint8_t)((this->mlen & 0x0000000000ff0000L) >> 16); + this->mb[lstart+6] = + (uint8_t)((this->mlen & 0x000000000000ff00L) >> 8); + this->mb[lstart+7] = + (uint8_t)(this->mlen & 0x00000000000000ffL); + + this->update_message_block(); + + // Assumption: updating the message block has not left the context in a + // corrupted state. + assert(EMSHA_ROK == this->hstatus); +} + + +EMSHA_RESULT +SHA256::finalize(uint8_t *d) +{ + // Check invariants. + // The digest cannot be a null pointer; this library allocates + // no memory of its own. + if (nullptr == d) return EMSHA_NULLPTR; + + // If the SHA256 object is in a bad state, don't proceed. + if (EMSHA_ROK != this->hstatus) return this->hstatus; + + // If the hash has been finalised, don't proceed. + if (0 != this->hcomplete) return EMSHA_INVALID_STATE; + // Invariants satisfied by here. + + this->pad_message(0x80); + + // Assumption: padding the message block has not left the context in a + // corrupted state. + assert(EMSHA_ROK == this->hstatus); + for (uint8_t i = 0; i < SHA256_MB_SIZE; i++) { + this->mb[i] = 0; + } + + this->hcomplete = 1; + this->mlen = 0; + + uint32_to_chunk(this->i_hash[0], d); + uint32_to_chunk(this->i_hash[1], d+4); + uint32_to_chunk(this->i_hash[2], d+8); + uint32_to_chunk(this->i_hash[3], d+12); + uint32_to_chunk(this->i_hash[4], d+16); + uint32_to_chunk(this->i_hash[5], d+20); + uint32_to_chunk(this->i_hash[6], d+24); + uint32_to_chunk(this->i_hash[7], d+28); + + return EMSHA_ROK; +} + + +EMSHA_RESULT +SHA256::result(uint8_t *d) +{ + // Check invariants. + // The digest cannot be a null pointer; this library allocates + // no memory of its own. + if (nullptr == d) return EMSHA_NULLPTR; + + // If the SHA256 object is in a bad state, don't proceed. + if (EMSHA_ROK != this->hstatus) return this->hstatus; + // Invariants satisfied by here. + + if (!this->hcomplete) { + return this->finalize(d); + } + + uint32_to_chunk(this->i_hash[0], d); + uint32_to_chunk(this->i_hash[1], d+4); + uint32_to_chunk(this->i_hash[2], d+8); + uint32_to_chunk(this->i_hash[3], d+12); + uint32_to_chunk(this->i_hash[4], d+16); + uint32_to_chunk(this->i_hash[5], d+20); + uint32_to_chunk(this->i_hash[6], d+24); + uint32_to_chunk(this->i_hash[7], d+28); + + return EMSHA_ROK; +} + + +#ifndef EMSHA_NO_SELFTEST +static const uint8_t empty_vector[] = { + 0xe3, 0xb0, 0xc4, 0x42, + 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, + 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, + 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, + 0x78, 0x52, 0xb8, 0x55 +}; + + +static const uint8_t hello_world[] = { + 0x09, 0xca, 0x7e, 0x4e, + 0xaa, 0x6e, 0x8a, 0xe9, + 0xc7, 0xd2, 0x61, 0x16, + 0x71, 0x29, 0x18, 0x48, + 0x83, 0x64, 0x4d, 0x07, + 0xdf, 0xba, 0x7c, 0xbf, + 0xbc, 0x4c, 0x8a, 0x2e, + 0x08, 0x36, 0x0d, 0x5b, +}; + +constexpr uint32_t EMSHA_SELF_TEST_ITERS = 4; + +static EMSHA_RESULT +run_test(const uint8_t *input, uint32_t input_len, const uint8_t *expected) +{ + uint8_t d[SHA256_HASH_SIZE]; + emsha::SHA256 ctx; + emsha::EMSHA_RESULT res; + + res = ctx.update(input, input_len); + if (EMSHA_ROK != res) { + return res; + } + + for (uint32_t n = 0; n < EMSHA_SELF_TEST_ITERS; n++) { + res = ctx.result(d); + if (EMSHA_ROK != res) { + return res; + } + + for (uint32_t i = 0; i < SHA256_HASH_SIZE; i++) { + if (expected[i] != d[i]) { + return EMSHA_TEST_FAILURE; + } + } + } + + return EMSHA_ROK; +} + + +EMSHA_RESULT +sha256_self_test() +{ + EMSHA_RESULT res; + + res = run_test((const uint8_t *)"", 0, empty_vector); + if (EMSHA_ROK == res) { + res = run_test((const uint8_t *)"hello, world", 12, hello_world); + } + + return res; +} + + +#else // #ifdef EMSHA_NO_SELFTEST +EMSHA_RESULT +sha256_self_test() +{ + return EMSHA_SELFTEST_DISABLED; +} + + +#endif // EMSHA_NO_SELFTEST + + +} // end namespace emsha diff --git a/src/test_emsha.cc b/src/test_emsha.cc new file mode 100644 index 0000000..2e68063 --- /dev/null +++ b/src/test_emsha.cc @@ -0,0 +1,112 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include + +#include + +#include "test_utils.hh" + + +using namespace std; + + +#ifndef EMSHA_NO_HEXSTRING +static void +hexstring_test(void) +{ + uint8_t buf[32]; + uint8_t out[65]; + string expected = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; + + out[64] = 0; + for (uint32_t i = 0; i < 32; i++) { + buf[i] = (uint8_t)i; + } + + emsha::hexstring(out, buf, emsha::SHA256_HASH_SIZE); + string outs(reinterpret_cast(out)); + if (outs != expected) { + cerr << "FAILED: hexstring" << endl; + cerr << "\twanted: " << expected << endl; + cerr << "\thave: " << out << endl; + exit(1); + } + + cout << "PASSED: hexstring "; +#ifdef EMSHA_NO_HEXLUT + cout << "(small LUT)"; +#else // #ifdef EMSHA_NO_HEXLUT + cout << "(large LUT)"; +#endif // #ifdef EMSHA_NO_HEXLUT + cout << endl; +} +#endif // #ifndef EMSHA_NO_HEXSTRING + + +static void +hash_equal_test(void) +{ + uint8_t a[emsha::SHA256_HASH_SIZE]; + uint8_t b[emsha::SHA256_HASH_SIZE]; + + for (uint32_t i = 0; i < emsha::SHA256_HASH_SIZE; i++) { + a[i] = static_cast(i); + b[i] = static_cast(i); + } + + if (!(emsha::hash_equal(a, b))) { + cerr << "FAILED: hash_equal\n"; + cerr << "\thash_equal should have succeeded comparing a and b.\n"; + cerr << "\ta <- " << dump_hexstring(a, emsha::SHA256_HASH_SIZE) << std::endl; + cerr << "\tb <- " << dump_hexstring(b, emsha::SHA256_HASH_SIZE) << std::endl; + exit(1); + } + + for (uint32_t i = 0; i < emsha::SHA256_HASH_SIZE; i++) { + a[i] = static_cast(i); + b[i] = static_cast(emsha::SHA256_HASH_SIZE - i); + } + + if (emsha::hash_equal(a, b)) { + cerr << "FAILED: hash_equal\n"; + cerr << "\thash_equal should not have succeeded comparing a and b.\n"; + cerr << "\ta <- " << dump_hexstring(a, emsha::SHA256_HASH_SIZE) << std::endl; + cerr << "\tb <- " << dump_hexstring(b, emsha::SHA256_HASH_SIZE) << std::endl; + exit(1); + } + + cout << "PASSED: hash_equal\n"; +} + + +int +main(void) +{ +#ifndef EMSHA_NO_HEXSTRING + hexstring_test(); +#endif + hash_equal_test(); +} diff --git a/src/test_hmac.cc b/src/test_hmac.cc new file mode 100644 index 0000000..77df8c3 --- /dev/null +++ b/src/test_hmac.cc @@ -0,0 +1,137 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include + +#include +#include + +#include "test_utils.hh" + +using namespace std; + + +const struct hmac_test rfc4231[] = { + { + {0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b}, 20, + "Hi There", + "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" + }, + { + {0x4a, 0x65, 0x66, 0x65}, 4, + "what do ya want for nothing?", + "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" + }, + { + {0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa}, 20, + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd", + "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", + }, + { + {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19}, 25, + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd", + "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b" + }, + { + {0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa}, 131, + "Test Using Larger Than Block-Size Key - Hash Key First", + "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54" + }, + { + {0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa}, 131, + "This is a test using a larger than block-size key " + "and a larger than block-size data. The key needs to " + "be hashed before being used by the HMAC algorithm.", + "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2" + } +}; + + + +int +main(void) +{ + int res; + + res = run_hmac_tests((struct hmac_test *)rfc4231, + sizeof rfc4231 / sizeof rfc4231[0], + "RFC 4231"); + if (-1 == res) { + exit(1); + } + + exit(0); +} diff --git a/src/test_mem.cc b/src/test_mem.cc new file mode 100644 index 0000000..c9de9e4 --- /dev/null +++ b/src/test_mem.cc @@ -0,0 +1,214 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +// Number of test iterations. +static constexpr std::uint32_t ITERS = 3000000; + +// The key used for HMAC. +static constexpr std::uint8_t k[] = { + 0xc5, 0xb6, 0x80, 0xac, 0xdc, 0xf4, 0xff, 0xa1, + 0x37, 0x05, 0xc0, 0x71, 0x11, 0x24, 0x31, 0x7c, + 0xa5, 0xa2, 0xcf, 0x4d, 0x33, 0x00, 0x56, 0x4f, + 0x69, 0x0f, 0x76, 0x70, 0x87, 0xd9, 0x35, 0xce, + 0xa3, 0xad, 0xa3, 0x4f, 0x30, 0xe2, 0x7c, 0x58, + 0x88, 0xd4, 0x89, 0x6a, 0xb5, 0xe0, 0x97, 0x1c, + 0x7a, 0x69, 0x65, 0xc7, 0x61, 0x0d, 0x6d, 0xb6, + 0x9b, 0x0e, 0x56, 0xd7, 0x0f, 0x5a, 0x01, 0x50, +}; +static constexpr std::uint32_t kl = sizeof(k) / sizeof(k[0]); + +// The message provided to both SHA-256 and HMAC-SHA-256; it is +// "The fugacity of a constituent in a mixture of gases at a given +// temperature is proportional to its mole fraction. Lewis-Randall Rule", +// chosen as one of the longer test vectors. +static const std::uint8_t m[] = { + 0x54, 0x68, 0x65, 0x20, 0x66, 0x75, 0x67, 0x61, + 0x63, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, + 0x61, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x69, + 0x74, 0x75, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, + 0x20, 0x61, 0x20, 0x6d, 0x69, 0x78, 0x74, 0x75, + 0x72, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x67, 0x61, + 0x73, 0x65, 0x73, 0x20, 0x61, 0x74, 0x20, 0x61, + 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, + 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x74, + 0x73, 0x20, 0x6d, 0x6f, 0x6c, 0x65, 0x20, 0x66, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x20, 0x20, 0x4c, 0x65, 0x77, 0x69, 0x73, 0x2d, + 0x52, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6c, 0x20, + 0x52, 0x75, 0x6c, 0x65 +}; + +// d is the expected result of SHA256(m). +static constexpr std::uint8_t d[emsha::SHA256_HASH_SIZE] = { + 0x39, 0x55, 0x85, 0xce, 0x30, 0x61, 0x7b, 0x62, + 0xc8, 0x0b, 0x93, 0xe8, 0x20, 0x8c, 0xe8, 0x66, + 0xd4, 0xed, 0xc8, 0x11, 0xa1, 0x77, 0xfd, 0xb4, + 0xb8, 0x2d, 0x39, 0x11, 0xd8, 0x69, 0x64, 0x23 +}; + +// t is the expected result of HMAC-SHA-256(k, m). +static constexpr std::uint8_t t[emsha::SHA256_HASH_SIZE] = { + 0xbb, 0xc4, 0x7c, 0x35, 0x33, 0x4b, 0x9d, 0x90, + 0xee, 0x20, 0x88, 0x30, 0xe1, 0x1a, 0x0f, 0xf3, + 0xf4, 0x7d, 0xcc, 0xb0, 0xc5, 0xfb, 0x83, 0xe5, + 0xc2, 0xf5, 0xa7, 0x94, 0x50, 0xb6, 0xe0, 0xe0, +}; + +// dig is used to store the output of SHA-256 and HMAC-SHA-256. +static std::uint8_t dig[emsha::SHA256_HASH_SIZE]; + + +static void +init(void) +{ + std::fill(dig, dig+emsha::SHA256_HASH_SIZE, 0); +} + + +static void +iterate_sha(void) +{ + emsha::SHA256 ctx; + int cmp; + emsha::EMSHA_RESULT res; + + res = ctx.update(m, sizeof(m)); + assert(emsha::EMSHA_ROK == res); + res = ctx.result(dig); + assert(emsha::EMSHA_ROK == res); + + cmp = std::memcmp(dig, d, emsha::SHA256_HASH_SIZE); + assert(0 == cmp); +} + + +static void +iterate_hmac(void) +{ + emsha::HMAC ctx(k, kl); + int cmp; + emsha::EMSHA_RESULT res; + + res = ctx.update(m, sizeof(m)); + assert(emsha::EMSHA_ROK == res); + res = ctx.result(dig); + assert(emsha::EMSHA_ROK == res); + + cmp = std::memcmp(dig, t, emsha::SHA256_HASH_SIZE); + assert(0 == cmp); +} + + +static void +iterate_sha_sp(void) +{ + int cmp; + + assert(emsha::EMSHA_ROK == emsha::sha256_digest(m, sizeof(m), dig)); + cmp = std::memcmp(dig, d, emsha::SHA256_HASH_SIZE); + assert(0 == cmp); +} + + +static void +iterate_hmac_sp(void) +{ + int cmp; + emsha::EMSHA_RESULT res; + + res = emsha::compute_hmac(k, kl, m, sizeof(m), dig); + assert(emsha::EMSHA_ROK == res); + + cmp = std::memcmp(dig, t, emsha::SHA256_HASH_SIZE); + assert(0 == cmp); +} + + +static void +iterate(std::string label, void(iteration)(void)) +{ + std::cout << "=== " << label << " ===" << std::endl; + auto start = std::chrono::steady_clock::now(); + + for (std::uint32_t i = 0; i < ITERS; i++) + iteration(); + + auto end = std::chrono::steady_clock::now(); + auto delta = (end - start ); + + std::cout << "Total time: " + << std::chrono::duration (delta).count() + << " ms" << std::endl; + std::cout << "Average over " << ITERS << " tests: " + << std::chrono::duration (delta).count() / ITERS + << " ns" << std::endl; +} + + +static void +cold_start(void) +{ + std::cout << "=== SHA-256 cold start ===\n"; + auto start = std::chrono::steady_clock::now(); + + iterate_sha(); + + auto end = std::chrono::steady_clock::now(); + auto delta = (end - start ); + + std::cout << "Total time: " + << std::chrono::duration (delta).count() + << " ns" << std::endl; +} + +int +main(void) +{ + init(); + + cold_start(); + iterate("SHA-256", iterate_sha); + iterate("SHA-256 single-pass", iterate_sha_sp); + iterate("HMAC-SHA-256", iterate_hmac); + iterate("HMAC-SHA-256 single-pass", iterate_hmac_sp); +} diff --git a/src/test_sha256.cc b/src/test_sha256.cc new file mode 100644 index 0000000..68fb0df --- /dev/null +++ b/src/test_sha256.cc @@ -0,0 +1,102 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include +#include +#include + +#include + +#include "test_utils.hh" + +using namespace std; + + +// Tests taken from the Go SHA-256 package. +const struct hash_test golden_tests[] = { + {"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""}, + {"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", "a"}, + {"fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603", "ab"}, + {"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "abc"}, + {"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", "abcd"}, + {"36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c", "abcde"}, + {"bef57ec7f53a6d40beb640a780a639c83bc29ac8a9816f1fc6c5c6dcd93c4721", "abcdef"}, + {"7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a", "abcdefg"}, + {"9c56cc51b374c3ba189210d5b6d4bf57790d351c96c47c02190ecf1e430635ab", "abcdefgh"}, + {"19cc02f26df43cc571bc9ed7b0c4d29224a3ec229529221725ef76d021c8326f", "abcdefghi"}, + {"72399361da6a7754fec986dca5b7cbaf1c810a28ded4abaf56b2106d06cb78b0", "abcdefghij"}, + {"a144061c271f152da4d151034508fed1c138b8c976339de229c3bb6d4bbb4fce", "Discard medicine more than two years old."}, + {"6dae5caa713a10ad04b46028bf6dad68837c581616a1589a265a11288d4bb5c4", "He who has a shady past knows that nice guys finish last."}, + {"ae7a702a9509039ddbf29f0765e70d0001177914b86459284dab8b348c2dce3f", "I wouldn't marry him with a ten foot pole."}, + {"6748450b01c568586715291dfa3ee018da07d36bb7ea6f180c1af6270215c64f", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"}, + {"14b82014ad2b11f661b5ae6a99b75105c2ffac278cd071cd6c05832793635774", "The days of the digital watch are numbered. -Tom Stoppard"}, + {"7102cfd76e2e324889eece5d6c41921b1e142a4ac5a2692be78803097f6a48d8", "Nepal premier won't resign."}, + {"23b1018cd81db1d67983c5f7417c44da9deb582459e378d7a068552ea649dc9f", "For every action there is an equal and opposite government program."}, + {"8001f190dfb527261c4cfcab70c98e8097a7a1922129bc4096950e57c7999a5a", "His money is twice tainted: 'taint yours and 'taint mine."}, + {"8c87deb65505c3993eb24b7a150c4155e82eee6960cf0c3a8114ff736d69cad5", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"}, + {"bfb0a67a19cdec3646498b2e0f751bddc41bba4b7f30081b0b932aad214d16d7", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"}, + {"7f9a0b9bf56332e19f5a0ec1ad9c1425a153da1c624868fda44561d6b74daf36", "size: a.out: bad magic"}, + {"b13f81b8aad9e3666879af19886140904f7f429ef083286195982a7588858cfc", "The major problem is with sendmail. -Mark Horton"}, + {"b26c38d61519e894480c70c8374ea35aa0ad05b2ae3d6674eec5f52a69305ed4", "Give me a rock, paper and scissors and I will move the world. CCFestoon"}, + {"049d5e26d4f10222cd841a119e38bd8d2e0d1129728688449575d4ff42b842c1", "If the enemy is within range, then so are you."}, + {"0e116838e3cc1c1a14cd045397e29b4d087aa11b0853fc69ec82e90330d60949", "It's well we cannot hear the screams/That we create in others' dreams."}, + {"4f7d8eb5bcf11de2a56b971021a444aa4eafd6ecd0f307b5109e4e776cd0fe46", "You remind me of a TV show, but that's all right: I watch it anyway."}, + {"61c0cc4c4bd8406d5120b3fb4ebc31ce87667c162f29468b3c779675a85aebce", "C is as portable as Stonehedge!!"}, + {"1fb2eb3688093c4a3f80cd87a5547e2ce940a4f923243a79a2a1e242220693ac", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"}, + {"395585ce30617b62c80b93e8208ce866d4edc811a177fdb4b82d3911d8696423", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"}, + {"4f9b189a13d030838269dce846b16a1ce9ce81fe63e65de2f636863336a98fe6", "How can you write a big system without C++? -Paul Glick"}, +}; + + +int +main(void) +{ + int res; + +#ifdef EMSHA_NO_SELFTEST + cout << "[NOTICE] internal self-tests have been disabled.\n"; +#else + res = emsha::sha256_self_test(); + switch (res) { + case emsha::EMSHA_ROK: + cout << "PASSED: SHA-256 self test" << endl; + break; + case emsha::EMSHA_TEST_FAILURE: + cout << "FAILED: SHA-256 self test (test failure)" << endl; + break; + default: + cout << "FAILED: SHA-256 self test (fault " << res << ")" + << endl; + } +#endif + res = run_hash_tests(const_cast(golden_tests), + sizeof golden_tests / sizeof golden_tests[0], + "golden tests"); + if (-1 == res) { + exit(1); + } + + exit(0); +} diff --git a/src/test_utils.cc b/src/test_utils.cc new file mode 100644 index 0000000..e5631bd --- /dev/null +++ b/src/test_utils.cc @@ -0,0 +1,299 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#include +#include +#include +#include + +#include "test_utils.hh" + +using std::uint8_t; +using std::uint32_t; +using std::string; +using std::cout; +using std::cerr; +using std::endl; + + +string +dump_hexstring(uint8_t *s, uint32_t sl) +{ + uint32_t bl = (2 * sl) + 1; + char *buf = new char[bl]; + string hs; + + memset(buf, 0, bl); + emsha::hexstring((uint8_t *)buf, s, sl); + hs = string(buf); + delete[] buf; + return hs; +} + + +emsha::EMSHA_RESULT +run_hmac_test(struct hmac_test test, string label) +{ + emsha::HMAC h(test.key, test.keylen); + emsha::EMSHA_RESULT res; + uint8_t dig[emsha::SHA256_HASH_SIZE]; + string hs = ""; + + res = h.update((uint8_t *)test.input.c_str(), test.input.size()); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + for (uint32_t n = 0; n < RESULT_ITERATIONS; n++) { + res = h.result(dig); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + hs = dump_hexstring(dig, emsha::SHA256_HASH_SIZE); + if (hs != test.output) { + res = emsha::EMSHA_TEST_FAILURE; + goto exit; + } + memset(dig, 0, emsha::SHA256_HASH_SIZE); + } + + // Ensure that a reset and update gives the same results. + h.reset(); + + res = h.update((uint8_t *)test.input.c_str(), test.input.size()); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + for (uint32_t n = 0; n < RESULT_ITERATIONS; n++) { + res = h.result(dig); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + hs = dump_hexstring(dig, emsha::SHA256_HASH_SIZE); + if (hs != test.output) { + res = emsha::EMSHA_TEST_FAILURE; + goto exit; + } + memset(dig, 0, emsha::SHA256_HASH_SIZE); + } + + // Test that the single-pass function works. + res = emsha::compute_hmac(test.key, test.keylen, + (uint8_t *)test.input.c_str(), test.input.size(), + dig); + if (emsha::EMSHA_ROK != res) { + cerr << "(running single pass function test)\n"; + goto exit; + } + + hs = dump_hexstring(dig, emsha::SHA256_HASH_SIZE); + if (hs != test.output) { + cerr << "(comparing single pass function output)\n"; + res = emsha::EMSHA_TEST_FAILURE; + goto exit; + } + memset(dig, 0, emsha::SHA256_HASH_SIZE); + + res = emsha::EMSHA_ROK; + +exit: + if (emsha::EMSHA_ROK != res) { + cerr << "FAILED: " << label << endl; + cerr << "\tinput: " << test.input << endl; + cerr << "\twanted: " << test.output << endl; + cerr << "\thave: " << hs << endl; + } + + return res; +} + + +int +run_hmac_tests(struct hmac_test *tests, uint32_t ntests, string label) +{ + for (uint32_t i = 0; i < ntests; i++) { + if (emsha::EMSHA_ROK != run_hmac_test(*(tests + i), label)) { + return -1; + } + } + cout << "PASSED: " << label << " (" << ntests << ")" << endl; + return 0; +} + + +emsha::EMSHA_RESULT +run_hash_test(struct hash_test test, string label) +{ + emsha::SHA256 ctx; + emsha::EMSHA_RESULT res; + uint8_t dig[emsha::SHA256_HASH_SIZE]; + string hs; + + res = ctx.update((uint8_t *)test.input.c_str(), test.input.size()); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + for (uint32_t n = 0; n < RESULT_ITERATIONS; n++) { + res = ctx.result(dig); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + hs = dump_hexstring(dig, emsha::SHA256_HASH_SIZE); + if (hs != test.output) { + res = emsha::EMSHA_TEST_FAILURE; + goto exit; + } + memset(dig, 0, emsha::SHA256_HASH_SIZE); + } + + // Ensure that a reset and update gives the same results. + ctx.reset(); + + res = ctx.update((uint8_t *)test.input.c_str(), test.input.size()); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + for (uint32_t n = 0; n < RESULT_ITERATIONS; n++) { + res = ctx.result(dig); + if (emsha::EMSHA_ROK != res) { + goto exit; + } + + hs = dump_hexstring(dig, emsha::SHA256_HASH_SIZE); + if (hs != test.output) { + res = emsha::EMSHA_TEST_FAILURE; + goto exit; + } + memset(dig, 0, emsha::SHA256_HASH_SIZE); + } + + // Test that the single-pass function works. + res = emsha::sha256_digest((uint8_t *)test.input.c_str(), + test.input.size(), dig); + if (emsha::EMSHA_ROK != res) { + cerr << "(running single pass function test)\n"; + goto exit; + } + + hs = dump_hexstring(dig, emsha::SHA256_HASH_SIZE); + if (hs != test.output) { + cerr << "(comparing single pass function output)\n"; + res = emsha::EMSHA_TEST_FAILURE; + goto exit; + } + memset(dig, 0, emsha::SHA256_HASH_SIZE); + res = emsha::EMSHA_ROK; + +exit: + if (emsha::EMSHA_ROK != res) { + cerr << "FAILED: " << label << endl; + cerr << "\tinput: '" << test.input << "'" << endl; + cerr << "\twanted: " << test.output << endl; + cerr << "\thave: " << hs << endl; + } + return res; +} + + +int +run_hash_tests(struct hash_test *tests, uint32_t ntests, string label) +{ + for (uint32_t i = 0; i < ntests; i++) { + if (emsha::EMSHA_ROK != run_hash_test(*(tests + i), label)) { + return -1; + } + } + cout << "PASSED: " << label << " (" << ntests << ")" << endl; + return 0; +} + + +#ifdef EMSHA_NO_HEXSTRING + +// If the library was built without hex string support, include it in +// the test code. +namespace emsha { +static void +write_hex_char(uint8_t *dest, uint8_t src) +{ + static constexpr uint8_t lut[256][3] = { + "00", "01", "02", "03", "04", "05", "06", "07", + "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", + "10", "11", "12", "13", "14", "15", "16", "17", + "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", + "20", "21", "22", "23", "24", "25", "26", "27", + "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", + "30", "31", "32", "33", "34", "35", "36", "37", + "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", + "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", + "50", "51", "52", "53", "54", "55", "56", "57", + "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", + "60", "61", "62", "63", "64", "65", "66", "67", + "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", + "70", "71", "72", "73", "74", "75", "76", "77", + "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", + "80", "81", "82", "83", "84", "85", "86", "87", + "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", + "90", "91", "92", "93", "94", "95", "96", "97", + "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", + "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", + "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", + "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", + "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", + "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", + "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", + "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", + "d8", "d9", "da", "db", "dc", "dd", "de", "df", + "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", + "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", + "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff" + }; + + memcpy(dest, lut[src], 2); +} + + +void +hexstring(uint8_t *dest, uint8_t *src, uint32_t srclen) +{ + uint8_t *dp = dest; + + for (uint32_t i = 0; i < srclen; i++, dp += 2) { + write_hex_char(dp, src[i]); + } +} + + +} + +#endif // EMSHA_NO_HEXSTRING diff --git a/src/test_utils.hh b/src/test_utils.hh new file mode 100644 index 0000000..df8bdfc --- /dev/null +++ b/src/test_utils.hh @@ -0,0 +1,85 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 K. Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + +#ifndef __EMSHA_TEST_UTILS_HH +#define __EMSHA_TEST_UTILS_HH + + +#include +#include + +#include +#include +#include + + +// How many times should a test result be checked? The goal is to +// ensure that the 'result' method is idempotent. My science teachers +// always said 5 data points was the minimum you needed, so there you +// go. Thanks, Mr. Franklin. +constexpr uint32_t RESULT_ITERATIONS = 5; + + +// Test data structures. + +struct hash_test { + std::string output; + std::string input; +}; + + +struct hmac_test { + std::uint8_t key[256]; + std::uint32_t keylen; + std::string input; + std::string output; +}; + + +// General-purpose debuggery. +std::string dump_hexstring(std::uint8_t *, std::uint32_t); +void dump_pair(std::uint8_t *, std::uint8_t *); + + +// SHA-256 testing functions. +emsha::EMSHA_RESULT run_hash_test(struct hash_test, std::string); +int run_hash_tests(struct hash_test *, std::uint32_t, + std::string); + + +// HMAC-SHA-256 testery. +emsha::EMSHA_RESULT run_hmac_test(struct hmac_test, std::string); +int run_hmac_tests(struct hmac_test *, std::uint32_t, + std::string); + + +#ifdef EMSHA_NO_HEXSTRING +namespace emsha { + void hexstring(uint8_t *dest, uint8_t *src, uint32_t srclen); +} +#endif // EMSHA_NO_HEXSTRING + + +#endif // __EMSHA_TEST_UTILS_HH