From 0260ffe813b5f007374459e06367a7562df2f9c0 Mon Sep 17 00:00:00 2001 From: wangyoukang Date: Wed, 17 May 2023 10:13:46 +0800 Subject: [PATCH] add upstream initial src code Signed-off-by: wangyoukang --- CHANGELOG.md | 57 ++ CODE_OF_CONDUCT.md | 133 ++++ CONTRIBUTING.md | 36 ++ LICENSE | 24 + MAINTAINERS | 2 + Makefile.am | 226 +++++++ RELEASE.md | 101 ++++ SECURITY.md | 37 ++ bash-completion/tpm2tss-genkey | 44 ++ bootstrap | 7 + configure.ac | 256 ++++++++ include/tpm2-tss-engine.h | 103 ++++ m4/flags.m4 | 52 ++ man/tpm2tss-genkey.1.md | 118 ++++ man/tpm2tss_ecc_genkey.3.md | 36 ++ man/tpm2tss_ecc_getappdata.3.md | 39 ++ man/tpm2tss_ecc_makekey.3.md | 36 ++ man/tpm2tss_rsa_genkey.3.md | 36 ++ man/tpm2tss_rsa_makekey.3.md | 36 ++ man/tpm2tss_tpm2data_write.3.md | 42 ++ openssl.conf.sample | 22 + src/tpm2-tss-engine-common.c | 698 +++++++++++++++++++++ src/tpm2-tss-engine-common.h | 199 ++++++ src/tpm2-tss-engine-digest-sign.c | 315 ++++++++++ src/tpm2-tss-engine-ecc.c | 875 +++++++++++++++++++++++++++ src/tpm2-tss-engine-err.c | 187 ++++++ src/tpm2-tss-engine-err.h | 133 ++++ src/tpm2-tss-engine-rand.c | 151 +++++ src/tpm2-tss-engine-rsa.c | 813 +++++++++++++++++++++++++ src/tpm2-tss-engine.c | 372 ++++++++++++ src/tpm2tss-genkey.c | 415 +++++++++++++ test/ecdh.sh | 62 ++ test/ecdsa-emptyauth.sh | 15 + test/ecdsa-handle-flush.sh | 52 ++ test/ecdsa-restricted.sh | 52 ++ test/ecdsa.sh | 15 + test/error_tpm2-tss-engine-common.c | 96 +++ test/failload.sh | 12 + test/failwrite.sh | 9 + test/neg-handle.pem | 13 + test/rand.sh | 5 + test/rsadecrypt.sh | 16 + test/rsasign.sh | 18 + test/rsasign_importtpm.sh | 39 ++ test/rsasign_importtpmparent.sh | 43 ++ test/rsasign_parent.sh | 35 ++ test/rsasign_parent_pass.sh | 50 ++ test/rsasign_persistent.sh | 48 ++ test/rsasign_persistent_emptyauth.sh | 57 ++ test/rsasign_restricted.sh | 52 ++ test/sclient.sh | 43 ++ test/sh_log_compiler.sh | 83 +++ test/sserver.sh | 27 + test/tpm2-tss-engine-common.c | 31 + 54 files changed, 6474 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MAINTAINERS create mode 100644 Makefile.am create mode 100644 RELEASE.md create mode 100644 SECURITY.md create mode 100644 bash-completion/tpm2tss-genkey create mode 100755 bootstrap create mode 100644 configure.ac create mode 100644 include/tpm2-tss-engine.h create mode 100644 m4/flags.m4 create mode 100644 man/tpm2tss-genkey.1.md create mode 100644 man/tpm2tss_ecc_genkey.3.md create mode 100644 man/tpm2tss_ecc_getappdata.3.md create mode 100644 man/tpm2tss_ecc_makekey.3.md create mode 100644 man/tpm2tss_rsa_genkey.3.md create mode 100644 man/tpm2tss_rsa_makekey.3.md create mode 100644 man/tpm2tss_tpm2data_write.3.md create mode 100644 openssl.conf.sample create mode 100755 src/tpm2-tss-engine-common.c create mode 100755 src/tpm2-tss-engine-common.h create mode 100644 src/tpm2-tss-engine-digest-sign.c create mode 100644 src/tpm2-tss-engine-ecc.c create mode 100644 src/tpm2-tss-engine-err.c create mode 100644 src/tpm2-tss-engine-err.h create mode 100644 src/tpm2-tss-engine-rand.c create mode 100644 src/tpm2-tss-engine-rsa.c create mode 100644 src/tpm2-tss-engine.c create mode 100644 src/tpm2tss-genkey.c create mode 100755 test/ecdh.sh create mode 100755 test/ecdsa-emptyauth.sh create mode 100755 test/ecdsa-handle-flush.sh create mode 100755 test/ecdsa-restricted.sh create mode 100755 test/ecdsa.sh create mode 100644 test/error_tpm2-tss-engine-common.c create mode 100755 test/failload.sh create mode 100755 test/failwrite.sh create mode 100644 test/neg-handle.pem create mode 100755 test/rand.sh create mode 100755 test/rsadecrypt.sh create mode 100755 test/rsasign.sh create mode 100755 test/rsasign_importtpm.sh create mode 100755 test/rsasign_importtpmparent.sh create mode 100755 test/rsasign_parent.sh create mode 100755 test/rsasign_parent_pass.sh create mode 100755 test/rsasign_persistent.sh create mode 100755 test/rsasign_persistent_emptyauth.sh create mode 100755 test/rsasign_restricted.sh create mode 100755 test/sclient.sh create mode 100755 test/sh_log_compiler.sh create mode 100755 test/sserver.sh create mode 100644 test/tpm2-tss-engine-common.c diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c57f08b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.2.0] - 2023-01-09 +### Fixed +- Updated minimal version of tpm2-tss to 2.4.x +- Fix encoding of emptyauth +- Fix some memory leaks +- Parent handle issues with signed representation by switching parent handle to BIGNUM. +- Fixed RSA_NO_PADDING modes with OpenSSL 1.1.1 +- Fixed autogen (bootstrap) call from release package by embedding VERSION file. + +### Added +- Use of restricted keys for signing +- StirRandom +- Run tests using swtpm +- The ability to import key blobs from things like the tpm2-tools project. +- Compatibility with openssl >=1.1.x +- Support for ECDH +- QNX support. +- Only set -Werror for non-release builds. +- Additional checks on TPM responses +- CODE_OF_CONDUCT +- SECURITY reporting instructions + +## [1.1.0] - 2020-11-20 +### Added +- Configure option for ptpm tests +- Configure script AX_CHECK_ENABLE_DEBUG +- Option for setting tcti on executable +- TCTI-env variable used by default +- Support for parent key passwords +- openssl.cnf sample file + +### Changed +- Fix several build system, autotools and testing related issues + Now adhere to CFLAGS conventions +- Include pkg-config dependecy on libtss2-mu in order to work with tpm2-tss 2.3 +- Enables parallel testing of integration tests: + Make integration tests use TPM simulator; instead of first TPM it finds + Use of different port numbers for TCP based tests +- Fix EC param info (using named curve format) +- Use tpm2-tools 4.X stable branch for integration tests +- Use libtss2-tctildr.so instead of custom code for tcti setup +- Fix manpages for -P/--parent option and correct engine name +- Fix TCTI env variable handling + +## [1.0.0] - 2019-04-04 +### Added +- Initial release of the OpenSSL engine for TPM2.0 using the TCG's TPM + Software Stack compliant tpm2-tss libraries. +- tpm2tss (the engine) compatible against OpenSSL 1.0.2 and 1.1.0. +- tpm2tss-genkey (cli-tool) for creating keys for use with the engine. +- man-pages and bash-completion are included. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2dc02b2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[MAINTAINERS](MAINTAINERS). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4035aca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# Guidelines for submitting bugs: +All non security bugs should be filed on the Issues tracker: +https://github.com/tpm2-software/tpm2-tss-engine/issues + +Security sensitive bugs should follow the details in SECURITY.md. + +# Guideline for submitting changes: +All changes to the source code must follow the coding standard used in the +tpm2-tss project [here](https://github.com/tpm2-software/tpm2-tss/blob/master/doc/coding_standard_c.md). + +All changes should be introduced via github pull requests. This allows anyone to +comment and provide feedback in lieu of having a mailing list. For pull requests +opened by non-maintainers, any maintainer may review and merge that pull +request. For maintainers, they either must have their pull request reviewed by +another maintainer if possible, or leave the PR open for at least 24 hours, we +consider this the window for comments. + +## Patch requirements +* All tests must pass on Travis CI for the merge to occur. +* All changes must not introduce superfluous changes or whitespace errors. +* All commits should adhere to the git commit message guidelines described +here: https://chris.beams.io/posts/git-commit/ with the following exceptions. + * We allow commit subject lines up to 80 characters. +* All contributions must adhere to the Developers Certificate of Origin. The +full text of the DCO is here: https://developercertificate.org/. Contributors +must add a 'Signed-off-by' line to their commits. This indicates the +submitters acceptance of the DCO. + +## Guideline for merging changes +Pull Requests MUST be assigned to an upcoming release tag. If a release milestone does +not exist, the maintainer SHALL create it per the [RELEASE.md](RELEASE.md) instructions. +When accepting and merging a change, the maintainer MUST edit the description field for +the release milestone to add the CHANGELOG entry. + +Changes must be merged with the "rebase" option on github to avoid merge commits. +This provides for a clear linear history. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..37897e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..054591c --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,2 @@ +Andreas Fuchs +Juergen Repp (occasionally) diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..bda32b4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,226 @@ +#;*****************************************************************************; +# Copyright (c) 2018 Fraunhofer SIT sponsored by Infineon Technologies AG +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of tpm2-tss-engine nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +#;*****************************************************************************; + +### Initialize global variables used throughout the file ### +INCLUDE_DIRS = -I$(srcdir)/include -I$(srcdir)/src +ACLOCAL_AMFLAGS = -I m4 --install +AM_CFLAGS = $(INCLUDE_DIRS) $(EXTRA_CFLAGS) $(TSS2_ESYS_CFLAGS) \ + $(TSS2_MU_CFLAGS) $(TSS2_TCTILDR_CFLAGS) $(CRYPTO_CFLAGS) \ + $(CODE_COVERAGE_CFLAGS) +AM_LDFLAGS = $(EXTRA_LDFLAGS) $(CODE_COVERAGE_LIBS) +AM_LDADD = $(TSS2_ESYS_LIBS) $(TSS2_MU_LIBS) $(TSS2_TCTILDR_LIBS) \ + $(CRYPTO_LIBS) + +AM_DISTCHECK_CONFIGURE_FLAGS = --with-enginesdir= --with-completionsdir= \ + --enable-unit + +# Initialize empty variables to be extended throughout +EXTRA_DIST = +CLEANFILES = +bin_PROGRAMS = + +### Add ax_* rules ### +# ax_code_coverage +if AUTOCONF_CODE_COVERAGE_2019_01_06 +include $(top_srcdir)/aminclude_static.am +clean-local: code-coverage-clean +distclean-local: code-coverage-dist-clean +else +@CODE_COVERAGE_RULES@ +endif + +# ax_valgrind_check +@VALGRIND_CHECK_RULES@ + +### OpenSSL Engine ### +openssl_enginedir = $(ENGINESDIR) +openssl_engine_LTLIBRARIES = libtpm2tss.la + +include_HEADERS = include/tpm2-tss-engine.h + +libtpm2tss_la_SOURCES = src/tpm2-tss-engine.c \ + src/tpm2-tss-engine-common.c \ + src/tpm2-tss-engine-common.h \ + src/tpm2-tss-engine-digest-sign.c \ + src/tpm2-tss-engine-err.c \ + src/tpm2-tss-engine-err.h \ + src/tpm2-tss-engine-ecc.c \ + src/tpm2-tss-engine-rand.c \ + src/tpm2-tss-engine-rsa.c +libtpm2tss_la_CFLAGS = $(AM_CFLAGS) +libtpm2tss_la_LIBADD = $(AM_LDADD) +libtpm2tss_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -avoid-version \ + -export-symbols-regex '(tpm2tss*|bind_engine|v_check)' + +install-exec-local: + ([ -e $(DESTDIR)$(openssl_enginedir) ] || \ + $(MKDIR_P) $(DESTDIR)$(openssl_enginedir)) + +# Due to confusions with OpenSSL Naming conventions for engines regarding the +# lib* prefix, we will create a symlink for the engine on install +# see https://github.com/tpm2-software/tpm2-tss-engine/issues/6#issuecomment-422489744 +# see https://github.com/openssl/openssl/commit/9ee0ed3de66678a15db126d10b3e4226e835b8f5 +install-exec-hook: + (cd $(DESTDIR)$(openssl_enginedir) && \ + $(LN_S) -f libtpm2tss.so tpm2tss.so) + +uninstall-hook: + (cd $(DESTDIR)$(openssl_enginedir) && \ + [ -L tpm2tss.so ] && rm -f tpm2tss.so) + +### KeyGenerator ### +bin_PROGRAMS += tpm2tss-genkey + +tpm2tss_genkey_SOURCES = src/tpm2tss-genkey.c +tpm2tss_genkey_CFLAGS = $(AM_CFLAGS) +tpm2tss_genkey_LDADD = $(AM_LDADD) libtpm2tss.la +tpm2tss_genkey_LDFLAGS = $(AM_LDFLAGS) + +### Tests ### +TESTS = $(TESTS_INTEGRATION) $(TESTS_UNIT) + +check_PROGRAMS = $(TESTS_UNIT) +TESTS_UNIT = +TESTS_INTEGRATION = + +if INTEGRATION +TESTS_INTEGRATION += $(TESTS_SHELL) +endif #INTEGRATION +TESTS_SHELL = test/ecdsa.sh \ + test/ecdsa-emptyauth.sh \ + test/ecdsa-handle-flush.sh \ + test/rand.sh \ + test/rsadecrypt.sh \ + test/rsasign.sh \ + test/failload.sh \ + test/failwrite.sh \ + test/rsasign_importtpm.sh \ + test/rsasign_importtpmparent.sh \ + test/rsasign_parent.sh \ + test/rsasign_parent_pass.sh \ + test/rsasign_persistent.sh \ + test/rsasign_persistent_emptyauth.sh \ + test/sserver.sh \ + test/sclient.sh +if HAVE_OPENSSL_ECDH +TESTS_SHELL += test/ecdh.sh +endif +if HAVE_OPENSSL_DIGEST_SIGN +TESTS_SHELL += test/ecdsa-restricted.sh \ + test/rsasign_restricted.sh +endif +EXTRA_DIST += $(TESTS_SHELL) test/neg-handle.pem +TEST_EXTENSIONS = .sh +SH_LOG_COMPILER = $(srcdir)/test/sh_log_compiler.sh +SH_LOG_FLAGS = $(INTEGRATION_ARGS) +EXTRA_DIST += $(SH_LOG_COMPILER) + +if UNIT +TESTS_UNIT += test/error_tpm2-tss-engine-common test/tpm2-tss-engine-common +test_error_tpm2_tss_engine_common_CFLAGS = $(AM_CFLAGS) $(CMOCKA_CFLAGS) +test_error_tpm2_tss_engine_common_LDADD = $(AM_LDADD) $(CMOCKA_LIBS) +test_error_tpm2_tss_engine_common_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=Esys_Initialize +test_error_tpm2_tss_engine_common_SOURCES = test/error_tpm2-tss-engine-common.c \ + $(libtpm2tss_la_SOURCES) +test_tpm2_tss_engine_common_CFLAGS = $(AM_CFLAGS) $(CMOCKA_CFLAGS) \ + -DNEG_HANDLE_PEM=\"$(top_srcdir)/test/neg-handle.pem\" +test_tpm2_tss_engine_common_LDADD = $(AM_LDADD) $(CMOCKA_LIBS) +test_tpm2_tss_engine_common_LDFLAGS = $(AM_LDFLAGS) +test_tpm2_tss_engine_common_SOURCES = test/tpm2-tss-engine-common.c \ + $(libtpm2tss_la_SOURCES) +endif #UNIT + +# Adding user and developer information +EXTRA_DIST += \ + CHANGELOG.md \ + CONTRIBUTING.md \ + INSTALL.md \ + LICENSE \ + README.md \ + VERSION + +# Generate the AUTHORS file from git log +AUTHORS: + $(AM_V_GEN)git log --format='%aN <%aE>' | \ + grep -v 'users.noreply.github.com' | sort -u > $@ +EXTRA_DIST += AUTHORS +CLEANFILES += AUTHORS + +if HAVE_MAN_PAGES +### Man Pages +dist_man_MANS = \ + man/man1/tpm2tss-genkey.1 \ + man/man3/tpm2tss_tpm2data_write.3 \ + man/man3/tpm2tss_rsa_makekey.3 \ + man/man3/tpm2tss_rsa_genkey.3 \ + man/man3/tpm2tss_ecc_makekey.3 \ + man/man3/tpm2tss_ecc_genkey.3 \ + man/man3/tpm2tss_ecc_getappdata.3 \ + man/man3/tpm2tss_tpm2data_read.3 \ + man/man3/tpm2tss_ecc_setappdata.3 +endif + +if !HAVE_PANDOC +# If pandoc is not enabled, we want to complain that you need pandoc for make dist, +# so hook the target and complain. +dist-hook: + @(>&2 echo "You do not have pandoc, a requirement for the distribution of manpages") + @exit 1 +endif + +man/man3/tpm2tss_tpm2data_read.3: man/man3/tpm2tss_tpm2data_write.3 + $(AM_V_GEN)(rm $@ 2>/dev/null || true) && ln -s tpm2tss_tpm2data_write.3 $@ + +man/man3/tpm2tss_ecc_setappdata.3: man/man3/tpm2tss_ecc_getappdata.3 + $(AM_V_GEN)(rm $@ 2>/dev/null || true) && ln -s tpm2tss_ecc_getappdata.3 $@ + +man/man1/%.1: man/%.1.md + $(AM_V_GEN)mkdir -p man/man1 && cat $< | $(PANDOC) -s -t man >$@ + +man/man3/%.3: man/%.3.md + $(AM_V_GEN)mkdir -p man/man3 && cat $< | $(PANDOC) -s -t man >$@ + +EXTRA_DIST += \ + man/tpm2tss-genkey.1.md \ + man/tpm2tss_tpm2data_write.3.md \ + man/tpm2tss_rsa_makekey.3.md \ + man/tpm2tss_rsa_genkey.3.md \ + man/tpm2tss_ecc_makekey.3.md \ + man/tpm2tss_ecc_genkey.3.md \ + man/tpm2tss_ecc_getappdata.3.md + +CLEANFILES += \ + $(dist_man_MANS) + +### Bash Completion +bash_completiondir = $(completionsdir) +bash_completion_DATA = bash-completion/tpm2tss-genkey +EXTRA_DIST += bash-completion/tpm2tss-genkey diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..fb9c065 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,101 @@ +# Release Process: +This document describes the general process that maintainers must follow when +making a release of the `tpm2-tss-engine` library and cli-tool. + +# Milestones +All releases should have a milestone used to track the release. If the release version is not known, as covered in [Version Numbers](#Version Numbers), +then an "x" may be used for the unknown number, or the generic term "next" may be used. The description field of the milestone will be used to record +the CHANGELOG for that release. See [CHANGELOG Update](#CHANGELOG Update) for details. + +# Version Numbers +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +In summary: Given a version number MAJOR.MINOR.PATCH, increment the: +1. MAJOR version when you make incompatible API changes, +2. MINOR version when you add functionality in a backwards-compatible manner, and +3. PATCH version when you make backwards-compatible bug fixes. +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + +## Version String +The version string is set for the rest of the autotools bits by autoconf. +Autoconf gets this string from the `AC_INIT` macro in the configure.ac file. +Once you decide on the next version number (using the scheme above) you must set +it manually in configure.ac. The version string must be in the form `A.B.C` +where `A`, `B` and `C` are integers representing the major, minor and micro +components of the version number. + +## Release Candidates +In the run up to a release the maintainers may create tags to identify progress +toward the release. In these cases we will append a string to the release number +to indicate progress using the abbreviation `rc` for 'release candidate'. This +string will take the form of `-rcX`. We append an incremental digit `X` in case +more than one release candidate is necessary to communicate progress as +development moves forward. + +# CHANGELOG Update +Before tagging the repository with the release version, the maintainer MUST update the CHANGELOG file with the contents from the description field +from the corresponding release milestone and update any missing version string details in the CHANGELOG and milestone entry. + +# Git Tags +When a release is made a tag is created in the git repo identifying the release +by the [version string](#Version String). The tag should be pushed to upstream +git repo as the last step in the release process. +**NOTE** tags for release candidates will be deleted from the git repository +after a release with the corresponding version number has been made. +**NOTE** release (not release candidate) tags should be considered immutable. + +## Signed tags +Git supports GPG signed tags and releases will have tags signed by a maintainer. +For details on how to sign and verify git tags see: +https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work. + +# Release tarballs +We use the git tag as a way to mark the point of the release in the projects +history. We do not however encourage users to build from git unless they intend +to modify the source code and contribute to the project. For the end user we +provide release tarballs following the GNU conventions as closely as possible. + +To make a release tarball use the `distcheck` make target. +This target includes a number of sanity checks that are extremely helpful. +For more information on `automake` and release tarballs see: +https://www.gnu.org/software/automake/manual/html_node/Dist.html#Dist + +## Hosting Releases on Github +Github automagically generates a page in their UI that maps git tags to +'releases' (even if the tag isn't for a release). Additionally they support +hosting release tarballs through this same interface. The release tarball +created in the previous step must be posted to github using the release +interface. Additionally, this tarball must be accompanied by a detached GPG +signature. The Debian wiki has an excellent description of how to post a signed +release to Github here: +https://wiki.debian.org/Creating%20signed%20GitHub%20releases +**NOTE** release candidates must be taken down after a release with the +corresponding version number is available. + +## Signing Release Tarballs +Signatures must be generated using the `--detach-sign` and `--armor` options to +the `gpg` command. + +## Verifying Signatures +Verifying the signature on a release tarball requires the project maintainers +public keys be installed in the GPG keyring of the verifier. With both the +release tarball and signature file in the same directory the following command +will verify the signature: +``` +$ gpg --verify tpm2-tss-engine-X.Y.Z.tar.gz.asc +``` + +## Signing Keys +The GPG keys used to sign a release tag and the associated tarball must be the +same. Additionally they must: +* belong to a project maintainer +* be discoverable using a public GPG key server +* be associated with the maintainers github account +(https://help.github.com/articles/adding-a-new-gpg-key-to-your-github-account/) + +# Announcements +Release candidates and proper releases should be announced on the mailing list: + - https://lists.linuxfoundation.org/mailman/listinfo/tpm2 + +This announcement should be accompanied by a link to the release page on Github +as well as a link to the CHANGELOG.md accompanying the release. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a59e069 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +## Supported Versions + +Currently supported versions: + +| Version | Supported | +| ------- | ------------------ | +| any | :white_check_mark: | + +## Reporting a Vulnerability + +### Reporting + +Security vulnerabilities can be disclosed in one of two ways: +- GitHub: *preferred* By following [these](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) instructions. +- Email: A descirption *should be emailed* to **all** members of the [MAINTAINERS](MAINTAINERS) file to coordinate the +disclosure of the vulnerability. + +### Tracking + +When a maintainer is notified of a security vulnerability, they *must* create a GitHub security advisory +per the instructions at: + + - + +Maintainers *should* use the optional feature through GitHub to request a CVE be issued, alternatively RedHat has provided CVE's +in the past and *may* be used, but preference is on GitHub as the issuing CNA. + +### Publishing + +Once ready, maintainers should publish the security vulnerability as outlined in: + + - + +As well as ensuring the publishing of the CVE, maintainers *shal*l have new release versions ready to publish at the same time as +the CVE. Maintainers *should* should strive to adhere to a sub 60 say turn around from report to release. diff --git a/bash-completion/tpm2tss-genkey b/bash-completion/tpm2tss-genkey new file mode 100644 index 0000000..3897ff6 --- /dev/null +++ b/bash-completion/tpm2tss-genkey @@ -0,0 +1,44 @@ +_tpm2tss-genkey() +{ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + case "${prev}" in + -a | --alg) + COMPREPLY=( $(compgen -W "rsa ecdsa" -- ${cur}) ); + return 0 + ;; + -c | --curve) + COMPREPLY=( $(compgen -W "nist_p256" -- ${cur}) ); + return 0 + ;; + -e | --exponent) + COMPREPLY=( $(compgen -W "65537" -- ${cur}) ); + return 0 + ;; + -o | --ownerpw | \ + -p | --password) + COMPREPLY="" + return 0 + ;; + -s | --keysize) + COMPREPLY=( $(compgen -W "2048" -- ${cur}) ); + return 0 + ;; + -W | --parentpw) + COMPREPLY="" + return 0 + ;; + esac; + + opts="-a --alg -c --curve -e --exponent -h --help -o --ownerpw -p --password -s --keysize -v --verbose -W --parentpw" + if [[ ${cur} = -* ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + COMPREPLY=( $(compgen -f ${cur}) ) +} +complete -F _tpm2tss-genkey tpm2tss-genkey diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..ea4b79c --- /dev/null +++ b/bootstrap @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +git describe --tags --always --dirty > VERSION + +autoreconf --install --sym diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..d4a9356 --- /dev/null +++ b/configure.ac @@ -0,0 +1,256 @@ +#;*****************************************************************************; +# Copyright (c) 2018 Fraunhofer SIT sponsored by Infineon Technologies AG +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of tpm2-tss-engine nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +#;*****************************************************************************; +AC_PREREQ([2.68]) + +AC_INIT([tpm2-tss-engine], + [m4_esyscmd_s([cat ./VERSION])], + [https://github.com/tpm2-software/tpm2-tss-engine/issues], + [], + [https://github.com/tpm2-software/tpm2-tss-engine]) + +dnl Let's be FHS-conform by default. +if test "$prefix" = '/usr'; then + test "$sysconfdir" = '${prefix}/etc' && sysconfdir="/etc" + test "$sharedstatedir" = '${prefix}/com' && sharedstatedir="/var" + test "$localstatedir" = '${prefix}/var' && localstatedir="/var" +fi + +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_SRCDIR([src/tpm2-tss-engine.c]) +AC_CONFIG_AUX_DIR([build-aux]) + +# propagate configure arguments to distcheck +AC_SUBST([DISTCHECK_CONFIGURE_FLAGS],[$ac_configure_args]) + +AC_CANONICAL_SYSTEM + +AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Wno-portability]) +#Backward compatible setting of "silent-rules" +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AM_MAINTAINER_MODE([enable]) + +AX_IS_RELEASE([dash-version]) +AX_CHECK_ENABLE_DEBUG([info]) + +AC_PROG_CC +AC_PROG_CC_C99 +AM_PROG_CC_C_O +LT_INIT() + +AC_PROG_MKDIR_P +AC_PROG_LN_S + +AC_CONFIG_HEADERS([src/config.h]) + +AC_ARG_ENABLE([tctienvvar], + [AS_HELP_STRING([--disable-tctienvvar], + [Disable setting the TCTI option from an environment variable])],, + [enable_tctienvvar=yes]) +AS_IF([test "x$enable_tctienvvar" = xyes], [AC_DEFINE([ENABLE_TCTIENVVAR], [1], + 'Enable getting TCTI from env variable')]) + +AC_CONFIG_FILES([Makefile]) + +AC_ARG_ENABLE([defaultflags], + [AS_HELP_STRING([--disable-defaultflags], + [Disable default preprocessor, compiler, and linker flags.])],, + [enable_defaultflags=yes]) +AS_IF([test "x$enable_defaultflags" = "xyes"], + [ + AX_ADD_COMPILER_FLAG([-std=gnu99]) + AX_ADD_COMPILER_FLAG([-Wall]) + AX_ADD_COMPILER_FLAG([-Wextra]) + AX_ADD_COMPILER_FLAG([-Wformat-security]) + AS_IF([test "x$ax_is_release" = "xno"], [AX_ADD_COMPILER_FLAG([-Werror])]) + AX_ADD_COMPILER_FLAG([-fstack-protector-all]) + AX_ADD_COMPILER_FLAG([-fpic]) + AX_ADD_COMPILER_FLAG([-fPIC]) + + # work around GCC bug #53119 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119 + AX_ADD_COMPILER_FLAG([-Wno-missing-braces]) + + AX_ADD_LINK_FLAG([-Wl,--no-undefined]) + AX_ADD_LINK_FLAG([-Wl,-z,noexecstack]) + AX_ADD_LINK_FLAG([-Wl,-z,now]) + AX_ADD_LINK_FLAG([-Wl,-z,relro]) + ]) + +AX_CODE_COVERAGE +m4_ifdef([_AX_CODE_COVERAGE_RULES], + [AM_CONDITIONAL(AUTOCONF_CODE_COVERAGE_2019_01_06, [true])], + [AM_CONDITIONAL(AUTOCONF_CODE_COVERAGE_2019_01_06, [false])]) +AX_ADD_AM_MACRO_STATIC([]) + +PKG_PROG_PKG_CONFIG([0.25]) +PKG_CHECK_MODULES([CRYPTO], [libcrypto >= 1.0.2g], + [ac_enginesdir=`$PKG_CONFIG --variable=enginesdir libcrypto`]) +PKG_CHECK_MODULES([TSS2_ESYS], [tss2-esys >= 2.3]) +PKG_CHECK_MODULES([TSS2_MU], [tss2-mu]) +PKG_CHECK_MODULES([TSS2_TCTILDR], [tss2-tctildr]) +AC_CHECK_LIB([crypto], EC_KEY_METHOD_set_compute_key, + [AM_CONDITIONAL([HAVE_OPENSSL_ECDH], true)], + [AM_CONDITIONAL([HAVE_OPENSSL_ECDH], false)]) +AC_CHECK_LIB([crypto], EVP_PKEY_meth_set_digest_custom, + [AM_CONDITIONAL([HAVE_OPENSSL_DIGEST_SIGN], true)], + [AM_CONDITIONAL([HAVE_OPENSSL_DIGEST_SIGN], false)]) +AS_IF([test "x$ac_cv_lib_crypto_EVP_PKEY_meth_set_digest_custom" = xyes], + [AC_DEFINE([HAVE_OPENSSL_DIGEST_SIGN], [1], + Have required functionality from OpenSSL to support digest and sign)]) + +AC_PATH_PROG([PANDOC], [pandoc]) +AS_IF([test -z "$PANDOC"], + [AC_MSG_WARN([Required executable pandoc not found, man pages will not be built])]) +AM_CONDITIONAL([HAVE_PANDOC],[test -n "$PANDOC"]) +AM_CONDITIONAL([HAVE_MAN_PAGES],[test -d "${srcdir}/man/man1" -o -n "$PANDOC"]) + +AC_PATH_PROG([EXPECT], [expect]) +AS_IF([test -z "$EXPECT"], + [AC_MSG_WARN([Required executable expect not found, some tests might fail])]) + +AC_ARG_WITH([enginesdir], + [AS_HELP_STRING([--with-enginesdir], + [Set the OpenSSL engine directory (default: use pkg-config)])], + [], + [with_enginesdir=$ac_enginesdir]) +AS_IF([test -z "$with_enginesdir"], + [AC_MSG_WARN([Empty enginesdir, using $libdir/engines instead.])]) +# This weirdness is necessary to enable distcheck via DISTCHECK_CONFIGURE_FLAGS +AS_IF([test -z "$with_enginesdir"], + [with_enginesdir=$libdir/engines]) +AC_SUBST(ENGINESDIR, "$with_enginesdir") + +AC_ARG_WITH([completionsdir], + [AS_HELP_STRING([--with-completionsdir], + [Set the bash completions directory (default: use pkg-config)])], + [], + [with_completionsdir=`$PKG_CONFIG --variable=completionsdir bash-completion`]) +AS_IF([test -z "$with_completionsdir"], + [AC_MSG_WARN([Empty completionsdir, using $datarootdir/bash-completion/completions instead.])]) +AS_IF([test -z "$with_completionsdir"], + [with_completionsdir=$datarootdir/bash-completion/completions]) +AC_SUBST(completionsdir, "$with_completionsdir") + +AC_ARG_ENABLE([unit], + [AS_HELP_STRING([--enable-unit], + [build cmocka unit tests])],, + [enable_unit=no]) +AS_IF([test "x$enable_unit" != "xno" ], + [PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.0])]) +AM_CONDITIONAL([UNIT], [test "x$enable_unit" != xno]) + +AC_ARG_ENABLE([integration], + [AS_HELP_STRING([--enable-integration], + [build integration tests against TPM])],, + [enable_integration=no]) +AM_CONDITIONAL([INTEGRATION], [test "x$enable_integration" != xno]) + +# Use physical TPM device for testing +AC_ARG_WITH([device], + [AS_HELP_STRING([--with-device=],[TPM device for testing])], + [AS_IF([test \( -w "$with_device" \) -a \( -r "$with_device" \)], + [AC_MSG_RESULT([success]) + AX_NORMALIZE_PATH([with_device]) + with_device_set=yes], + [AC_MSG_ERROR([TPM device provided does not exist or is not writable])])], + [with_device_set=no]) +AM_CONDITIONAL([TESTDEVICE],[test "x$with_device_set" = xyes]) + +AC_CHECK_FUNC([backtrace_symbols_fd],[AC_DEFINE([HAVE_EXECINFO],[1], ['Define to 1 if you have the header file.'])]) + +# Integration test with simulator +AS_IF([test "x$enable_integration" = xyes && test "x$with_device_set" = xno], + [integration_args="" + AC_CHECK_PROG([tpm2_startup], [tpm2_startup], [yes]) + AS_IF([test "x$tpm2_startup" != xyes], + [AC_MSG_ERROR([Integration tests require the tpm2_startup executable])]) + AC_CHECK_PROG([swtpm], [swtpm], [yes]) + AC_CHECK_PROG([tpm_server], [tpm_server], [yes]) + AS_IF([test "x$swtpm" != xyes && test "x$tpm_server" != xyes], + [AC_MSG_ERROR([Integration tests require either the swtpm or the tpm_server executable])]) + AC_CHECK_PROG([realpath], [realpath], [yes]) + AS_IF([test "x$realpath" != xyes], + [AC_MSG_ERROR([Integration tests require the realpath executable])]) + AC_CHECK_PROG([ss], [ss], [yes]) + AS_IF([test "x$ss" != xyes], + [AC_MSG_ERROR([Integration tests require the ss executable])]) + AS_IF([test "x$enable_tctienvvar" != xyes], + [AC_MSG_ERROR([Integration tests require building with TCTI environment variable support])]) + AC_SUBST([INTEGRATION_ARGS], [$integration_args]) + ]) + +# Integration test with physical device +AS_IF([test "x$enable_integration" = xyes && test "x$with_device_set" = xyes ], + [integration_args="$with_device" + AC_CHECK_PROG([realpath], [realpath], [yes]) + AS_IF([test "x$realpath" != xyes], + [AC_MSG_ERROR([Integration tests require the realpath executable])]) + AS_IF([test "x$enable_tctienvvar" != xyes], + [AC_MSG_ERROR([Integration tests require building with TCTI environment variable support])]) + AC_SUBST([INTEGRATION_ARGS], [$integration_args]) + ]) + +AX_VALGRIND_CHECK + +# +# sanitizer compiler flags +# +AC_ARG_WITH([sanitizer], + [AS_HELP_STRING([--with-sanitizer={none,address,undefined}], + [build with the given sanitizer])],, + [with_sanitizer=none]) +AS_CASE(["x$with_sanitizer"], + ["xnone"], + [], + ["xaddress"], + [ + SANITIZER_CFLAGS="-fsanitize=address -fno-omit-frame-pointer" + SANITIZER_LDFLAGS="-lasan" + ], + ["xundefined"], + [ + SANITIZER_CFLAGS="-fsanitize=undefined" + SANITIZER_LDFLAGS="-lubsan" + ], + [AC_MSG_ERROR([Bad value for --with-sanitizer])]) +AC_SUBST([SANITIZER_CFLAGS]) +AC_SUBST([SANITIZER_LDFLAGS]) + +AC_OUTPUT + +AC_MSG_RESULT([ +$PACKAGE_NAME $VERSION + man-pages: $PANDOC + enginesdir: $with_enginesdir + completionsdir: $with_completionsdir + device: $with_device +]) + diff --git a/include/tpm2-tss-engine.h b/include/tpm2-tss-engine.h new file mode 100644 index 0000000..1d29530 --- /dev/null +++ b/include/tpm2-tss-engine.h @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +#ifndef TPM2_TSS_ENGINE_H +#define TPM2_TSS_ENGINE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + KEY_TYPE_BLOB, + KEY_TYPE_HANDLE +} KEY_TYPE; + +typedef struct { + int emptyAuth; + TPM2B_DIGEST userauth; + TPM2B_PUBLIC pub; + TPM2_HANDLE parent; + KEY_TYPE privatetype; + union { + TPM2B_PRIVATE priv; + TPM2_HANDLE handle; + }; +} TPM2_DATA; + +#define TPM2TSS_SET_OWNERAUTH ENGINE_CMD_BASE +#define TPM2TSS_SET_TCTI (ENGINE_CMD_BASE + 1) +#define TPM2TSS_SET_PARENTAUTH (ENGINE_CMD_BASE + 2) + +int +tpm2tss_tpm2data_write(const TPM2_DATA *tpm2data, const char *filename); + +int +tpm2tss_tpm2data_read(const char *filename, TPM2_DATA **tpm2Datap); + +int +tpm2tss_tpm2data_readtpm(uint32_t handle, TPM2_DATA **tpm2Datap); + +int +tpm2tss_tpm2data_importtpm(const char *filenamepub, const char *filenametpm, + TPM2_HANDLE parent, int emptyAuth, + TPM2_DATA **tpm2Datap); + +EVP_PKEY * +tpm2tss_rsa_makekey(TPM2_DATA *tpm2Data); + +int +tpm2tss_rsa_genkey(RSA *rsa, int bits, BIGNUM *e, char *password, + TPM2_HANDLE parentHandle); + +EVP_PKEY * +tpm2tss_ecc_makekey(TPM2_DATA *tpm2Data); + +int +tpm2tss_ecc_genkey(EC_KEY *key, TPMI_ECC_CURVE curve, const char *password, + TPM2_HANDLE parentHandle); + +TPM2_DATA * +#if OPENSSL_VERSION_NUMBER < 0x10100000 +tpm2tss_ecc_getappdata(EC_KEY *key); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +tpm2tss_ecc_getappdata(const EC_KEY *key); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +int +tpm2tss_ecc_setappdata(EC_KEY *key, TPM2_DATA *data); + +#ifdef __cplusplus +} +#endif +#endif /* TPM2_TSS_ENGINE_H */ diff --git a/m4/flags.m4 b/m4/flags.m4 new file mode 100644 index 0000000..8dcee7d --- /dev/null +++ b/m4/flags.m4 @@ -0,0 +1,52 @@ +dnl AX_ADD_COMPILER_FLAG: +dnl A macro to add a CFLAG to the EXTRA_CFLAGS variable. This macro will +dnl check to be sure the compiler supports the flag. Flags can be made +dnl mandatory (configure will fail). +dnl $1: C compiler flag to add to EXTRA_CFLAGS. +dnl $2: Set to "required" to cause configure failure if flag not supported. +AC_DEFUN([AX_ADD_COMPILER_FLAG],[ + AX_CHECK_COMPILE_FLAG([$1],[ + EXTRA_CFLAGS="$EXTRA_CFLAGS $1" + AC_SUBST([EXTRA_CFLAGS])],[ + AS_IF([test x$2 != xrequired],[ + AC_MSG_WARN([Optional CFLAG "$1" not supported by your compiler, continuing.])],[ + AC_MSG_ERROR([Required CFLAG "$1" not supported by your compiler, aborting.])] + )],[ + -Wall -Werror] + )] +) +dnl AX_ADD_PREPROC_FLAG: +dnl Add the provided preprocessor flag to the EXTRA_CFLAGS variable. This +dnl macro will check to be sure the preprocessor supports the flag. +dnl The flag can be made mandatory by providing the string 'required' as +dnl the second parameter. +dnl $1: Preprocessor flag to add to EXTRA_CFLAGS. +dnl $2: Set to "required" t ocause configure failure if preprocesor flag +dnl is not supported. +AC_DEFUN([AX_ADD_PREPROC_FLAG],[ + AX_CHECK_PREPROC_FLAG([$1],[ + EXTRA_CFLAGS="$EXTRA_CFLAGS $1" + AC_SUBST([EXTRA_CFLAGS])],[ + AS_IF([test x$2 != xrequired],[ + AC_MSG_WARN([Optional preprocessor flag "$1" not supported by your compiler, continuing.])],[ + AC_MSG_ERROR([Required preprocessor flag "$1" not supported by your compiler, aborting.])] + )],[ + -Wall -Werror] + )] +) +dnl AX_ADD_LINK_FLAG: +dnl A macro to add a LDLAG to the EXTRA_LDFLAGS variable. This macro will +dnl check to be sure the linker supports the flag. Flags can be made +dnl mandatory (configure will fail). +dnl $1: linker flag to add to EXTRA_LDFLAGS. +dnl $2: Set to "required" to cause configure failure if flag not supported. +AC_DEFUN([AX_ADD_LINK_FLAG],[ + AX_CHECK_LINK_FLAG([$1],[ + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $1" + AC_SUBST([EXTRA_LDFLAGS])],[ + AS_IF([test x$2 != xrequired],[ + AC_MSG_WARN([Optional LDFLAG "$1" not supported by your linker, continuing.])],[ + AC_MSG_ERROR([Required LDFLAG "$1" not supported by your linker, aborting.])] + )] + )] +) diff --git a/man/tpm2tss-genkey.1.md b/man/tpm2tss-genkey.1.md new file mode 100644 index 0000000..ba18174 --- /dev/null +++ b/man/tpm2tss-genkey.1.md @@ -0,0 +1,118 @@ +% tpm2tss-genkey(1) tpm2-tss-engine | General Commands Manual +% +% OCTOBER 2020 + +# NAME +**tpm2tss-genkey**(1) -- generate TPM keys for tpm2-tss-engine + +# SYNOPSIS + +**tpm2tss-genkey** [*options*] <*filename*> + +# DESCRIPTION + +**tpm2tss-genkey** creates a key inside a TPM 2.0 connected via the +tpm2tss software stack. Those keys may be an RSA key for decryption or signing +or an ECC key for ECDSA signatures. + +The tool respects the OPENSSL_CONF option for specifying engine specific control +parameters. See `man(5) config` for details on openssl config files. + +# ARGUMENTS + +The `tpm2tss-genkey` command expects a filename for storing the resulting TPM +key information. This file can then be loaded with OpenSSL using +`openssl pkeyutl -engine tpm2tss -keyform engine -inkey `. + +# OPTIONS + + * `-a `, `--alg `: + The public key algorithm (rsa, ecdsa) (default: rsa) + + * `-c `, `--curve `: + If alg ecdsa is chosen, the curve for ecc (default: nist_p256) + + * `-u `, `--public `: + Public key (TPM2B_PUBLIC) to be imported. Requires `-r`. + + * `-r `, `--private `: + The (encrypted) private key (TPM2B_PRIVATE) to be imported. + Requires `-u`. + + * `-e `, `--exponent `: + If alg rsa is chosen, the exponent for rsa (default: 65537) + + * `-h`, `--help`: + Print help + + * `-o `, `--ownerpw `: + Password for the owner hierarchy (default: none) + Openssl Config control command: `SET_OWNERAUTH` + + * `-p `, `--password `: + Password for the created key (default: none) + + * `-P `, `--parent `: + Specific handle for the parent key (default: none) + + * `-s `, `--keysize `: + If alg rsa is chosen, the key size in bits (default: 2048) + + * `-v`, `--verbose`: + Print verbose messages + + * `-W `, `--parentpw `: + Password for the parent key (default: none) + Openssl Config control command: `SET_PARENTAUTH` + + * `-t `, `--tcti `: + TCTI Configuration string (default: none) + Openssl Config control command: `SET_TCTI` + +# EXAMPLES + +Engine information can be retrieved using: +``` +$ openssl engine -t -c tpm2tss +``` +The following sequence of commands creates an RSA key using the TPM, exports the +public key, encrypts a data file and decrypts it using the TPM: +``` +$ tpm2tss-genkey -a rsa -s 2048 mykey +$ openssl rsa -engine tpm2tss -inform engine -in mykey -pubout -outform pem -out mykey.pub +$ openssl pkeyutl -pubin -inkey mykey.pub -in mydata -encrypt -out mycipher +$ openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -decrypt -in mycipher -out mydata +``` +The following sequence of commands creates an RSA key using the TPM, exports the +public key, signs a data file using the TPM and validates the signature: +``` +$ tpm2tss-genkey -a rsa -s 2048 mykey +$ openssl rsa -engine tpm2tss -inform engine -in mykey -pubout -outform pem -out mykey.pub +$ openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -sign -in mydata -out mysig +$ openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -verify -in mydata -sigfile mysig +``` +The following sequence of commands creates an ECDSA key using the TPM, exports +the public key, signs a data file using the TPM and validates the signature: +``` +$ tpm2tss-genkey -a ecdsa -c nist_p256 mykey +$ openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -sign -in mydata -out mysig +$ openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -verify -in mydata -sigfile mysig +``` + +# RETURNS + +0 on success or 1 on failure. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2017-2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1) + diff --git a/man/tpm2tss_ecc_genkey.3.md b/man/tpm2tss_ecc_genkey.3.md new file mode 100644 index 0000000..7084815 --- /dev/null +++ b/man/tpm2tss_ecc_genkey.3.md @@ -0,0 +1,36 @@ +% tpm2tss-tpm2data_write(3) tpm2-tss-engine | Library calls +% +% JUNE 2018 + +# NAME +**tpm2tss_ecc_genkey** -- Make an ECC key object + +# SYNOPSIS + +**#include ** + +**int tpm2tss_ecc_genkey(EC_KEY *key, TPMI_ECC_CURVE curve, const char *password);** + +# DESCRIPTION + +**tpm2tss_ECC_genkey** issues the generation of an ECC key `key` using the TPM. +The ECC curve is determined by `curve`. The new key will be protected by +`password`. + +# RETURN VALUE + +Upon successful completion **tpm2tss_ecc_genkey**() returns 1. Otherwise 0. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1), tpm2tss_genkey(1) + diff --git a/man/tpm2tss_ecc_getappdata.3.md b/man/tpm2tss_ecc_getappdata.3.md new file mode 100644 index 0000000..32abb5b --- /dev/null +++ b/man/tpm2tss_ecc_getappdata.3.md @@ -0,0 +1,39 @@ +% tpm2tss-tpm2data_write(3) tpm2-tss-engine | Library calls +% +% JUNE 2018 + +# NAME +**tpm2tss_ecc_getappdata**, **tpm2tss_ecc_setappdata** -- Make an ECC key object + +# SYNOPSIS + +**#include ** + +**TPM2_DATA * tpm2tss_ecc_getappdata(const EC_KEY *key);** + +**int tpm2tss_ecc_setappdata(EC_KEY *key, TPM2_DATA *data);** + +# DESCRIPTION + +**tpm2tss_ecc_getappdata** + +**tpm2tss_ecc_setappdata** + +# RETURN VALUE + +Upon successful completion **tpm2tss_ecc_getappdata**() and +**tpm2tss_ecc_setappdata**() return 1. Otherwise 0. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1), tpm2tss_genkey(1) + diff --git a/man/tpm2tss_ecc_makekey.3.md b/man/tpm2tss_ecc_makekey.3.md new file mode 100644 index 0000000..fe9cd31 --- /dev/null +++ b/man/tpm2tss_ecc_makekey.3.md @@ -0,0 +1,36 @@ +% tpm2tss-tpm2data_write(3) tpm2-tss-engine | Library calls +% +% JUNE 2018 + +# NAME +**tpm2tss_ecc_makekey** -- Make an ECC key object + +# SYNOPSIS + +**#include ** + +**EVP_PKEY * tpm2tss_ecc_makekey(TPM2_DATA *tpm2Data);** + +# DESCRIPTION + +**tpm2tss_ecc_makekey** takes a TPM2_DATA object as `tpm2Data` and creates a +corresponding OpenSSL EVP_PKEY object. + +# RETURN VALUE + +Upon successful completion **tpm2tss_ecc_makekey**() returns the created +EVP_PKEY object's pointer. Otherwise NULL. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1) + diff --git a/man/tpm2tss_rsa_genkey.3.md b/man/tpm2tss_rsa_genkey.3.md new file mode 100644 index 0000000..baa2dbe --- /dev/null +++ b/man/tpm2tss_rsa_genkey.3.md @@ -0,0 +1,36 @@ +% tpm2tss-tpm2data_write(3) tpm2-tss-engine | Library calls +% +% JUNE 2018 + +# NAME +**tpm2tss_rsa_genkey** -- Make an RSA key object + +# SYNOPSIS + +**#include ** + +**int tpm2tss_rsa_genkey(RSA *rsa, int bits, BIGNUM *e, char *password);** + +# DESCRIPTION + +**tpm2tss_rsa_genkey** issues the generation of an RSA key `rsa` using the TPM. +The keylength is determined by `bits`. The exponent is determined by `e`. +The new key will be protected by `password`. + +# RETURN VALUE + +Upon successful completion **tpm2tss_rsa_genkey**() returns 1. Otherwise 0. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1), tpm2tss_genkey(1) + diff --git a/man/tpm2tss_rsa_makekey.3.md b/man/tpm2tss_rsa_makekey.3.md new file mode 100644 index 0000000..7fbe13b --- /dev/null +++ b/man/tpm2tss_rsa_makekey.3.md @@ -0,0 +1,36 @@ +% tpm2tss-tpm2data_write(3) tpm2-tss-engine | Library calls +% +% JUNE 2018 + +# NAME +**tpm2tss_rsa_makekey** -- Make an RSA key object + +# SYNOPSIS + +**#include ** + +**EVP_PKEY * tpm2tss_rsa_makekey(TPM2_DATA *tpm2Data);** + +# DESCRIPTION + +**tpm2tss_rsa_makekey** takes a TPM2_DATA object as `tpm2Data` and creates a +corresponding OpenSSL EVP_PKEY object. + +# RETURN VALUE + +Upon successful completion **tpm2tss_rsa_makekey**() returns the created +EVP_PKEY object's pointer. Otherwise NULL. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1) + diff --git a/man/tpm2tss_tpm2data_write.3.md b/man/tpm2tss_tpm2data_write.3.md new file mode 100644 index 0000000..7ccc072 --- /dev/null +++ b/man/tpm2tss_tpm2data_write.3.md @@ -0,0 +1,42 @@ +% tpm2tss-tpm2data_write(3) tpm2-tss-engine | Library calls +% +% JUNE 2018 + +# NAME +**tpm2tss_tpm2data_write**, **tpm2tss_tpm2data_read** -- read/write TPM2_DATA + +# SYNOPSIS + +**#include ** + +**int tpm2tss_tpm2data_read(const char *filename, TPM2_DATA **tpm2Datap);** + +**int tpm2tss_tpm2data_write(const TPM2_DATA *tpm2Data, const char *filename);** + +# DESCRIPTION + +**tpm2tss_tpm2data_read** reads the TPM2_DATA object from a file called +`filename`, allocates memory and stores it under the parameter `tpm2Datap`. +Must be freed using the `free()` function. + +**tpm2tss_tpm2data_write** writes the TPM2_DATA object from the parameter +`tpm2Data` to a newly created file called `filename`. + +# RETURN VALUE + +Upon successful completion **tpm2tss_tpm2data_write**() and +**tpm2tss_tpm2data_read**() return 1. Otherwise 0. + +## AUTHOR + +Written by Andreas Fuchs. + +## COPYRIGHT + +tpm2tss is Copyright (C) 2018 Fraunhofer SIT sponsored by Infineon +Technologies AG. License BSD 3-clause. + +## SEE ALSO + +openssl(1) + diff --git a/openssl.conf.sample b/openssl.conf.sample new file mode 100644 index 0000000..dcb136e --- /dev/null +++ b/openssl.conf.sample @@ -0,0 +1,22 @@ +openssl_conf = openssl_init + +[openssl_init] +engines = engine_section + +[engine_section] +tpm2tss = tpm2tss_section + +[tpm2tss_section] +engine_id = tpm2tss +dynamic_path = /usr/lib/engines-1.1/libtpm2tss.so +default_algorithms = RSA,ECDSA +init = 1 +#SET_TCTI = +#SET_OWNERAUTH = +#SET_PARENTAUTH = + +[req] +distinguished_name = subject + +[subject] +# prompts and defaults here diff --git a/src/tpm2-tss-engine-common.c b/src/tpm2-tss-engine-common.c new file mode 100755 index 0000000..9dc100d --- /dev/null +++ b/src/tpm2-tss-engine-common.c @@ -0,0 +1,698 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * Copyright (c) 2019, Wind River Systems. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include + +#include + +#include +#include +#include + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +ASN1_SEQUENCE(TSSPRIVKEY) = { + ASN1_SIMPLE(TSSPRIVKEY, type, ASN1_OBJECT), + ASN1_EXP_OPT(TSSPRIVKEY, emptyAuth, ASN1_BOOLEAN, 0), + ASN1_SIMPLE(TSSPRIVKEY, parent, ASN1_INTEGER), + ASN1_SIMPLE(TSSPRIVKEY, pubkey, ASN1_OCTET_STRING), + ASN1_SIMPLE(TSSPRIVKEY, privkey, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(TSSPRIVKEY) + +#define TSSPRIVKEY_PEM_STRING "TSS2 PRIVATE KEY" + +IMPLEMENT_ASN1_FUNCTIONS(TSSPRIVKEY); +IMPLEMENT_PEM_write_bio(TSSPRIVKEY, TSSPRIVKEY, TSSPRIVKEY_PEM_STRING, TSSPRIVKEY); +IMPLEMENT_PEM_read_bio(TSSPRIVKEY, TSSPRIVKEY, TSSPRIVKEY_PEM_STRING, TSSPRIVKEY); + +/** Initialize the Esys context + * + * Initialize an Esys context. + * @param esys_ctx The context to initialize. + * @retval TSS2_RC_SUCCESS on success + * @retval TSS2_BASE_RC_BAD_REFERENCE if no pointer was provided + * @retval Errors from Tcti initialization or Esys_Initialize() + */ +TSS2_RC +esys_ctx_init(ESYS_CONTEXT **esys_ctx) +{ + + TSS2_RC r; + if (!esys_ctx) { + ERR(esys_ctx_init, TPM2TSS_R_GENERAL_FAILURE); + r = TSS2_BASE_RC_BAD_REFERENCE; + } else { + TSS2_TCTI_CONTEXT *tcti_ctx = NULL; + + r = Tss2_TctiLdr_Initialize(tcti_nameconf, &tcti_ctx); + if (TSS2_RC_SUCCESS != r) { + ERR(esys_ctx_init, TPM2TSS_R_GENERAL_FAILURE); + } else { + r = Esys_Initialize(esys_ctx, tcti_ctx, NULL); + if (TSS2_RC_SUCCESS != r) { + ERR(esys_ctx_init, TPM2TSS_R_GENERAL_FAILURE); + Tss2_TctiLdr_Finalize(&tcti_ctx); + } + } + } + return r; +} + +/** Finalize the Esys context + * + * Get the TCTI context and finalize this alongside the Esys context. + * @param esys_ctx The Esys context + * @retval TSS2_RC_SUCCESS on success + * @retval TSS2_BASE_RC_BAD_REFERENCE if no pointer was provided + * @retval Errors from Esys_GetTcti() + */ +TSS2_RC +esys_ctx_free(ESYS_CONTEXT **esys_ctx) +{ + TSS2_RC r; + if ((!esys_ctx) || (!*esys_ctx)) { + ERR(esys_ctx_free, TPM2TSS_R_GENERAL_FAILURE); + r = TSS2_BASE_RC_BAD_REFERENCE; + } else { + TSS2_TCTI_CONTEXT *tcti_ctx; + r = Esys_GetTcti(*esys_ctx, &tcti_ctx); + Esys_Finalize(esys_ctx); + if (TSS2_RC_SUCCESS != r) { + ERR(esys_ctx_free, TPM2TSS_R_GENERAL_FAILURE); + } else { + Tss2_TctiLdr_Finalize(&tcti_ctx); + } + } + return r; +} + +/** Serialize tpm2data onto disk + * + * Write the tpm2tss key data into a file using PEM encoding. + * @param tpm2Data The data to be written to disk. + * @param filename The filename to write the data to. + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_tpm2data_write(const TPM2_DATA *tpm2Data, const char *filename) +{ + TSS2_RC r; + BIO *bio = NULL; + TSSPRIVKEY *tpk = NULL; + BIGNUM *bn_parent = NULL; + + uint8_t privbuf[sizeof(tpm2Data->priv)]; + uint8_t pubbuf[sizeof(tpm2Data->pub)]; + size_t privbuf_len = 0, pubbuf_len = 0; + + if ((bio = BIO_new_file(filename, "w")) == NULL) { + ERR(tpm2tss_tpm2data_write, TPM2TSS_R_FILE_WRITE); + goto error; + } + + tpk = TSSPRIVKEY_new(); + if (!tpk) { + ERR(tpm2tss_tpm2data_write, ERR_R_MALLOC_FAILURE); + goto error; + } + + r = Tss2_MU_TPM2B_PRIVATE_Marshal(&tpm2Data->priv, &privbuf[0], + sizeof(privbuf), &privbuf_len); + if (r) { + ERR(tpm2tss_tpm2data_write, TPM2TSS_R_DATA_CORRUPTED); + goto error; + } + + r = Tss2_MU_TPM2B_PUBLIC_Marshal(&tpm2Data->pub, &pubbuf[0], + sizeof(pubbuf), &pubbuf_len); + if (r) { + ERR(tpm2tss_tpm2data_write, TPM2TSS_R_DATA_CORRUPTED); + goto error; + } + tpk->type = OBJ_txt2obj(OID_loadableKey, 1); + tpk->parent = ASN1_INTEGER_new(); + tpk->privkey = ASN1_OCTET_STRING_new(); + tpk->pubkey = ASN1_OCTET_STRING_new(); + if (!tpk->type || !tpk->privkey || !tpk->pubkey || !tpk->parent) { + ERR(tpm2tss_tpm2data_write, ERR_R_MALLOC_FAILURE); + goto error; + } + + tpk->emptyAuth = tpm2Data->emptyAuth ? 0xFF : 0; + bn_parent = BN_new(); + if (!bn_parent) { + goto error; + } + if (tpm2Data->parent != 0) { + BN_set_word(bn_parent, tpm2Data->parent); + } else { + BN_set_word(bn_parent, TPM2_RH_OWNER); + } + BN_to_ASN1_INTEGER(bn_parent, tpk->parent); + ASN1_STRING_set(tpk->privkey, &privbuf[0], privbuf_len); + ASN1_STRING_set(tpk->pubkey, &pubbuf[0], pubbuf_len); + + PEM_write_bio_TSSPRIVKEY(bio, tpk); + TSSPRIVKEY_free(tpk); + BIO_free(bio); + + return 1; + error: + if (bio) + BIO_free(bio); + if (tpk) + TSSPRIVKEY_free(tpk); + return 0; +} + +/** Create tpm2data from a TPM key + * + * Retrieve the public key of tpm2data from the TPM for a given handle. + * @param handle The TPM's key handle. + * @param tpm2Datap The data after read. + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_tpm2data_readtpm(uint32_t handle, TPM2_DATA **tpm2Datap) +{ + TSS2_RC r; + TPM2_DATA *tpm2Data = NULL; + ESYS_TR keyHandle = ESYS_TR_NONE; + ESYS_CONTEXT *esys_ctx = NULL; + TPM2B_PUBLIC *outPublic; + + tpm2Data = OPENSSL_malloc(sizeof(*tpm2Data)); + if (tpm2Data == NULL) { + ERR(tpm2tss_tpm2data_readtpm, ERR_R_MALLOC_FAILURE); + goto error; + } + memset(tpm2Data, 0, sizeof(*tpm2Data)); + + tpm2Data->privatetype = KEY_TYPE_HANDLE; + tpm2Data->handle = handle; + + r = esys_ctx_init(&esys_ctx); + if (r) { + ERR(tpm2tss_tpm2data_readtpm, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + r = Esys_TR_FromTPMPublic(esys_ctx, tpm2Data->handle, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &keyHandle); + if (r) { + ERR(tpm2tss_tpm2data_readtpm, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + r = Esys_ReadPublic(esys_ctx, keyHandle, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &outPublic, NULL, NULL); + if (r) { + ERR(tpm2tss_tpm2data_readtpm, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + /* If the persistent key has the NODA flag set, we check whether it does + have an empty authValue. If NODA is not set, then we don't check because + that would increment the DA lockout counter */ + if ((outPublic->publicArea.objectAttributes & TPMA_OBJECT_NODA) != 0) { + ESYS_TR session; + TPMT_SYM_DEF sym = {.algorithm = TPM2_ALG_AES, + .keyBits = {.aes = 128}, + .mode = {.aes = TPM2_ALG_CFB} + }; + + /* Esys_StartAuthSession() and session handling use OpenSSL for random + bytes and thus might end up inside this engine again. This becomes + a problem if we have no resource manager, i.e. the tpm simulator. */ + const RAND_METHOD *rand_save = RAND_get_rand_method(); +#if OPENSSL_VERSION_NUMBER < 0x10100000 + RAND_set_rand_method(RAND_SSLeay()); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + RAND_set_rand_method(RAND_OpenSSL()); +#endif + + /* We do the check by starting a bound audit session and executing a + very cheap command. */ + r = Esys_StartAuthSession(esys_ctx, ESYS_TR_NONE, keyHandle, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + NULL, TPM2_SE_HMAC, &sym, TPM2_ALG_SHA256, + &session); + /* Though this response code is sub-optimal, it's the only way to + detect the bug in ESYS. */ + if (r == TSS2_ESYS_RC_GENERAL_FAILURE) { + DBG("Running tpm2-tss < 2.2 which has a bug here. Requiring auth."); + tpm2Data->emptyAuth = 0; + goto session_error; + } else if (r) { + ERR(tpm2tss_tpm2data_readtpm, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + Esys_TRSess_SetAttributes(esys_ctx, session, + TPMA_SESSION_ENCRYPT, TPMA_SESSION_ENCRYPT); + Esys_TRSess_SetAttributes(esys_ctx, session, + TPMA_SESSION_CONTINUESESSION, + TPMA_SESSION_CONTINUESESSION); + + r = Esys_ReadPublic(esys_ctx, keyHandle, + session, ESYS_TR_NONE, ESYS_TR_NONE, + NULL, NULL, NULL); + + RAND_set_rand_method(rand_save); + + /* tpm2-tss < 2.2 has some bugs. (1) it may miscalculate the auth from + above leading to a password query in case of empty auth and (2) it + may return an error because the object's auth value is "\0". */ + if (r == TSS2_RC_SUCCESS) { + DBG("Object does not require auth"); + tpm2Data->emptyAuth = 1; + } else if (r == (TPM2_RC_BAD_AUTH | TPM2_RC_S | TPM2_RC_1)) { + DBG("Object does require auth"); + tpm2Data->emptyAuth = 0; + } else { + ERR(tpm2tss_tpm2data_readtpm, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + Esys_FlushContext (esys_ctx, session); + } + +session_error: + + Esys_TR_Close(esys_ctx, &keyHandle); + + esys_ctx_free(&esys_ctx); + tpm2Data->pub = *outPublic; + Esys_Free(outPublic); + + *tpm2Datap = tpm2Data; + return 1; + error: + if (keyHandle != ESYS_TR_NONE) + Esys_TR_Close(esys_ctx, &keyHandle); + esys_ctx_free(&esys_ctx); + if (tpm2Data) + OPENSSL_free(tpm2Data); + return 0; +} + +/** Deserialize tpm2data from disk + * + * Read the tpm2tss key data from a file using PEM encoding. + * @param filename The filename to read the data from. + * @param tpm2Datap The data after read. + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_tpm2data_read(const char *filename, TPM2_DATA **tpm2Datap) +{ + TSS2_RC r; + BIO *bio = NULL; + TSSPRIVKEY *tpk = NULL; + TPM2_DATA *tpm2Data = NULL; + char type_oid[64]; + BIGNUM *bn_parent; + + if ((bio = BIO_new_file(filename, "r")) == NULL) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_FILE_READ); + goto error; + } + + tpk = PEM_read_bio_TSSPRIVKEY(bio, NULL, NULL, NULL); + if (!tpk) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_DATA_CORRUPTED); + goto error; + } + BIO_free(bio); + bio = NULL; + + tpm2Data = OPENSSL_malloc(sizeof(*tpm2Data)); + if (tpm2Data == NULL) { + ERR(tpm2tss_tpm2data_read, ERR_R_MALLOC_FAILURE); + goto error; + } + memset(tpm2Data, 0, sizeof(*tpm2Data)); + + tpm2Data->privatetype = KEY_TYPE_BLOB; + + tpm2Data->emptyAuth = !!tpk->emptyAuth; + + bn_parent = ASN1_INTEGER_to_BN(tpk->parent, NULL); + if (!bn_parent) { + goto error; + } + if (BN_is_negative(bn_parent)) { + tpm2Data->parent = ASN1_INTEGER_get(tpk->parent); + } else { + tpm2Data->parent = BN_get_word(bn_parent); + } + if (tpm2Data->parent == 0) + tpm2Data->parent = TPM2_RH_OWNER; + + if (!OBJ_obj2txt(type_oid, sizeof(type_oid), tpk->type, 1) || + strcmp(type_oid, OID_loadableKey)) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_CANNOT_MAKE_KEY); + goto error; + } + r = Tss2_MU_TPM2B_PRIVATE_Unmarshal(tpk->privkey->data, + tpk->privkey->length, NULL, + &tpm2Data->priv); + if (r) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_DATA_CORRUPTED); + goto error; + } + r = Tss2_MU_TPM2B_PUBLIC_Unmarshal(tpk->pubkey->data, tpk->pubkey->length, + NULL, &tpm2Data->pub); + if (r) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_DATA_CORRUPTED); + goto error; + } + + TSSPRIVKEY_free(tpk); + + *tpm2Datap = tpm2Data; + return 1; + error: + if (tpm2Data) + OPENSSL_free(tpm2Data); + if (bio) + BIO_free(bio); + if (tpk) + TSSPRIVKEY_free(tpk); + + return 0; +} + +static TPM2B_PUBLIC primaryEccTemplate = TPM2B_PUBLIC_PRIMARY_ECC_TEMPLATE; +static TPM2B_PUBLIC primaryRsaTemplate = TPM2B_PUBLIC_PRIMARY_RSA_TEMPLATE; + +static TPM2B_SENSITIVE_CREATE primarySensitive = { + .sensitive = { + .userAuth = { + .size = 0, + }, + .data = { + .size = 0, + } + } +}; + +static TPM2B_DATA allOutsideInfo = { + .size = 0, +}; + +static TPML_PCR_SELECTION allCreationPCR = { + .count = 0, +}; + +/** Initialize the ESYS TPM connection and primary/persistent key + * + * Establish a connection with the TPM using ESYS libraries and create a primary + * key under the owner hierarchy or to initialize the ESYS object for a + * persistent if provided. + * @param esys_ctx The resulting ESYS context. + * @param parentHandle The TPM handle of a persistent key or TPM2_RH_OWNER or 0 + * @param parent The resulting ESYS_TR handle for the parent key. + * @retval TSS2_RC_SUCCESS on success + * @retval TSS2_RCs according to the error + */ +TSS2_RC +init_tpm_parent(ESYS_CONTEXT **esys_ctx, + TPM2_HANDLE parentHandle, ESYS_TR *parent) +{ + TSS2_RC r; + TPM2B_PUBLIC *primaryTemplate = NULL; + TPMS_CAPABILITY_DATA *capabilityData = NULL; + UINT32 index; + *parent = ESYS_TR_NONE; + *esys_ctx = NULL; + + DBG("Establishing connection with TPM.\n"); + r = esys_ctx_init(esys_ctx); + ERRchktss(init_tpm_parent, r, goto error); + + if (parentHandle && parentHandle != TPM2_RH_OWNER) { + DBG("Connecting to a persistent parent key.\n"); + r = Esys_TR_FromTPMPublic(*esys_ctx, parentHandle, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + parent); + ERRchktss(init_tpm_parent, r, goto error); + + r = Esys_TR_SetAuth(*esys_ctx, *parent, &parentauth); + ERRchktss(init_tpm_parent, r, goto error); + + return TSS2_RC_SUCCESS; + } + + DBG("Creating primary key under owner.\n"); + r = Esys_TR_SetAuth(*esys_ctx, ESYS_TR_RH_OWNER, &ownerauth); + ERRchktss(init_tpm_parent, r, goto error); + + r = Esys_GetCapability (*esys_ctx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_ALGS, 0, TPM2_MAX_CAP_ALGS, + NULL, &capabilityData); + ERRchktss(init_tpm_parent, r, goto error); + + for (index = 0; index < capabilityData->data.algorithms.count; index++) { + if (capabilityData->data.algorithms.algProperties[index].alg == TPM2_ALG_ECC) { + primaryTemplate = &primaryEccTemplate; + break; + } + } + + /* + * TPM2_ALG_ECC is *mandatory* for TPM2.0; the above should never + * fail. However, *if* such a broken TPM is used then ephemeral + * primaries according to the TSS2 PEM file standard can *never* + * have worked on that hardware, so it isn't *breaking* anything + * for us to unilaterally use an ephemeral RSA parent in this case + * instead. + * + * However, it may not be interoperable to do so, and it isn't a + * good idea anyway since RSA keys are *slow* to generate, so + * users with a broken TPM like this really *should* have followed + * the recommendation to create the RSA primary and store it in + * the NVRAM at 0x81000001. And then the TSS2 PEM keys should use + * *that* as the parent, not the ephemeral version. In fact, there + * is a strong case to be made for defaulting to 0x81000001 if it + * exists, *before* (or never) falling back to generating an RSA + * key here. + */ + if (primaryTemplate == NULL) { + for (index = 0; index < capabilityData->data.algorithms.count; index++) { + if (capabilityData->data.algorithms.algProperties[index].alg == TPM2_ALG_RSA) { + primaryTemplate = &primaryRsaTemplate; + break; + } + } + } + + Esys_Free (capabilityData); + + if (primaryTemplate == NULL) { + ERR(init_tpm_parent, TPM2TSS_R_UNKNOWN_ALG); + goto error; + } + + r = Esys_CreatePrimary(*esys_ctx, ESYS_TR_RH_OWNER, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &primarySensitive, primaryTemplate, &allOutsideInfo, + &allCreationPCR, + parent, NULL, NULL, NULL, NULL); + if (r == 0x000009a2) { + ERR(init_tpm_parent, TPM2TSS_R_OWNER_AUTH_FAILED); + goto error; + } + ERRchktss(init_tpm_parent, r, goto error); + + return TSS2_RC_SUCCESS; + error: + if (*parent != ESYS_TR_NONE) + Esys_FlushContext(*esys_ctx, *parent); + *parent = ESYS_TR_NONE; + + esys_ctx_free(esys_ctx); + return r; +} + +/** Initialize the ESYS TPM connection and load the key + * + * Establish a connection with the TPM using ESYS libraries, create a primary + * key under the owner hierarchy and then load the TPM key and set its auth + * value. + * @param esys_ctx The ESYS_CONTEXT to be populated. + * @param keyHandle The resulting handle for the key key. + * @param tpm2Data The key data, owner auth and key auth to be used + * @retval TSS2_RC_SUCCESS on success + * @retval TSS2_RCs according to the error + */ +TSS2_RC +init_tpm_key (ESYS_CONTEXT **esys_ctx, ESYS_TR *keyHandle, TPM2_DATA *tpm2Data) +{ + TSS2_RC r; + ESYS_TR parent = ESYS_TR_NONE; + *keyHandle = ESYS_TR_NONE; + *esys_ctx = NULL; + + if (tpm2Data->privatetype == KEY_TYPE_HANDLE) { + DBG("Establishing connection with TPM.\n"); + r = esys_ctx_init(esys_ctx); + ERRchktss(init_tpm_key, r, goto error); + + r = Esys_TR_FromTPMPublic(*esys_ctx, tpm2Data->handle, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + keyHandle); + ERRchktss(init_tpm_key, r, goto error); + } else if (tpm2Data->privatetype == KEY_TYPE_BLOB + && tpm2Data->parent != TPM2_RH_OWNER) { + r = init_tpm_parent(esys_ctx, tpm2Data->parent, &parent); + ERRchktss(init_tpm_key, r, goto error); + + DBG("Loading key blob.\n"); + r = Esys_Load(*esys_ctx, parent, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &tpm2Data->priv, &tpm2Data->pub, keyHandle); + Esys_TR_Close(*esys_ctx, &parent); + ERRchktss(init_tpm_key, r, goto error); + } else if (tpm2Data->privatetype == KEY_TYPE_BLOB) { + r = init_tpm_parent(esys_ctx, 0, &parent); + ERRchktss(init_tpm_key, r, goto error); + + DBG("Loading key blob.\n"); + r = Esys_Load(*esys_ctx, parent, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &tpm2Data->priv, &tpm2Data->pub, keyHandle); + ERRchktss(init_tpm_key, r, goto error); + + r = Esys_FlushContext(*esys_ctx, parent); + ERRchktss(rsa_priv_enc, r, goto error); + parent = ESYS_TR_NONE; + } else { + r = -1; + ERR(init_tpm_key, TPM2TSS_R_TPM2DATA_READ_FAILED); + goto error; + } + + r = Esys_TR_SetAuth(*esys_ctx, *keyHandle, &tpm2Data->userauth); + ERRchktss(init_tpm_key, r, goto error); + + return TSS2_RC_SUCCESS; + error: + if (parent != ESYS_TR_NONE) + Esys_FlushContext(*esys_ctx, parent); + if (*keyHandle != ESYS_TR_NONE) + Esys_FlushContext(*esys_ctx, *keyHandle); + *keyHandle = ESYS_TR_NONE; + + esys_ctx_free(esys_ctx); + return r; +} + +/** Deserialize a tpm key from disk + * + * Read a tpm key as marshaled TPM2B_PUBLIC and (encrypted) TPM2B_PRIVATE from + * disk and convert them into a TPM2_DATA representation + * @param filenamepub The filename to read the public portion from. + * @param filenametpm The filename to read the private portion from. + * @param parent Handle of the parent key. + * @param emptyAuth Whether the object does not require authentication. + * @param tpm2Datap The data after read. + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_tpm2data_importtpm(const char *filenamepub, const char *filenametpm, + TPM2_HANDLE parent, int emptyAuth, + TPM2_DATA **tpm2Datap) +{ + TSS2_RC r; + BIO *bio; + TPM2_DATA *tpm2data; + int filepub_size, filepriv_size; + + uint8_t filepub[sizeof(TPM2B_PUBLIC)]; + uint8_t filepriv[sizeof(TPM2B_PRIVATE)]; + + if ((bio = BIO_new_file(filenamepub, "r")) == NULL) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_FILE_READ); + return 0; + } + filepub_size = BIO_read(bio, &filepub[0], sizeof(filepub)); + BIO_free(bio); + if (filepub_size < 0) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_FILE_READ); + return 0; + } + + if ((bio = BIO_new_file(filenametpm, "r")) == NULL) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_FILE_READ); + return 0; + } + filepriv_size = BIO_read(bio, &filepriv[0], sizeof(filepriv)); + BIO_free(bio); + if (filepriv_size < 0) { + ERR(tpm2tss_tpm2data_read, TPM2TSS_R_FILE_READ); + return 0; + } + + tpm2data = OPENSSL_malloc(sizeof(TPM2_DATA)); + if (!tpm2data) + return 0; + + memset(tpm2data, 0, sizeof(*tpm2data)); + tpm2data->privatetype = KEY_TYPE_BLOB; + tpm2data->parent = parent; + tpm2data->emptyAuth = emptyAuth; + + r = Tss2_MU_TPM2B_PUBLIC_Unmarshal(&filepub[0], filepub_size, NULL, + &tpm2data->pub); + ERRchktss(tpm2tss_tpm2data_read, r, goto error); + + r = Tss2_MU_TPM2B_PRIVATE_Unmarshal(&filepriv[0], filepriv_size, NULL, + &tpm2data->priv); + ERRchktss(tpm2tss_tpm2data_read, r, goto error); + + *tpm2Datap = tpm2data; + return 1; + + error: + OPENSSL_free(tpm2data); + return 0; +} diff --git a/src/tpm2-tss-engine-common.h b/src/tpm2-tss-engine-common.h new file mode 100755 index 0000000..66dbf94 --- /dev/null +++ b/src/tpm2-tss-engine-common.h @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * Copyright (c) 2019, Wind River Systems. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +#ifndef TPM2_TSS_ENGINE_COMMON_H +#define TPM2_TSS_ENGINE_COMMON_H + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \ + && !defined(__STDC_NO_ATOMICS__) +# include +# define TPM2_TSS_ENGINE_HAVE_C11_ATOMICS +typedef _Atomic int T2TE_ATOMIC_INT; +#else +typedef int T2TE_ATOMIC_INT; +#endif + +#include +#include +#include + +#include "tpm2-tss-engine-err.h" + +#include +#include +#include + +extern TPM2B_DIGEST ownerauth; +extern TPM2B_DIGEST parentauth; + +extern char *tcti_nameconf; + +int init_ecc(ENGINE *e); +int init_rand(ENGINE *e); +int init_rsa(ENGINE *e); + +TSS2_RC esys_ctx_init (ESYS_CONTEXT **esys_ctx); + +TSS2_RC esys_ctx_free (ESYS_CONTEXT **esys_ctx); + +TSS2_RC init_tpm_parent ( ESYS_CONTEXT **esys_ctx, + TPM2_HANDLE parentHandle, + ESYS_TR *parent); + +TSS2_RC init_tpm_key ( ESYS_CONTEXT **esys_ctx, + ESYS_TR *keyHandle, + TPM2_DATA *tpm2Data); + +#define ENGINE_HASH_ALG TPM2_ALG_SHA256 + +#define TPM2B_PUBLIC_PRIMARY_RSA_TEMPLATE { \ + .publicArea = { \ + .type = TPM2_ALG_RSA, \ + .nameAlg = ENGINE_HASH_ALG, \ + .objectAttributes = (TPMA_OBJECT_USERWITHAUTH | \ + TPMA_OBJECT_RESTRICTED | \ + TPMA_OBJECT_DECRYPT | \ + TPMA_OBJECT_NODA | \ + TPMA_OBJECT_FIXEDTPM | \ + TPMA_OBJECT_FIXEDPARENT | \ + TPMA_OBJECT_SENSITIVEDATAORIGIN), \ + .authPolicy = { \ + .size = 0, \ + }, \ + .parameters.rsaDetail = { \ + .symmetric = { \ + .algorithm = TPM2_ALG_AES, \ + .keyBits.aes = 128, \ + .mode.aes = TPM2_ALG_CFB, \ + }, \ + .scheme = { \ + .scheme = TPM2_ALG_NULL, \ + .details = {} \ + }, \ + .keyBits = 2048, \ + .exponent = 0,\ + }, \ + .unique.rsa = { \ + .size = 0, \ + } \ + } \ +} + +/* + * The parameters of this key can never be changed because they are + * part of the interoperable 'standard' form for TSS2 PEM keys. + * Where the parent key is ephemeral and generated on demand, it + * has to be generated precisely the *same* every time or it cannot + * work. The ECC primary is used for *all* keys regardless of their + * type. + */ +#define TPM2B_PUBLIC_PRIMARY_ECC_TEMPLATE { \ + .publicArea = { \ + .type = TPM2_ALG_ECC, \ + .nameAlg = ENGINE_HASH_ALG, \ + .objectAttributes = (TPMA_OBJECT_USERWITHAUTH | \ + TPMA_OBJECT_RESTRICTED | \ + TPMA_OBJECT_DECRYPT | \ + TPMA_OBJECT_NODA | \ + TPMA_OBJECT_FIXEDTPM | \ + TPMA_OBJECT_FIXEDPARENT | \ + TPMA_OBJECT_SENSITIVEDATAORIGIN), \ + .authPolicy = { \ + .size = 0, \ + }, \ + .parameters.eccDetail = { \ + .symmetric = { \ + .algorithm = TPM2_ALG_AES, \ + .keyBits.aes = 128, \ + .mode.aes = TPM2_ALG_CFB, \ + }, \ + .scheme = { \ + .scheme = TPM2_ALG_NULL, \ + .details = {} \ + }, \ + .curveID = TPM2_ECC_NIST_P256, \ + .kdf = { \ + .scheme = TPM2_ALG_NULL, \ + .details = {} \ + }, \ + }, \ + .unique.ecc = { \ + .x.size = 0, \ + .y.size = 0 \ + } \ + } \ +} + +typedef struct { + ASN1_OBJECT *type; + ASN1_BOOLEAN emptyAuth; + ASN1_INTEGER *parent; + ASN1_OCTET_STRING *pubkey; + ASN1_OCTET_STRING *privkey; +} TSSPRIVKEY; + + +DECLARE_ASN1_FUNCTIONS(TSSPRIVKEY); + +DECLARE_PEM_write_bio(TSSPRIVKEY, TSSPRIVKEY); +DECLARE_PEM_read_bio(TSSPRIVKEY, TSSPRIVKEY); + +#define OID_loadableKey "2.23.133.10.1.3" + +typedef struct { + T2TE_ATOMIC_INT refcount; + ESYS_CONTEXT *esys_ctx; + ESYS_TR key_handle; + int privatetype; +} TPM2_SIG_KEY_CTX; + +typedef struct { + TPM2_SIG_KEY_CTX *key; + TPM2_ALG_ID hash_alg; + ESYS_TR seq_handle; + size_t sig_size; +} TPM2_SIG_DATA; + +int +digest_update(EVP_MD_CTX *ctx, const void *data, size_t count); +int +digest_finish(TPM2_SIG_DATA *data, TPM2B_DIGEST **digest, + TPMT_TK_HASHCHECK **validation); +int +digest_sign_init(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx, TPM2_DATA *tpm2data, + size_t sig_size); +int +digest_sign_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src); +void +digest_sign_cleanup(EVP_PKEY_CTX *ctx); + +#endif /* TPM2_TSS_ENGINE_COMMON_H */ diff --git a/src/tpm2-tss-engine-digest-sign.c b/src/tpm2-tss-engine-digest-sign.c new file mode 100644 index 0000000..7b02bad --- /dev/null +++ b/src/tpm2-tss-engine-digest-sign.c @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright 2021, Graphiant, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include + +#include + +#include "tpm2-tss-engine-common.h" + +#ifndef TPM2_TSS_ENGINE_HAVE_C11_ATOMICS +/* fall back to using GCC/clang atomic builtins */ +# define atomic_fetch_add(PTR, VAL) \ + __atomic_fetch_add((PTR), (VAL), __ATOMIC_SEQ_CST) +#define atomic_fetch_sub(PTR, VAL) \ + __atomic_fetch_sub ((PTR), (VAL), __ATOMIC_SEQ_CST) +#endif /* TPM2_TSS_ENGINE_HAVE_C11_ATOMICS */ + +/** + * Initialise a digest operation for digest and sign. + * + * @param ctx OpenSSL message digest context + * @param data Digest and sign data + * @retval 1 on success + * @retval 0 on failure + */ +static int +digest_init(EVP_MD_CTX *ctx, TPM2_SIG_DATA *data) +{ + TPM2B_AUTH null_auth = { .size = 0 }; + const EVP_MD *md; + TSS2_RC r; + + md = EVP_MD_CTX_md(ctx); + if (!md) { + ERR(digest_init, TPM2TSS_R_GENERAL_FAILURE); + return 0; + } + + switch (EVP_MD_type(md)) { + case NID_sha1: + data->hash_alg = TPM2_ALG_SHA1; + break; + case NID_sha256: + data->hash_alg = TPM2_ALG_SHA256; + break; + case NID_sha384: + data->hash_alg = TPM2_ALG_SHA384; + break; + case NID_sha512: + data->hash_alg = TPM2_ALG_SHA512; + break; + default: + ERR(digest_init, TPM2TSS_R_UNKNOWN_ALG); + return 0; + } + + r = Esys_HashSequenceStart(data->key->esys_ctx, ESYS_TR_NONE, + ESYS_TR_NONE, ESYS_TR_NONE, &null_auth, + data->hash_alg, &data->seq_handle); + ERRchktss(digest_init, r, return 0); + + return 1; +} + +/** + * Update a digest with more data + * + * @param ctx OpenSSL message digest context + * @param data Data to add to digest + * @param count Length of data to add + * @retval 1 on success + * @retval 0 on failure + */ +int +digest_update(EVP_MD_CTX *ctx, const void *data, size_t count) +{ + EVP_PKEY_CTX *pctx = EVP_MD_CTX_pkey_ctx(ctx); + TPM2_SIG_DATA *sig_data = EVP_PKEY_CTX_get_app_data(pctx); + const uint8_t *current_data = data; + TSS2_RC r; + + DBG("digest_update %p %p\n", pctx, ctx); + + while (count > 0) { + TPM2B_MAX_BUFFER digest_data = { .size = count }; + if (digest_data.size > sizeof(digest_data.buffer)) + digest_data.size = sizeof(digest_data.buffer); + memcpy(&digest_data.buffer[0], current_data, digest_data.size); + current_data += digest_data.size; + count -= digest_data.size; + + r = Esys_SequenceUpdate(sig_data->key->esys_ctx, sig_data->seq_handle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &digest_data); + ERRchktss(digest_update, r, return 0); + } + + return 1; +} + +/** + * Finish a digest operation for digest and sign + * + * @param data Digest and sign data + * @param digest Digest calculated by TPM + * @param validation Validation ticket for the digest calculated by TPM + * @retval 1 on success + * @retval 0 on failure + */ +int +digest_finish(TPM2_SIG_DATA *data, TPM2B_DIGEST **digest, + TPMT_TK_HASHCHECK **validation) +{ + TSS2_RC r; + + r = Esys_SequenceComplete(data->key->esys_ctx, data->seq_handle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, NULL, ESYS_TR_RH_OWNER, + digest, validation); + ERRchktss(digest_finish, r, return 0); + + /* Esys_SequenceComplete consumes the handle */ + data->seq_handle = ESYS_TR_NONE; + + return 1; +} + +/** + * Initialise a digest and sign operation + * + * @param ctx OpenSSL pkey context + * @param mctx OpenSSL message digest context + * @param tpm2data TPM data for the key to use + * @param sig_size Size of the signature data + * @retval 1 on success + * @retval 0 on failure + */ +int +digest_sign_init(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx, TPM2_DATA *tpm2data, + size_t sig_size) +{ + TSS2_RC r; + + if (!tpm2data) + /* non-TPM key - nothing to do */ + return 1; + + TPM2_SIG_DATA *data = OPENSSL_malloc(sizeof(*data)); + if (!data) { + ERR(digest_sign_init, ERR_R_MALLOC_FAILURE); + return 0; + } + + data->seq_handle = ESYS_TR_NONE; + data->sig_size = sig_size; + + data->key = OPENSSL_malloc(sizeof(*data->key)); + if (!data->key) { + ERR(digest_sign_init, ERR_R_MALLOC_FAILURE); + goto error; + } + + data->key->refcount = 1; + + r = init_tpm_key(&data->key->esys_ctx, &data->key->key_handle, tpm2data); + ERRchktss(digest_sign_init, r, goto error); + data->key->privatetype = tpm2data->privatetype; + + EVP_PKEY_CTX_set_app_data(ctx, data); + /* + * Override the update function so that the TPM performs the + * digest, which is required for restricted keys - the TPM will + * reject a null validation ticket in this case for the signing + * operation. + */ + EVP_MD_CTX_set_update_fn(mctx, digest_update); + + if (!digest_init(mctx, data)) + goto error; + + return 1; + + error: + if (data->key) { + if (data->key->key_handle != ESYS_TR_NONE) { + if (data->key->privatetype == KEY_TYPE_HANDLE) { + Esys_TR_Close(data->key->esys_ctx, &data->key->key_handle); + } else { + Esys_FlushContext(data->key->esys_ctx, data->key->key_handle); + } + } + if (data->key->esys_ctx) + esys_ctx_free(&data->key->esys_ctx); + OPENSSL_free(data->key); + } + OPENSSL_free(data); + return 0; +} + +/** + * Copy digest and sign context + * + * @param dst Destination OpenSSL pkey context + * @param src Source OpenSSL pkey context + * @retval 1 on success + * @retval 0 on failure + */ +int +digest_sign_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) +{ + TPM2_SIG_DATA *src_sig_data = EVP_PKEY_CTX_get_app_data(src); + TPMS_CONTEXT *context = NULL; + TPM2_SIG_DATA *dst_sig_data = NULL; + TSS2_RC r; + + if (src_sig_data) { + dst_sig_data = OPENSSL_malloc(sizeof(*dst_sig_data)); + if (!dst_sig_data) { + ERR(digest_sign_copy, ERR_R_MALLOC_FAILURE); + return 0; + } + + dst_sig_data->hash_alg = src_sig_data->hash_alg; + dst_sig_data->sig_size = src_sig_data->sig_size; + + if (src_sig_data->seq_handle != ESYS_TR_NONE) { + /* duplicate sequence handle */ + + r = Esys_ContextSave(src_sig_data->key->esys_ctx, + src_sig_data->seq_handle, &context); + ERRchktss(digest_sign_copy, r, goto error); + dst_sig_data->seq_handle = ESYS_TR_NONE; + r = Esys_ContextLoad(src_sig_data->key->esys_ctx, context, + &dst_sig_data->seq_handle); + ERRchktss(digest_sign_copy, r, goto error); + } + + dst_sig_data->key = src_sig_data->key; + atomic_fetch_add(&dst_sig_data->key->refcount, 1); + + EVP_PKEY_CTX_set_app_data(dst, dst_sig_data); + } + + Esys_Free(context); + return 1; + + error: + Esys_Free(context); + OPENSSL_free(dst_sig_data); + return 0; +} + +/** + * Clean up digest and sign context + * + * @param ctx OpenSSL pkey context + * @retval 1 on success + * @retval 0 on failure + */ +void +digest_sign_cleanup(EVP_PKEY_CTX *ctx) +{ + TPM2_SIG_DATA *sig_data = EVP_PKEY_CTX_get_app_data(ctx); + + if (sig_data) { + if (sig_data->seq_handle != ESYS_TR_NONE) + Esys_FlushContext(sig_data->key->esys_ctx, sig_data->seq_handle); + + if (atomic_fetch_sub(&sig_data->key->refcount, 1) == 1) { + if (sig_data->key->key_handle != ESYS_TR_NONE) { + if (sig_data->key->privatetype == KEY_TYPE_HANDLE) { + Esys_TR_Close(sig_data->key->esys_ctx, + &sig_data->key->key_handle); + } else { + Esys_FlushContext(sig_data->key->esys_ctx, + sig_data->key->key_handle); + } + } + esys_ctx_free(&sig_data->key->esys_ctx); + OPENSSL_free(sig_data->key); + } + OPENSSL_free(sig_data); + EVP_PKEY_CTX_set_app_data(ctx, NULL); + } +} diff --git a/src/tpm2-tss-engine-ecc.c b/src/tpm2-tss-engine-ecc.c new file mode 100644 index 0000000..9e72c85 --- /dev/null +++ b/src/tpm2-tss-engine-ecc.c @@ -0,0 +1,875 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +static int ec_key_app_data = -1; + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +const ECDSA_METHOD *ecc_method_default = NULL; +ECDSA_METHOD *ecc_methods = NULL; +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +const EC_KEY_METHOD *ecc_method_default = NULL; +EC_KEY_METHOD *ecc_methods = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +#ifdef HAVE_OPENSSL_DIGEST_SIGN +static int (*ecdsa_pkey_orig_copy)(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src); +static void (*ecdsa_pkey_orig_cleanup)(EVP_PKEY_CTX *ctx); +#endif /* HAVE_OPENSSL_DIGEST_SIGN */ + +static TPM2B_DATA allOutsideInfo = { + .size = 0, +}; + +static TPML_PCR_SELECTION allCreationPCR = { + .count = 0, +}; + +static TPM2B_PUBLIC keyEcTemplate = { + .publicArea = { + .type = TPM2_ALG_ECC, + .nameAlg = ENGINE_HASH_ALG, + .objectAttributes = (TPMA_OBJECT_USERWITHAUTH | + TPMA_OBJECT_SIGN_ENCRYPT | + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT | + TPMA_OBJECT_SENSITIVEDATAORIGIN | + TPMA_OBJECT_NODA), + .parameters.eccDetail = { + .curveID = 0, /* To be filled out later */ + .symmetric = { + .algorithm = TPM2_ALG_NULL, + .keyBits.aes = 0, + .mode.aes = 0, + }, + .scheme = { + .scheme = TPM2_ALG_NULL, + .details = {} + }, + .kdf = { + .scheme = TPM2_ALG_NULL, + .details = {} + }, + }, + .unique.ecc = { + .x.size = 0, + .y.size = 0 + } + } +}; + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +static int EC_GROUP_order_bits(const EC_GROUP *group) +{ + if (!group) + return 0; + + BIGNUM *order = BN_new(); + + if (order == NULL) { + ERR_clear_error(); + return 0; + } + + int ret = 0; + + if (!EC_GROUP_get_order(group, order, NULL)) { + ERR_clear_error(); + BN_free(order); + return 0; + } + + ret = BN_num_bits(order); + BN_free(order); + return ret; +} +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + /** + * Initialize a TPM2B_ECC_POINT from an OpenSSL EC_POINT. + * + * @param point Pointer to output tpm point + * @param pub_key OpenSSL public key to convert + * @param group Curve group + * @retval 0 on failure + */ +static int +init_tpm_public_point(TPM2B_ECC_POINT *point, const EC_POINT *ec_point, + const EC_GROUP *ec_group) +{ + unsigned char buffer[1 + sizeof(point->point.x.buffer) + + sizeof(point->point.y.buffer)] = {0}; + + BN_CTX *ctx = BN_CTX_new(); + if (!ctx) + return 0; + + BN_CTX_start(ctx); + + size_t len = 0; + + // first, check for actual buffer size required + if ((len = EC_POINT_point2oct(ec_group, ec_point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, ctx)) <= sizeof(buffer)) { + len = EC_POINT_point2oct(ec_group, ec_point, + POINT_CONVERSION_UNCOMPRESSED, buffer, sizeof(buffer), ctx); + } + + BN_CTX_end(ctx); + BN_CTX_free(ctx); + + if (len == 0 || len > sizeof(buffer)) + return 0; + + len = (len - 1) / 2; + + point->point.x.size = len; + point->point.y.size = len; + memcpy(point->point.x.buffer, &buffer[1], len); + memcpy(point->point.y.buffer, &buffer[1 + len], len); + + return 1; +} + +/** + * Generate a shared secret using a TPM key + * + * @param psec Pointer to output buffer holding shared secret + * @param pseclen Size of the psec buffer + * @param pub_key The peer's public key + * @param ecdh The ECC key object for the host private key + * @retval 0 on failure + */ +static int +ecdh_compute_key(unsigned char **psec, size_t *pseclen, + const EC_POINT *pub_key, const EC_KEY *eckey) +{ + /* + * If this is not a TPM2 key, bail out since fall through to software + * functions requires a non-const EC_KEY, yet the ECDH prototype only + * provides it as const. + */ + TPM2_DATA *tpm2Data = tpm2tss_ecc_getappdata(eckey); + if (tpm2Data == NULL) + return 0; + + TPM2B_ECC_POINT inPoint; + TPM2B_ECC_POINT *outPoint = NULL; + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + int ret = init_tpm_public_point(&inPoint, pub_key, group); + if (!ret) + return 0; + + ESYS_CONTEXT *esys_ctx = NULL; + ESYS_TR keyHandle = ESYS_TR_NONE; + TSS2_RC r = init_tpm_key(&esys_ctx, &keyHandle, tpm2Data); + ERRchktss(ecdh_compute_key, r, goto error); + + r = Esys_ECDH_ZGen(esys_ctx, keyHandle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &inPoint, &outPoint); + ERRchktss(ecdh_compute_key, r, goto error); + + *pseclen = outPoint->point.x.size; + *psec = OPENSSL_malloc(*pseclen); + if (!*psec) + goto error; + + memcpy(*psec, outPoint->point.x.buffer, *pseclen); + ret = 1; + goto out; +error: + ret = 0; +out: + if (keyHandle != ESYS_TR_NONE) { + if (tpm2Data->privatetype == KEY_TYPE_HANDLE) { + Esys_TR_Close(esys_ctx, &keyHandle); + } else { + Esys_FlushContext(esys_ctx, keyHandle); + } + } + Esys_Free(outPoint); + esys_ctx_free(&esys_ctx); + return ret; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +static ECDSA_SIG * +ecdsa_sign(ESYS_CONTEXT *esys_ctx, ESYS_TR key_handle, + TPM2B_DIGEST *digest, TPMT_TK_HASHCHECK *validation, + TPM2_ALG_ID hash_alg) +{ + TPMT_SIG_SCHEME inScheme = { + .scheme = TPM2_ALG_ECDSA, + .details.ecdsa.hashAlg = hash_alg, + }; + BIGNUM *bns = NULL, *bnr = NULL; + ECDSA_SIG *ret = NULL; + TPMT_SIGNATURE *sig = NULL; + TSS2_RC r; + + r = Esys_Sign(esys_ctx, key_handle, ESYS_TR_PASSWORD, + ESYS_TR_NONE, ESYS_TR_NONE, digest, &inScheme, + validation, &sig); + ERRchktss(ecdsa_sign, r, goto error); + + ret = ECDSA_SIG_new(); + if (ret == NULL) { + ERR(ecdsa_sign, ERR_R_MALLOC_FAILURE); + goto error; + } + + bns = BN_bin2bn(&sig->signature.ecdsa.signatureS.buffer[0], + sig->signature.ecdsa.signatureS.size, NULL); + bnr = BN_bin2bn(&sig->signature.ecdsa.signatureR.buffer[0], + sig->signature.ecdsa.signatureR.size, NULL); + if (!bns || !bnr) { + ERR(ecdsa_sign, ERR_R_MALLOC_FAILURE); + goto error; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + ret->s = bns; + ret->r = bnr; +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + ECDSA_SIG_set0(ret, bnr, bns); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + + goto out; + + error: + if (bns) + BN_free(bns); + if (bnr) + BN_free(bnr); + if (ret) + ECDSA_SIG_free(ret); + ret = NULL; + out: + Esys_Free(sig); + return ret; +} + +/** Sign data using a TPM key + * + * This function performs the sign function using the private key in ECDSA. + * This operation is usually used to perform signature and authentication + * operations. + * @param dgst The data to be signed. + * @param dgst_len Length of the from buffer. + * @param inv Ignored + * @param rp Ignored + * @param eckey The ECC key object. + * @retval 0 on failure + * @retval size Size of the returned signature + */ +static ECDSA_SIG * +ecdsa_ec_key_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, + const BIGNUM *rp, EC_KEY *eckey) +{ + ECDSA_SIG *ret = NULL; + TPM2_DATA *tpm2Data = tpm2tss_ecc_getappdata(eckey); + TPM2_ALG_ID hash_alg; + + /* If this is not a TPM2 key, fall through to software functions */ + if (tpm2Data == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x10100000 + ECDSA_set_method(eckey, ecc_method_default); + ret = ECDSA_do_sign_ex(dgst, dgst_len, inv, rp, eckey); + ECDSA_set_method(eckey, ecc_methods); + return ret; +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + EC_KEY_set_method(eckey, ecc_method_default); + ret = ECDSA_do_sign_ex(dgst, dgst_len, inv, rp, eckey); + EC_KEY_set_method(eckey, ecc_methods); + return ret; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + } + + DBG("ecdsa_sign called for input data(size=%i):\n", dgst_len); + DBGBUF(dgst, dgst_len); + + TSS2_RC r; + ESYS_CONTEXT *esys_ctx = NULL; + ESYS_TR keyHandle = ESYS_TR_NONE; + + TPMT_TK_HASHCHECK validation = { .tag = TPM2_ST_HASHCHECK, + .hierarchy = TPM2_RH_NULL, + .digest.size = 0 }; + + /* + * ECDSA signatures truncate the incoming hash to fit the curve, + * and the signature mechanism is the same regardless of the + * hash being used. + * + * The TPM bizarrely wants to be told the hash algorithm, and + * either it or the TSS will validate that the digest length + * matches the hash that it's told, despite it having no business + * caring about such things. + * + * So, we can truncate the digest any pretend it's any smaller + * digest that the TPM actually does support, as long as that + * digest is larger than the size of the curve. + */ + int curve_len = (EC_GROUP_order_bits(EC_KEY_get0_group(eckey)) + 7) / 8; + /* If we couldn't work it out, don't truncate */ + if (!curve_len) + curve_len = dgst_len; + + if (dgst_len == SHA_DIGEST_LENGTH || + (curve_len <= SHA_DIGEST_LENGTH && dgst_len > SHA_DIGEST_LENGTH)) { + hash_alg = TPM2_ALG_SHA1; + dgst_len = SHA_DIGEST_LENGTH; + } else if (dgst_len == SHA256_DIGEST_LENGTH || + (curve_len <= SHA256_DIGEST_LENGTH && dgst_len > SHA256_DIGEST_LENGTH)) { + hash_alg = TPM2_ALG_SHA256; + dgst_len = SHA256_DIGEST_LENGTH; + } else if (dgst_len == SHA384_DIGEST_LENGTH || + (curve_len <= SHA384_DIGEST_LENGTH && dgst_len > SHA384_DIGEST_LENGTH)) { + hash_alg = TPM2_ALG_SHA384; + dgst_len = SHA384_DIGEST_LENGTH; + } else if (dgst_len == SHA512_DIGEST_LENGTH || + (curve_len <= SHA512_DIGEST_LENGTH && dgst_len > SHA512_DIGEST_LENGTH)) { + hash_alg = TPM2_ALG_SHA512; + dgst_len = SHA512_DIGEST_LENGTH; + } else { + ERR(ecdsa_sign, TPM2TSS_R_PADDING_UNKNOWN); + goto error; + } + + TPM2B_DIGEST digest = { .size = dgst_len }; + if (digest.size > sizeof(digest.buffer)) { + ERR(ecdsa_sign, TPM2TSS_R_DIGEST_TOO_LARGE); + goto error; + } + memcpy(&digest.buffer[0], dgst, digest.size); + + r = init_tpm_key(&esys_ctx, &keyHandle, tpm2Data); + ERRchktss(ecdsa_sign, r, goto error); + + ret = ecdsa_sign(esys_ctx, keyHandle, &digest, &validation, hash_alg); + + goto out; + error: + r = -1; + out: + if (keyHandle != ESYS_TR_NONE) { + if (tpm2Data->privatetype == KEY_TYPE_HANDLE) { + Esys_TR_Close(esys_ctx, &keyHandle); + } else { + Esys_FlushContext(esys_ctx, keyHandle); + } + } + + esys_ctx_free(&esys_ctx); + return (r == TSS2_RC_SUCCESS) ? ret : NULL; +} + +#ifdef HAVE_OPENSSL_DIGEST_SIGN +static int +ecdsa_pkey_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) +{ + if (ecdsa_pkey_orig_copy && !ecdsa_pkey_orig_copy(dst, src)) + return 0; + + return digest_sign_copy(dst, src); +} + +static void +ecdsa_pkey_cleanup(EVP_PKEY_CTX *ctx) +{ + digest_sign_cleanup(ctx); + + if (ecdsa_pkey_orig_cleanup) + ecdsa_pkey_orig_cleanup(ctx); +} + +/* called for digest & sign init, after message digest algorithm set */ +static int +ecdsa_digest_custom(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx) +{ + EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx); + EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey); + TPM2_DATA *tpm2data = tpm2tss_ecc_getappdata(eckey); + + DBG("ecdsa_digest_custom %p %p\n", ctx, mctx); + + return digest_sign_init(ctx, mctx, tpm2data, ECDSA_size(eckey)); +} + +static int +ecdsa_signctx(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, + EVP_MD_CTX *mctx) +{ + TPM2_SIG_DATA *sig_data = EVP_PKEY_CTX_get_app_data(ctx); + TSS2_RC r = TSS2_RC_SUCCESS; + TPMT_TK_HASHCHECK *validation_ptr = NULL; + TPM2B_DIGEST *digest_ptr = NULL; + ECDSA_SIG *ecdsa_s = NULL; + + DBG("ecdsa_signctx %p %p sig_data %p\n", ctx, mctx, sig_data); + + if (!sig) { + /* caller just wants to know the size */ + *siglen = sig_data->sig_size; + return 1; + } + + if (!sig_data) { + /* handle non-TPM key */ + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int md_len = 0; + + if (!EVP_DigestFinal_ex(mctx, md, &md_len)) + return 0; + if (EVP_PKEY_sign(ctx, sig, siglen, md, md_len) <= 0) + return 0; + return 1; + } + + if (!digest_finish(sig_data, &digest_ptr, &validation_ptr)) + return 0; + + ecdsa_s = ecdsa_sign(sig_data->key->esys_ctx, sig_data->key->key_handle, + digest_ptr, validation_ptr, + sig_data->hash_alg); + if (!ecdsa_s) + goto error; + + *siglen = i2d_ECDSA_SIG(ecdsa_s, &sig); + + r = 1; + goto out; + + error: + r = 0; + out: + ECDSA_SIG_free(ecdsa_s); + Esys_Free(digest_ptr); + Esys_Free(validation_ptr); + + return r; +} +#endif /* HAVE_OPENSSL_DIGEST_SIGN */ + +/** Helper to populate the ECC key object. + * + * In order to use an ECC key object in a typical manner, all fields of the + * OpenSSL's corresponding object bust be filled. This function fills the public + * values correctly. + * @param key The key object to fill. + * @retval 0 on failure + * @retval 1 on success + */ +static int +populate_ecc(EC_KEY *key) +{ + EC_GROUP *ecgroup = NULL; + int nid; + BIGNUM *x = NULL, *y = NULL; + TPM2_DATA *tpm2Data = tpm2tss_ecc_getappdata(key); + if (tpm2Data == NULL) + return 0; + + switch (tpm2Data->pub.publicArea.parameters.eccDetail.curveID) { + case TPM2_ECC_NIST_P256: + nid = EC_curve_nist2nid("P-256"); + break; + case TPM2_ECC_NIST_P384: + nid = EC_curve_nist2nid("P-384"); + break; + default: + nid = -1; + } + if (nid < 0) { + ERR(populate_ecc, TPM2TSS_R_UNKNOWN_CURVE); + return 0; + } + ecgroup = EC_GROUP_new_by_curve_name(nid); + if (ecgroup == NULL) { + ERR(populate_ecc, TPM2TSS_R_UNKNOWN_CURVE); + return 0; + } + if (!EC_KEY_set_group(key, ecgroup)) { + ERR(populate_ecc, TPM2TSS_R_GENERAL_FAILURE); + EC_GROUP_free(ecgroup); + return 0; + } + EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); + EC_GROUP_free(ecgroup); + + x = BN_bin2bn(tpm2Data->pub.publicArea.unique.ecc.x.buffer, + tpm2Data->pub.publicArea.unique.ecc.x.size, NULL); + + y = BN_bin2bn(tpm2Data->pub.publicArea.unique.ecc.y.buffer, + tpm2Data->pub.publicArea.unique.ecc.y.size, NULL); + + if (!x || !y) { + ERR(populate_ecc, ERR_R_MALLOC_FAILURE); + return 0; + } + + if (!EC_KEY_set_public_key_affine_coordinates(key, x, y)) { + ERR(populate_ecc, TPM2TSS_R_GENERAL_FAILURE); + BN_free(y); + BN_free(x); + return 0; + } + + BN_free(y); + BN_free(x); + + return 1; +} + +/** Helper to load an ECC key from a tpm2Data + * + * This function creates a key object given a TPM2_DATA object. The resulting + * key object can then be used for signing with the tpm2tss engine. Ownership + * of the TPM2_DATA object is taken on success. + * @param tpm2Data The key data to use. Must have been allocated using + * OPENSSL_malloc. + * @retval key The key object + * @retval NULL on failure. + */ +EVP_PKEY * +tpm2tss_ecc_makekey(TPM2_DATA *tpm2Data) +{ + DBG("Creating ECC key object.\n"); + + EVP_PKEY *pkey; + EC_KEY *eckey; + + /* create the new objects to return */ + if ((pkey = EVP_PKEY_new()) == NULL) { + ERR(tpm2tss_ecc_makekey, ERR_R_MALLOC_FAILURE); + return NULL; + } + + if ((eckey = EC_KEY_new()) == NULL) { + ERR(tpm2tss_ecc_makekey, ERR_R_MALLOC_FAILURE); + EVP_PKEY_free(pkey); + + return NULL; + } +#if OPENSSL_VERSION_NUMBER < 0x10100000 + if (!ECDSA_set_method(eckey, ecc_methods)) { +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + if (!EC_KEY_set_method(eckey, ecc_methods)) { +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + ERR(tpm2tss_ecc_makekey, TPM2TSS_R_GENERAL_FAILURE); + EC_KEY_free(eckey); + goto error; + } + + if (!EVP_PKEY_assign_EC_KEY(pkey, eckey)) { + ERR(tpm2tss_ecc_makekey, TPM2TSS_R_GENERAL_FAILURE); + EC_KEY_free(eckey); + goto error; + } + + if (!tpm2tss_ecc_setappdata(eckey, tpm2Data)) { + ERR(tpm2tss_ecc_makekey, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + if (!populate_ecc(eckey)) + goto error; + + DBG("Created ECC key object.\n"); + + return pkey; + error: + EVP_PKEY_free(pkey); + return NULL; +} + +/** Retrieve app data + * + * Since the ECC api (opposed to the RSA api) does not provide a standardized + * way to retrieve app data between the library and an application, this helper + * is defined + * @param key The key object + * @retval tpm2Data The corresponding TPM data + * @retval NULL on failure. + */ +TPM2_DATA * +#if OPENSSL_VERSION_NUMBER < 0x10100000 +tpm2tss_ecc_getappdata(EC_KEY *key) +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +tpm2tss_ecc_getappdata(const EC_KEY *key) +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +{ + if (ec_key_app_data == -1) { + DBG("Module uninitialized\n"); + return NULL; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + return ECDSA_get_ex_data(key, ec_key_app_data); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + return EC_KEY_get_ex_data(key, ec_key_app_data); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +} + +/** Set app data + * + * Since the ECC api (opposed to the RSA api) does not provide a standardized + * way to set app data between the library and an application, this helper + * is defined + * @param key The key object + * @param tpm2Data The corresponding TPM data + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_ecc_setappdata(EC_KEY *key, TPM2_DATA *tpm2Data) +{ + if (ec_key_app_data == -1) { + DBG("Module uninitialized\n"); + return 0; + } +#if OPENSSL_VERSION_NUMBER < 0x10100000 + return ECDSA_set_ex_data(key, ec_key_app_data, tpm2Data); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + return EC_KEY_set_ex_data(key, ec_key_app_data, tpm2Data); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +} + +static void +free_ecc_appdata(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, + long argl, void *argp) +{ + TPM2_DATA *tpm2Data = ptr; + + (void)parent; + (void)ad; + (void)idx; + (void)argl; + (void)argp; + + if (!ptr) + return; + + OPENSSL_free(tpm2Data); +} + +/** Generate a tpm2tss ecc key object. + * + * This function creates a new TPM ECC key. The TPM data is stored inside the + * object*s app data and can be retrieved using tpm2tss_ecc_getappdata(). + * @param key The key object for the TPM ECC key to be created + * @param curve The curve to be used for the key + * @param password The Password to be set for the new key + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_ecc_genkey(EC_KEY *key, TPMI_ECC_CURVE curve, const char *password, + TPM2_HANDLE parentHandle) +{ + DBG("GenKey for ecdsa.\n"); + + TSS2_RC r; + ESYS_CONTEXT *esys_ctx = NULL; + ESYS_TR parent = ESYS_TR_NONE; + TPM2B_PUBLIC *keyPublic = NULL; + TPM2B_PRIVATE *keyPrivate = NULL; + TPM2_DATA *tpm2Data = NULL; + TPM2B_PUBLIC inPublic = keyEcTemplate; + TPM2B_SENSITIVE_CREATE inSensitive = { + .sensitive = { + .userAuth = { + .size = 0, + }, + .data = { + .size = 0, + } + } + }; + + tpm2Data = OPENSSL_malloc(sizeof(*tpm2Data)); + if (tpm2Data == NULL) { + ERR(tpm2tss_ecc_genkey, ERR_R_MALLOC_FAILURE); + goto error; + } + memset(tpm2Data, 0, sizeof(*tpm2Data)); + + inPublic.publicArea.parameters.eccDetail.curveID = curve; + + if (password) { + DBG("Setting a password for the created key.\n"); + if (strlen(password) > sizeof(tpm2Data->userauth.buffer) - 1 || strlen(password) > sizeof(inSensitive.sensitive.userAuth.buffer) - 1) { + goto error; + } + tpm2Data->userauth.size = strlen(password); + memcpy(&tpm2Data->userauth.buffer[0], password, + tpm2Data->userauth.size); + + inSensitive.sensitive.userAuth.size = strlen(password); + memcpy(&inSensitive.sensitive.userAuth.buffer[0], password, + strlen(password)); + } else + tpm2Data->emptyAuth = 1; + + r = init_tpm_parent(&esys_ctx, parentHandle, &parent); + ERRchktss(tpm2tss_ecc_genkey, r, goto error); + + tpm2Data->parent = parentHandle; + + DBG("Generating the ECC key inside the TPM.\n"); + + r = Esys_Create(esys_ctx, parent, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &inSensitive, &inPublic, &allOutsideInfo, &allCreationPCR, + &keyPrivate, &keyPublic, NULL, NULL, NULL); + ERRchktss(tpm2tss_ecc_genkey, r, goto error); + + DBG("Generated the ECC key inside the TPM.\n"); + + tpm2Data->pub = *keyPublic; + tpm2Data->priv = *keyPrivate; + + if (!tpm2tss_ecc_setappdata(key, tpm2Data)) { + ERR(tpm2tss_ecc_genkey, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + if (!populate_ecc(key)) { + goto error; + } + + goto end; + error: + r = -1; + tpm2tss_ecc_setappdata(key, NULL); + if (tpm2Data) + OPENSSL_free(tpm2Data); + + end: + Esys_Free(keyPrivate); + Esys_Free(keyPublic); + + if (parent != ESYS_TR_NONE && !parentHandle) + Esys_FlushContext(esys_ctx, parent); + + esys_ctx_free(&esys_ctx); + + return (r == TSS2_RC_SUCCESS); +} + +/** Initialize the tpm2tss engine's ecc submodule + * + * Initialize the tpm2tss engine's submodule by setting function pointer. + * @param e The engine context. + * @retval 1 on success + * @retval 0 on failure + */ +int +init_ecc(ENGINE *e) +{ + (void)(e); + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + ecc_method_default = ECDSA_OpenSSL(); + if (ecc_method_default == NULL) + return 0; + + ecc_methods = ECDSA_METHOD_new(ecc_method_default); + if (ecc_methods == NULL) + return 0; + + ECDSA_METHOD_set_sign(ecc_methods, ecdsa_ec_key_sign); + + if (ec_key_app_data == -1) + ec_key_app_data = ECDSA_get_ex_new_index(0, NULL, NULL, NULL, + free_ecc_appdata); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + ecc_method_default = EC_KEY_OpenSSL(); + if (ecc_method_default == NULL) + return 0; + + ecc_methods = EC_KEY_METHOD_new(ecc_method_default); + if (ecc_methods == NULL) + return 0; + + int (*orig_sign) (int, const unsigned char *, int, unsigned char *, + unsigned int *, const BIGNUM *, const BIGNUM *, EC_KEY *) + = NULL; + EC_KEY_METHOD_get_sign(ecc_methods, &orig_sign, NULL, NULL); + EC_KEY_METHOD_set_sign(ecc_methods, orig_sign, NULL, ecdsa_ec_key_sign); + EC_KEY_METHOD_set_compute_key(ecc_methods, ecdh_compute_key); + + if (ec_key_app_data == -1) + ec_key_app_data = EC_KEY_get_ex_new_index(0, NULL, NULL, NULL, + free_ecc_appdata); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +#if HAVE_OPENSSL_DIGEST_SIGN + /* digest and sign support */ + + EVP_PKEY_METHOD *pkey_ecc_methods; + + pkey_ecc_methods = EVP_PKEY_meth_new(EVP_PKEY_EC, 0); + if (pkey_ecc_methods == NULL) + return 0; + + const EVP_PKEY_METHOD *pkey_orig_ecc_methods = + EVP_PKEY_meth_find(EVP_PKEY_EC); + if (pkey_orig_ecc_methods == NULL) + return 0; + EVP_PKEY_meth_copy(pkey_ecc_methods, pkey_orig_ecc_methods); + /* + * save originals since we only override some of the pkey + * functionality, rather than reimplementing all of it + */ + EVP_PKEY_meth_get_copy(pkey_ecc_methods, &ecdsa_pkey_orig_copy); + EVP_PKEY_meth_get_cleanup(pkey_ecc_methods, &ecdsa_pkey_orig_cleanup); + + EVP_PKEY_meth_set_copy(pkey_ecc_methods, ecdsa_pkey_copy); + EVP_PKEY_meth_set_cleanup(pkey_ecc_methods, ecdsa_pkey_cleanup); + EVP_PKEY_meth_set_signctx(pkey_ecc_methods, NULL, ecdsa_signctx); + EVP_PKEY_meth_set_digest_custom(pkey_ecc_methods, ecdsa_digest_custom); + EVP_PKEY_meth_add0(pkey_ecc_methods); +#endif /* HAVE_OPENSSL_DIGEST_SIGN */ + + return 1; +} diff --git a/src/tpm2-tss-engine-err.c b/src/tpm2-tss-engine-err.c new file mode 100644 index 0000000..e164e1b --- /dev/null +++ b/src/tpm2-tss-engine-err.c @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include "tpm2-tss-engine-err.h" + +#define TPM2TSS_LIB_NAME "tpm2-tss-engine" + +#define xstr(s) str(s) +#define str(s) #s + +#define ERR_F(f) { ERR_PACK(0, TPM2TSS_F_ ## f, 0), xstr(f) } +#define ERR_R(r, s) { ERR_PACK(0, 0, r), xstr(s) } + +#ifndef OPENSSL_NO_ERR +static ERR_STRING_DATA TPM2TSS_f[] = { + /* tpm2-tss-engine.c */ + ERR_F(loadkey), + ERR_F(init_engine), + ERR_F(get_auth), + ERR_F(engine_ctrl), + /* tpm2-tss-engine-common.c */ + ERR_F(tpm2tss_tpm2data_write), + ERR_F(tpm2tss_tpm2data_read), + ERR_F(tpm2tss_tpm2data_readtpm), + ERR_F(init_tpm_parent), + ERR_F(init_tpm_key), + ERR_F(esys_ctx_init), + ERR_F(esys_ctx_free), + /* tpm2-tss-engine-ecc.c */ + ERR_F(ecdsa_sign), + ERR_F(populate_ecc), + ERR_F(tpm2tss_ecc_genkey), + ERR_F(tpm2tss_ecc_makekey), + /* tpm2-tss-engine-rand.c */ + ERR_F(rand_bytes), + ERR_F(rand_seed), + /* tpm2-tss-engine-rsa.c */ + ERR_F(rsa_priv_enc), + ERR_F(rsa_priv_dec), + ERR_F(tpm2tss_rsa_genkey), + ERR_F(populate_rsa), + {0, NULL} +}; + +static ERR_STRING_DATA TPM2TSS_r[] = { + ERR_R(TPM2TSS_R_TPM2DATA_READ_FAILED, Failed to read TPM2 data), + ERR_R(TPM2TSS_R_UNKNOWN_ALG, The algorithm is unknown (neither RSA, ECDSA)), + ERR_R(TPM2TSS_R_CANNOT_MAKE_KEY, Cannot create OpenSSL key object), + ERR_R(TPM2TSS_R_SUBINIT_FAILED, Could not initialize submodule), + ERR_R(TPM2TSS_R_FILE_WRITE, Could not create file for writing), + ERR_R(TPM2TSS_R_DATA_CORRUPTED, Data is corrupted and could not be parsed), + ERR_R(TPM2TSS_R_FILE_READ, Could not open file for reading), + ERR_R(TPM2TSS_R_PADDING_UNKNOWN, Unknown padding scheme requested), + ERR_R(TPM2TSS_R_PADDING_FAILED, Padding operation failed), + ERR_R(TPM2TSS_R_UNKNOWN_TPM_ERROR, Unknown TPM error occurred. Please check tpm2tss logs), + ERR_R(TPM2TSS_R_DIGEST_TOO_LARGE, The provided digest value is too large), + ERR_R(TPM2TSS_R_GENERAL_FAILURE, Some unknown error occurred), + ERR_R(TPM2TSS_R_UNKNOWN_CURVE, Unknown ECC curve), + ERR_R(TPM2TSS_R_UI_ERROR, User interaction), + ERR_R(TPM2TSS_R_UNKNOWN_CTRL, Unknown engine ctrl), + ERR_R(TPM2TSS_R_DL_OPEN_FAILED, Failed to open TCTI library), + ERR_R(TPM2TSS_R_DL_INVALID, The TCTI library is invalid), + /* TPM/TSS Reasons that are useful to the user */ + ERR_R(TPM2TSS_R_AUTH_FAILURE, Authorization failed), + ERR_R(TPM2TSS_R_OWNER_AUTH_FAILED, Owner authorization failed), + ERR_R(TPM2TSS_R_OLD_TSS, An old TSS (<2.2) was detected and a TPM session may have leaked), + {0, NULL} +}; +#endif /* OPENSSL_NO_ERR */ + +static int TPM2TSS_lib_error_code = 0; +static int TPM2TSS_error_init = 0; + +static ERR_STRING_DATA TPM2TSS_lib_name[] = { + {0, TPM2TSS_LIB_NAME}, + {0, NULL} +}; + +/** Load TPM2TSS error string + * + * Load the errorstring from TPM2TSS_f and TPM2TSS_r into OpenSSL's error + * handling stack. + */ +void +ERR_load_TPM2TSS_strings(void) +{ + if (TPM2TSS_lib_error_code == 0) + TPM2TSS_lib_error_code = ERR_get_next_error_library(); + + if (!TPM2TSS_error_init) { + TPM2TSS_error_init = 1; +#ifndef OPENSSL_NO_ERR + ERR_load_strings(TPM2TSS_lib_error_code, TPM2TSS_f); + ERR_load_strings(TPM2TSS_lib_error_code, TPM2TSS_r); +#endif /* OPENSSL_NO_ERR */ + + TPM2TSS_lib_name->error = ERR_PACK(TPM2TSS_lib_error_code, 0, 0); + ERR_load_strings(0, TPM2TSS_lib_name); + } +} + +/** Unload TPM2TSS error string + * + * Unload the errorstring from TPM2TSS_f and TPM2TSS_r into OpenSSL's error + * handling stack. + */ +void +ERR_unload_TPM2TSS_strings(void) +{ + if (TPM2TSS_error_init) { +#ifndef OPENSSL_NO_ERR + ERR_unload_strings(TPM2TSS_lib_error_code, TPM2TSS_f); + ERR_unload_strings(TPM2TSS_lib_error_code, TPM2TSS_r); +#endif /* OPENSSL_NO_ERR */ + + ERR_unload_strings(0, TPM2TSS_lib_name); + TPM2TSS_error_init = 0; + } +} + +/** Add error to error stack + * + * Add the error to the error stack of OpenSSL. + * This function is usually not called directly but using the macros ERR(f,r) + * or ERRchktss(f, r, s) from source code. + * @param function Identifier of the function invocing the error. + * @param reason Identifier of the reason for the error. + * @param file File from which the error originates. + * @param line Line inside the file from which the error originates. + */ +void +ERR_error(int function, int reason, const char *file, int line) +{ + (void)(function); + (void)(file); + (void)(line); + if (TPM2TSS_lib_error_code == 0) + TPM2TSS_lib_error_code = ERR_get_next_error_library(); + ERR_PUT_error(TPM2TSS_lib_error_code, function, reason, file, line); +} + +/** Print a buffer to stderr + * + * A helper function to print data buffers to stderr. This function is usually + * not called directly, but the macro DBGBUF() is used instead. + * @param b The buffer + * @param s The buffer's size + */ +void +printbuf(const uint8_t *b, size_t s) +{ + if (s > 1000) + return; + for (size_t i = 0; i < s; i++) + fprintf(stderr, "%02x", b[i]); + fprintf(stderr, "\n"); +} diff --git a/src/tpm2-tss-engine-err.h b/src/tpm2-tss-engine-err.h new file mode 100644 index 0000000..742c4c5 --- /dev/null +++ b/src/tpm2-tss-engine-err.h @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +#ifndef TPM2_TSS_ENGINE_ERR_H +#define TPM2_TSS_ENGINE_ERR_H + +#include "config.h" + +#include + +#ifndef NDEBUG +#define DBG(...) fprintf(stderr, __VA_ARGS__) +#define DBGBUF(...) printbuf(__VA_ARGS__) +void printbuf(const uint8_t *b, size_t s); + +#else /* DEBUG */ +#define DBG(...) +#define DBGBUF(...) +#endif /* DEBUG */ + +#define ERR(f,r) ERR_error(TPM2TSS_F_ ## f, r, __FILE__, __LINE__) + +/* This macro checks for common TPM error codes which are meaningful to the + user */ +#define ERRchktss(f, r, s) do { \ + if (r) { \ + switch(r) { \ + case TSS2_ESYS_RC_MEMORY: \ + ERR(f, ERR_R_MALLOC_FAILURE); \ + break; \ + case 0x000009a2: \ + ERR(f, TPM2TSS_R_AUTH_FAILURE); \ + break; \ + default: \ + ERR(f, TPM2TSS_R_UNKNOWN_TPM_ERROR); \ + } \ + s; \ + } \ +} while (0); + +void ERR_load_TPM2TSS_strings(void); +void ERR_unload_TPM2TSS_strings(void); +void ERR_error(int function, int reason, const char *file, int line); + +/* Function codes */ +/* tpm2-tss-engine.c */ +#define TPM2TSS_F_loadkey 100 +#define TPM2TSS_F_init_engine 101 +#define TPM2TSS_F_get_auth 102 +#define TPM2TSS_F_engine_ctrl 103 +/* tpm2-tss-engine-common.c */ +#define TPM2TSS_F_tpm2tss_tpm2data_write 110 +#define TPM2TSS_F_tpm2tss_tpm2data_read 111 +#define TPM2TSS_F_tpm2tss_tpm2data_readtpm 112 +#define TPM2TSS_F_init_tpm_parent 113 +#define TPM2TSS_F_init_tpm_key 114 +#define TPM2TSS_F_esys_ctx_init 115 +#define TPM2TSS_F_esys_ctx_free 116 +/* tpm2-tss-engine-ecc.c */ +#define TPM2TSS_F_ecdsa_sign 120 +#define TPM2TSS_F_populate_ecc 121 +#define TPM2TSS_F_tpm2tss_ecc_genkey 122 +#define TPM2TSS_F_tpm2tss_ecc_makekey 123 +#define TPM2TSS_F_ecdh_compute_key 124 + +/* tpm2-tss-engine-digest-sign.c */ +#define TPM2TSS_F_digest_init 150 +#define TPM2TSS_F_digest_update 151 +#define TPM2TSS_F_digest_finish 152 +#define TPM2TSS_F_digest_sign_init 153 +#define TPM2TSS_F_digest_sign_copy 154 + +/* tpm2-tss-engine-rand.c */ +#define TPM2TSS_F_rand_bytes 130 +#define TPM2TSS_F_rand_seed 131 +/* tpm2-tss-engine-rsa.c */ +#define TPM2TSS_F_rsa_priv_enc 140 +#define TPM2TSS_F_rsa_priv_dec 141 +#define TPM2TSS_F_tpm2tss_rsa_genkey 142 +#define TPM2TSS_F_populate_rsa 143 +#define TPM2TSS_F_rsa_signctx 144 + +/* Reason codes */ +#define TPM2TSS_R_TPM2DATA_READ_FAILED 100 +#define TPM2TSS_R_UNKNOWN_ALG 101 +#define TPM2TSS_R_CANNOT_MAKE_KEY 102 +#define TPM2TSS_R_SUBINIT_FAILED 103 +#define TPM2TSS_R_FILE_WRITE 104 +#define TPM2TSS_R_DATA_CORRUPTED 105 +#define TPM2TSS_R_FILE_READ 106 +#define TPM2TSS_R_PADDING_UNKNOWN 107 +#define TPM2TSS_R_PADDING_FAILED 108 +#define TPM2TSS_R_UNKNOWN_TPM_ERROR 109 +#define TPM2TSS_R_DIGEST_TOO_LARGE 110 +#define TPM2TSS_R_GENERAL_FAILURE 111 +#define TPM2TSS_R_UNKNOWN_CURVE 112 +#define TPM2TSS_R_UI_ERROR 113 +#define TPM2TSS_R_UNKNOWN_CTRL 114 +#define TPM2TSS_R_DL_OPEN_FAILED 115 +#define TPM2TSS_R_DL_INVALID 116 +/* TPM/TSS Reasons that are useful to the user */ +#define TPM2TSS_R_AUTH_FAILURE 150 +#define TPM2TSS_R_OWNER_AUTH_FAILED 151 +#define TPM2TSS_R_OLD_TSS 152 + +#endif /* TPM2_TSS_ENGINE_ERR_H */ diff --git a/src/tpm2-tss-engine-rand.c b/src/tpm2-tss-engine-rand.c new file mode 100644 index 0000000..ef88b79 --- /dev/null +++ b/src/tpm2-tss-engine-rand.c @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include +#include + +#include +#include + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +/** rand seed + * @retval 1 on success + * @retval 0 on failure + */ +static int +rand_seed(const void *seed, int seed_len) +{ + ESYS_CONTEXT *esys_ctx = NULL; + TSS2_RC r; + + r = esys_ctx_init(&esys_ctx); + ERRchktss(rand_seed, r, goto end); + + TPM2B_SENSITIVE_DATA stir; + size_t offset = 0; + char *cur_data = (char*)seed; + + static const size_t tpm_random_stir_max_size = 128; + while(offset < (size_t)seed_len) { + size_t left = seed_len - offset; + size_t chunk = left > tpm_random_stir_max_size ? tpm_random_stir_max_size : left; + + stir.size = chunk; + memcpy(stir.buffer, cur_data + offset, chunk); + + r = Esys_StirRandom( + esys_ctx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &stir); + ERRchktss(rand_seed, r, goto end); + + offset += chunk; + } + +end: + if(esys_ctx) + esys_ctx_free(&esys_ctx); + return (r == TSS2_RC_SUCCESS)? 1 : 0; +} + +/** Genereate random values + * + * Use the TPM to generate a number of random values. + * @param buf The buffer to write the random values to + * @param num The amound of random bytes to generate + * @retval 1 on success + * @retval 0 on failure + */ +static int +rand_bytes(unsigned char *buf, int num) +{ + ESYS_CONTEXT *esys_ctx = NULL; + TSS2_RC r; + + r = esys_ctx_init(&esys_ctx); + ERRchktss(rand_bytes, r, goto end); + + TPM2B_DIGEST *b; + while (num > 0) { + r = Esys_GetRandom(esys_ctx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + num, &b); + ERRchktss(rand_bytes, r, goto end); + + memcpy(buf, &b->buffer, b->size); + num -= b->size; + buf += b->size; + Esys_Free(b); + } + + esys_ctx_free(&esys_ctx); + + end: + return (r == TSS2_RC_SUCCESS); +} + +/** Return the entropy status of the prng + * + * Since we provide real (TPM-based) randomness even for the pseudorand + * function, our status is allways good. + * @retval 1 allways good status + */ +static int +rand_status() +{ + return 1; +} + +static RAND_METHOD rand_methods = { + rand_seed, + rand_bytes, + NULL, /* cleanup() */ + NULL, /* add() */ + rand_bytes, /* pseudorand() */ + rand_status /* status() */ +}; + +/** Initialize the tpm2tss engine's rand submodule + * + * Initialize the tpm2tss engine's submodule by setting function pointer. + * @param e The engine context. + * @retval 1 on success + * @retval 0 on failure + */ +int +init_rand(ENGINE *e) +{ + return ENGINE_set_RAND(e, &rand_methods); +} diff --git a/src/tpm2-tss-engine-rsa.c b/src/tpm2-tss-engine-rsa.c new file mode 100644 index 0000000..41de34e --- /dev/null +++ b/src/tpm2-tss-engine-rsa.c @@ -0,0 +1,813 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include +#include + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +#define chkerr_goto(x) if (x) { DBG("%s:%i:%s: Error 0x%04x\n", __FILE__, \ + __LINE__, __func__, x); goto error; } + +const RSA_METHOD *default_rsa = NULL; + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +RSA_METHOD rsa_methods; +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ +RSA_METHOD *rsa_methods = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +#ifdef HAVE_OPENSSL_DIGEST_SIGN +static int (*rsa_pkey_orig_copy)(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src); +static void (*rsa_pkey_orig_cleanup)(EVP_PKEY_CTX *ctx); +#endif /* HAVE_OPENSSL_DIGEST_SIGN */ + +static int (*rsa_orig_finish)(RSA *rsa); + +static TPM2B_DATA allOutsideInfo = { + .size = 0, +}; + +static TPML_PCR_SELECTION allCreationPCR = { + .count = 0, +}; + +static TPM2B_PUBLIC keyTemplate = { + .publicArea = { + .type = TPM2_ALG_RSA, + .nameAlg = ENGINE_HASH_ALG, + .objectAttributes = (TPMA_OBJECT_USERWITHAUTH | + TPMA_OBJECT_SIGN_ENCRYPT | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT | + TPMA_OBJECT_SENSITIVEDATAORIGIN | + TPMA_OBJECT_NODA), + .authPolicy.size = 0, + .parameters.rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_NULL, + .keyBits.aes = 0, + .mode.aes = 0, + }, + .scheme = { + .scheme = TPM2_ALG_NULL, + .details = {} + }, + .keyBits = 0, /* to be set by the genkey function */ + .exponent = 0, /* to be set by the genkey function */ + }, + .unique.rsa.size = 0 + } +}; + +/** Sign data using a TPM key + * + * This function performs the encrypt function using the private key in RSA. + * This operation is usually used to perform signature and authentication + * operations. + * @param flen Length of the from buffer. + * @param from The data to be signed. + * @param to The buffer to write the signature to. + * @param rsa The rsa key object. + * @param padding The padding scheme to be used. + * @retval 0 on failure + * @retval size Size of the returned signature + */ +static int +rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + TPM2_DATA *tpm2Data = RSA_get_app_data(rsa); + + /* If this is not a TPM2 key, fall through to software functions */ + if (tpm2Data == NULL) { + DBG("Non-TPM key passed. Calling standard function.\n"); +#if OPENSSL_VERSION_NUMBER < 0x10100000 + return default_rsa->rsa_priv_enc(flen, from, to, rsa, padding); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + return RSA_meth_get_priv_enc(default_rsa)(flen, from, to, rsa, padding); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + } + + DBG("rsa_priv_enc called for scheme %i and input data(size=%i):\n", + padding, flen); + DBGBUF(from, flen); + + int ret = 0; + TSS2_RC r = TSS2_RC_SUCCESS; + ESYS_CONTEXT *esys_ctx = NULL; + ESYS_TR keyHandle = ESYS_TR_NONE; + TPM2B_DATA label = { .size = 0 }; + TPM2B_PUBLIC_KEY_RSA *sig = NULL; + TPMT_RSA_DECRYPT inScheme = { .scheme = TPM2_ALG_NULL }; + + TPM2B_PUBLIC_KEY_RSA digest; + digest.size = RSA_size(rsa); + if (digest.size > sizeof(digest.buffer)) { + ERR(rsa_priv_enc, TPM2TSS_R_DIGEST_TOO_LARGE); + goto error; + } + + switch (padding) { + case RSA_PKCS1_PADDING: + ret = RSA_padding_add_PKCS1_type_1(&digest.buffer[0], digest.size, + from, flen); + break; + case RSA_X931_PADDING: + ret = RSA_padding_add_X931(&digest.buffer[0], digest.size, from, flen); + break; + case RSA_NO_PADDING: + ret = RSA_padding_add_none(&digest.buffer[0], digest.size, from, flen); + break; + default: + ERR(rsa_priv_enc, TPM2TSS_R_PADDING_UNKNOWN); + goto error; + } + if (ret <= 0) { + ERR(rsa_priv_enc, TPM2TSS_R_PADDING_FAILED); + goto error; + } + + DBG("Padded digest data (size=%i):\n", digest.size); + DBGBUF(&digest.buffer[0], digest.size); + + r = init_tpm_key(&esys_ctx, &keyHandle, tpm2Data); + ERRchktss(rsa_priv_enc, r, goto error); + + DBG("Signing (via decrypt operation).\n"); + r = Esys_RSA_Decrypt(esys_ctx, keyHandle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &digest, &inScheme, &label, &sig); + ERRchktss(rsa_priv_enc, r, goto error); + + DBG("Signature done (size=%i):\n", sig->size); + DBGBUF(&sig->buffer[0], sig->size); + + ret = sig->size; + if (ret > RSA_size(rsa) || ret <= 0) { + ERR(rsa_priv_enc, TPM2TSS_R_DIGEST_TOO_LARGE); + goto error; + } + memcpy(to, &sig->buffer[0], ret); + + goto out; + + error: + r = -1; + + out: + Esys_Free(sig); + if (keyHandle != ESYS_TR_NONE) { + if (tpm2Data->privatetype == KEY_TYPE_HANDLE) { + Esys_TR_Close(esys_ctx, &keyHandle); + } else { + Esys_FlushContext(esys_ctx, keyHandle); + } + } + esys_ctx_free(&esys_ctx); + return (r == TSS2_RC_SUCCESS) ? ret : 0; +} + +/** Decrypt data using a TPM key + * + * This function performs the decrypt function using the private key in RSA. + * @param flen Length of the from buffer. + * @param from The data to be decrypted. + * @param to The buffer to write the plaintext to. + * @param rsa The rsa key object. + * @param padding The padding scheme to be used. + * @retval 0 on failure + * @retval size Size of the returned plaintext + */ +static int +rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA * rsa, + int padding) +{ + TPM2_DATA *tpm2Data = RSA_get_app_data(rsa); + + /* If this is not a TPM2 key, fall through to software functions */ + if (tpm2Data == NULL) +#if OPENSSL_VERSION_NUMBER < 0x10100000 + return default_rsa->rsa_priv_dec(flen, from, to, rsa, padding); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + return RSA_meth_get_priv_dec(default_rsa)(flen, from, to, rsa, padding); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + + DBG("rsa_priv_dec called for scheme %i and input data(size=%i):\n", + padding, flen); + DBGBUF(from, flen); + + TSS2_RC r; + ESYS_CONTEXT *esys_ctx = NULL; + ESYS_TR keyHandle = ESYS_TR_NONE; + TPM2B_DATA label = { .size = 0 }; + TPM2B_PUBLIC_KEY_RSA *message = NULL; + TPMT_RSA_DECRYPT inScheme; + + TPM2B_PUBLIC_KEY_RSA cipher = { .size = flen }; + if (flen > (int)sizeof(cipher.buffer) || flen < 0) { + ERR(rsa_priv_dec, TPM2TSS_R_DIGEST_TOO_LARGE); + goto error; + } + memcpy(&cipher.buffer[0], from, flen); + + switch (padding) { + case RSA_PKCS1_PADDING: + inScheme.scheme = TPM2_ALG_RSAES; + break; + case RSA_PKCS1_OAEP_PADDING: + inScheme.scheme = TPM2_ALG_OAEP; + inScheme.details.oaep.hashAlg = TPM2_ALG_SHA1; + break; + case RSA_NO_PADDING: + inScheme.scheme = TPM2_ALG_NULL; + break; + default: + ERR(rsa_priv_dec, TPM2TSS_R_PADDING_UNKNOWN); + goto error; + } + + r = init_tpm_key(&esys_ctx, &keyHandle, tpm2Data); + ERRchktss(rsa_priv_dec, r, goto out); + + r = Esys_RSA_Decrypt(esys_ctx, keyHandle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &cipher, &inScheme, &label, &message); + ERRchktss(rsa_priv_dec, r, goto out); + + DBG("Decrypted message (size=%i):\n", message->size); + DBGBUF(&message->buffer[0], message->size); + + flen = message->size; + if (flen > RSA_size(rsa) || flen <= 0) { + ERR(rsa_priv_dec, TPM2TSS_R_DIGEST_TOO_LARGE); + goto error; + } + memcpy(to, &message->buffer[0], flen); + + goto out; + + error: + r = -1; + + out: + Esys_Free(message); + if (keyHandle != ESYS_TR_NONE) { + if (tpm2Data->privatetype == KEY_TYPE_HANDLE) { + Esys_TR_Close(esys_ctx, &keyHandle); + } else { + Esys_FlushContext(esys_ctx, keyHandle); + } + } + + esys_ctx_free(&esys_ctx); + return (r == TSS2_RC_SUCCESS) ? flen : 0; +} + +/** Clean up the RSA key + * + * @param rsa The rsa key object. + * @retval 1 on success, or 0 on failure + */ +static int +rsa_finish(RSA *rsa) +{ + TPM2_DATA *tpm2Data = RSA_get_app_data(rsa); + + if (tpm2Data != NULL) { + OPENSSL_free(tpm2Data); + RSA_set_app_data(rsa, NULL); + } + if (rsa_orig_finish) { + rsa_orig_finish(rsa); + } + return 1; +} + +/** Helper to populate the RSA key object. + * + * In order to use an RSA key object in a typical manner, all fields of the + * OpenSSL's corresponding object bust be filled. This function fills the public + * values correctly and fill the private values with 0. + * @param rsa The key object to fill. + * @retval 0 on failure + * @retval 1 on success + */ +static int +populate_rsa(RSA *rsa) +{ + TPM2_DATA *tpm2Data = RSA_get_app_data(rsa); + UINT32 exponent; + + if (tpm2Data == NULL) + goto error; + + exponent = tpm2Data->pub.publicArea.parameters.rsaDetail.exponent; + if (!exponent) + exponent = 0x10001; + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + /* Setting the public portion of the key */ + rsa->n = BN_bin2bn(tpm2Data->pub.publicArea.unique.rsa.buffer, + tpm2Data->pub.publicArea.unique.rsa.size, rsa->n); + if (rsa->n == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + if (rsa->e == NULL) + rsa->e = BN_new(); + if (rsa->e == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->e, exponent); + + /* Setting private portions to 0 values so the public key can be extracted + from the keyfile if this is desired. */ + if (rsa->d == NULL) + rsa->d = BN_new(); + if (rsa->d == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->d, 0); + if (rsa->p == NULL) + rsa->p = BN_new(); + if (rsa->p == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->p, 0); + if (rsa->q == NULL) + rsa->q = BN_new(); + if (rsa->q == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->q, 0); + if (rsa->dmp1 == NULL) + rsa->dmp1 = BN_new(); + if (rsa->dmp1 == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->dmp1, 0); + if (rsa->dmq1 == NULL) + rsa->dmq1 = BN_new(); + if (rsa->dmq1 == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->dmq1, 0); + if (rsa->iqmp == NULL) + rsa->iqmp = BN_new(); + if (rsa->iqmp == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + BN_set_word(rsa->iqmp, 0); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + BIGNUM *n = BN_bin2bn(tpm2Data->pub.publicArea.unique.rsa.buffer, + tpm2Data->pub.publicArea.unique.rsa.size, NULL); + BIGNUM *e = BN_new(); + BIGNUM *d = BN_new(); + BIGNUM *p = BN_new(); + BIGNUM *q = BN_new(); + BIGNUM *dmp1 = BN_new(); + BIGNUM *dmq1 = BN_new(); + BIGNUM *iqmp = BN_new(); + + if (!n || !e || !d || !p || !q || !dmp1 || !dmq1 || !iqmp) { + if (n) + BN_free(n); + if (e) + BN_free(e); + if (d) + BN_free(d); + if (p) + BN_free(p); + if (q) + BN_free(q); + if (dmp1) + BN_free(dmp1); + if (dmq1) + BN_free(dmq1); + if (iqmp) + BN_free(iqmp); + + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + goto error; + } + + BN_set_word(e, exponent); + BN_set_word(d, 0); + BN_set_word(p, 0); + BN_set_word(q, 0); + BN_set_word(dmp1, 0); + BN_set_word(dmq1, 0); + BN_set_word(iqmp, 0); + + RSA_set0_key(rsa, n, e, d); + RSA_set0_factors(rsa, p, q); + RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + + return 1; + error: + return 0; +} + +/** Helper to load an RSA key from a tpm2Data + * + * This function creates a key object given a TPM2_DATA object. The resulting + * key object can then be used for signing and decrypting with the tpm2tss + * engine. Ownership of the TPM2_DATA object is taken on success. + * @param tpm2Data The key data to use. Must have been allocated using + * OPENSSL_malloc. + * @retval key The key object + * @retval NULL on failure. + */ +EVP_PKEY * +tpm2tss_rsa_makekey(TPM2_DATA *tpm2Data) +{ + EVP_PKEY *pkey; + RSA *rsa; + + DBG("Creating RSA key object.\n"); + + /* create the new objects to return */ + if ((pkey = EVP_PKEY_new()) == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + return NULL; + } + + if ((rsa = RSA_new()) == NULL) { + ERR(populate_rsa, ERR_R_MALLOC_FAILURE); + EVP_PKEY_free(pkey); + return NULL; + } +#if OPENSSL_VERSION_NUMBER < 0x10100000 + rsa->meth = &rsa_methods; +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + RSA_set_method(rsa, rsa_methods); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + + if (!EVP_PKEY_assign_RSA(pkey, rsa)) { + ERR(populate_rsa, TPM2TSS_R_GENERAL_FAILURE); + RSA_free(rsa); + goto error; + } + + if (!RSA_set_app_data(rsa, tpm2Data)) { + ERR(populate_rsa, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + if (!populate_rsa(rsa)) { + RSA_set_app_data(rsa, NULL); + goto error; + } + + DBG("Created RSA key object.\n"); + + return pkey; + error: + EVP_PKEY_free(pkey); + return NULL; +} + +/** Generate a tpm2tss rsa key object. + * + * This function creates a new TPM RSA key. The TPM data is stored inside the + * object*s app data and can be retrieved using RSA_get_app_data(). + * @param rsa The key object for the TPM RSA key to be created. + * @param bits The key size + * @param e The key's exponent + * @param password The Password to be set for the new key + * @retval 1 on success + * @retval 0 on failure + */ +int +tpm2tss_rsa_genkey(RSA *rsa, int bits, BIGNUM *e, char *password, + TPM2_HANDLE parentHandle) +{ + DBG("Generating RSA key for %i bits keysize.\n", bits); + + TSS2_RC r = TSS2_RC_SUCCESS; + ESYS_CONTEXT *esys_ctx = NULL; + ESYS_TR parent = ESYS_TR_NONE; + TPM2B_PUBLIC *keyPublic = NULL; + TPM2B_PRIVATE *keyPrivate = NULL; + TPM2_DATA *tpm2Data = NULL; + TPM2B_PUBLIC inPublic = keyTemplate; + TPM2B_SENSITIVE_CREATE inSensitive = { + .sensitive = { + .userAuth = { + .size = 0, + }, + .data = { + .size = 0, + } + } + }; + + tpm2Data = OPENSSL_malloc(sizeof(*tpm2Data)); + if (tpm2Data == NULL) { + ERR(tpm2tss_rsa_genkey, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + memset(tpm2Data, 0, sizeof(*tpm2Data)); + + inPublic.publicArea.parameters.rsaDetail.keyBits = bits; + if (e) + inPublic.publicArea.parameters.rsaDetail.exponent = BN_get_word(e); + + if (password) { + DBG("Setting a password for the created key.\n"); + if (strlen(password) > sizeof(tpm2Data->userauth.buffer) - 1) { + goto error; + } + tpm2Data->userauth.size = strlen(password); + memcpy(&tpm2Data->userauth.buffer[0], password, + tpm2Data->userauth.size); + + inSensitive.sensitive.userAuth.size = strlen(password); + memcpy(&inSensitive.sensitive.userAuth.buffer[0], password, + strlen(password)); + } else + tpm2Data->emptyAuth = 1; + + r = init_tpm_parent(&esys_ctx, parentHandle, &parent); + ERRchktss(tpm2tss_rsa_genkey, r, goto error); + + tpm2Data->parent = parentHandle; + + DBG("Generating the RSA key inside the TPM.\n"); + + r = Esys_Create(esys_ctx, parent, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &inSensitive, &inPublic, &allOutsideInfo, &allCreationPCR, + &keyPrivate, &keyPublic, NULL, NULL, NULL); + ERRchktss(tpm2tss_rsa_genkey, r, goto error); + + DBG("Generated the RSA key inside the TPM.\n"); + + tpm2Data->pub = *keyPublic; + tpm2Data->priv = *keyPrivate; + + if (!RSA_set_app_data(rsa, tpm2Data)) { + ERR(tpm2tss_rsa_genkey, TPM2TSS_R_GENERAL_FAILURE); + goto error; + } + + if (!populate_rsa(rsa)) { + goto error; + } + + goto end; + error: + r = -1; + if (rsa) + RSA_set_app_data(rsa, NULL); + if (tpm2Data) + OPENSSL_free(tpm2Data); + + end: + Esys_Free(keyPrivate); + Esys_Free(keyPublic); + + if (parent != ESYS_TR_NONE && !parentHandle) + Esys_FlushContext(esys_ctx, parent); + + esys_ctx_free(&esys_ctx); + + return (r == TSS2_RC_SUCCESS); +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000 +RSA_METHOD rsa_methods = { + "TPM2TSS RSA methods", + NULL, /* tpm_rsa_pub_enc */ + NULL, /* tpm_rsa_pub_dec */ + rsa_priv_enc, /* act sign */ + rsa_priv_dec, /* act decrypt */ + NULL, /* rsa_mod_exp */ + NULL, /* bn_mod_exp */ + NULL, /* init */ + NULL, /* finish */ + 0, + NULL, /* app_data */ + NULL, /* sign */ + NULL, /* verify */ + NULL /* genkey */ +}; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +#ifdef HAVE_OPENSSL_DIGEST_SIGN +static int +rsa_pkey_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) +{ + if (rsa_pkey_orig_copy && !rsa_pkey_orig_copy(dst, src)) + return 0; + + return digest_sign_copy(dst, src); +} + +static void +rsa_pkey_cleanup(EVP_PKEY_CTX *ctx) +{ + digest_sign_cleanup(ctx); + + if (rsa_pkey_orig_cleanup) + rsa_pkey_orig_cleanup(ctx); +} + +/* called for digest & sign init, after message digest algorithm set */ +static int +rsa_digest_custom(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx) +{ + EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx); + RSA *rsa = EVP_PKEY_get0_RSA(pkey); + TPM2_DATA *tpm2data = RSA_get_app_data(rsa); + + DBG("rsa_digest_custom %p %p\n", ctx, mctx); + + return digest_sign_init(ctx, mctx, tpm2data, RSA_size(rsa)); +} + +static int +rsa_signctx(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, + EVP_MD_CTX *mctx) +{ + TPM2_SIG_DATA *sig_data = EVP_PKEY_CTX_get_app_data(ctx); + TSS2_RC r = TSS2_RC_SUCCESS; + TPMT_TK_HASHCHECK *validation_ptr = NULL; + TPM2B_DIGEST *digest_ptr = NULL; + TPMT_SIGNATURE *tpm_sig = NULL; + int pad_mode; + + DBG("rsa_signctx %p %p sig_data %p\n", ctx, mctx, sig_data); + + if (!sig) { + /* caller just wants to know the size */ + *siglen = sig_data->sig_size; + return 1; + } + + if (!sig_data) { + /* handle non-TPM key */ + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int md_len = 0; + + if (!EVP_DigestFinal_ex(mctx, md, &md_len)) + return 0; + if (EVP_PKEY_sign(ctx, sig, siglen, md, md_len) <= 0) + return 0; + return 1; + } + + if (EVP_PKEY_CTX_get_rsa_padding(ctx, &pad_mode) <= 0) + return 0; + + TPMT_SIG_SCHEME in_scheme = { + .scheme = TPM2_ALG_NULL, + .details.rsassa.hashAlg = sig_data->hash_alg, + }; + switch (pad_mode) { + case RSA_PKCS1_PADDING: + in_scheme.scheme = TPM2_ALG_RSASSA; + break; + case RSA_PKCS1_PSS_PADDING: + in_scheme.scheme = TPM2_ALG_RSAPSS; + break; + default: + ERR(rsa_signctx, TPM2TSS_R_PADDING_UNKNOWN); + return 0; + } + + if (!digest_finish(sig_data, &digest_ptr, &validation_ptr)) + return 0; + + r = Esys_Sign(sig_data->key->esys_ctx, sig_data->key->key_handle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + digest_ptr, &in_scheme, validation_ptr, &tpm_sig); + ERRchktss(rsa_signctx, r, goto error); + + memcpy(sig, tpm_sig->signature.rsassa.sig.buffer, sig_data->sig_size); + *siglen = sig_data->sig_size; + + r = 1; + goto out; + + error: + r = 0; + out: + Esys_Free(tpm_sig); + Esys_Free(digest_ptr); + Esys_Free(validation_ptr); + + return r; +} +#endif /* HAVE_OPENSSL_DIGEST_SIGN */ + +/** Initialize the tpm2tss engine's rsa submodule + * + * Initialize the tpm2tss engine's submodule by setting function pointer. + * @param e The engine context. + * @retval 1 on success + * @retval 0 on failure + */ +int +init_rsa(ENGINE *e) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000 + default_rsa = RSA_PKCS1_SSLeay(); + if (default_rsa == NULL) + return 0; + + rsa_methods.rsa_pub_enc = default_rsa->rsa_pub_enc; + rsa_methods.rsa_pub_dec = default_rsa->rsa_pub_dec; + rsa_methods.rsa_mod_exp = default_rsa->rsa_mod_exp; + rsa_methods.bn_mod_exp = default_rsa->bn_mod_exp; + + if (!ENGINE_set_RSA(e, &rsa_methods)) + return 0; +#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + default_rsa = RSA_PKCS1_OpenSSL(); + if (default_rsa == NULL) + return 0; + + rsa_methods = RSA_meth_dup(default_rsa); + RSA_meth_set1_name(rsa_methods, "TPM2TSS RSA methods"); + RSA_meth_set_priv_enc(rsa_methods, rsa_priv_enc); + RSA_meth_set_priv_dec(rsa_methods, rsa_priv_dec); + rsa_orig_finish = RSA_meth_get_finish(rsa_methods); + RSA_meth_set_finish(rsa_methods, rsa_finish); + + if (!ENGINE_set_RSA(e, rsa_methods)) + return 0; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */ + +#if HAVE_OPENSSL_DIGEST_SIGN + /* digest and sign support */ + + EVP_PKEY_METHOD *pkey_rsa_methods; + + pkey_rsa_methods = EVP_PKEY_meth_new(EVP_PKEY_RSA, + EVP_PKEY_FLAG_AUTOARGLEN); + if (pkey_rsa_methods == NULL) + return 0; + + const EVP_PKEY_METHOD *pkey_orig_rsa_methods = + EVP_PKEY_meth_find(EVP_PKEY_RSA); + if (pkey_orig_rsa_methods == NULL) + return 0; + EVP_PKEY_meth_copy(pkey_rsa_methods, pkey_orig_rsa_methods); + /* + * save originals since we only override some of the pkey + * functionality, rather than reimplementing all of it + */ + EVP_PKEY_meth_get_copy(pkey_rsa_methods, &rsa_pkey_orig_copy); + EVP_PKEY_meth_get_cleanup(pkey_rsa_methods, &rsa_pkey_orig_cleanup); + + EVP_PKEY_meth_set_copy(pkey_rsa_methods, rsa_pkey_copy); + EVP_PKEY_meth_set_cleanup(pkey_rsa_methods, rsa_pkey_cleanup); + EVP_PKEY_meth_set_signctx(pkey_rsa_methods, NULL, rsa_signctx); + EVP_PKEY_meth_set_digest_custom(pkey_rsa_methods, rsa_digest_custom); + EVP_PKEY_meth_add0(pkey_rsa_methods); +#endif /* HAVE_OPENSSL_DIGEST_SIGN */ + + return 1; +} diff --git a/src/tpm2-tss-engine.c b/src/tpm2-tss-engine.c new file mode 100644 index 0000000..824f538 --- /dev/null +++ b/src/tpm2-tss-engine.c @@ -0,0 +1,372 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +#include "config.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +/** + * The identifier of the engine + */ +static const char *engine_id = "tpm2tss"; + +/** + * The full name of the engine + */ +static const char *engine_name = "TPM2-TSS engine for OpenSSL"; + +TPM2B_DIGEST ownerauth = { .size = 0 }; +TPM2B_DIGEST parentauth = { .size = 0 }; + +char *tcti_nameconf = NULL; + +/** Retrieve password + * + * Helper function to retreive a password from the user. + * @param prompt_info [in] The object name to ask the user for + * @param ui_method [in] The ui method callbacks to be used + * @param cb_data [in] The callback data for the ui + * @param auth [out] The user provided password + * @retval 1 on success + * @retval 0 on failure + */ +static int +get_auth(const char *prompt_info, UI_METHOD *ui_method, void *cb_data, + TPM2B_AUTH *auth) +{ + DBG("get_auth called for object %s with ui_method %p\n", prompt_info, + ui_method); + char *ui_prompt = NULL; + UI *ui = NULL; + if (!ui_method) { + ERR(get_auth, TPM2TSS_R_UI_ERROR); + goto error; + } + ui = UI_new_method(ui_method); + if (!ui) { + ERR(get_auth, TPM2TSS_R_UI_ERROR); + goto error; + } + ui_prompt = UI_construct_prompt(ui, "password", prompt_info); + if (!ui_prompt) { + ERR(get_auth, TPM2TSS_R_UI_ERROR); + goto error; + } + if (0 > UI_add_input_string(ui, ui_prompt, UI_INPUT_FLAG_DEFAULT_PWD, + (char *)&auth->buffer[0], 0, + sizeof(auth->buffer) - 1)) { + ERR(get_auth, TPM2TSS_R_UI_ERROR); + goto error; + } + UI_add_user_data(ui, cb_data); + if (0 > UI_process(ui)) { + ERR(get_auth, TPM2TSS_R_UI_ERROR); + goto error; + } + auth->size = strlen((char *)&auth->buffer[0]); + OPENSSL_free(ui_prompt); + UI_free(ui); + + DBG("password is %s\n", (char *)&auth->buffer[0]); + + return 1; + error: + if (ui_prompt) + OPENSSL_free(ui_prompt); + if (ui) + UI_free(ui); + return 0; +} + +static const ENGINE_CMD_DEFN cmd_defns[] = { + { TPM2TSS_SET_OWNERAUTH, "SET_OWNERAUTH", + "Set the password for the owner hierarchy (default none)", + ENGINE_CMD_FLAG_STRING }, + { TPM2TSS_SET_TCTI, "SET_TCTI", + "Set the TCTI module and options (default none)", + ENGINE_CMD_FLAG_STRING }, + { TPM2TSS_SET_PARENTAUTH, "SET_PARENTAUTH", + "Set the password for the parent key (default none)", + ENGINE_CMD_FLAG_STRING }, + {0, NULL, NULL, 0} +}; + +static int +engine_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) ()) +{ + (void)(e); + (void)(i); + (void)(f); + switch (cmd) { + case TPM2TSS_SET_OWNERAUTH: + if (!p) { + DBG("Setting owner auth to empty auth.\n"); + ownerauth.size = 0; + return 1; + } + DBG("Setting owner auth to password.\n"); + if (strlen((char *)p) > sizeof(ownerauth.buffer) - 1) { + return 0; + } + ownerauth.size = strlen((char *)p); + memcpy(&ownerauth.buffer[0], p, ownerauth.size); + return 1; + case TPM2TSS_SET_TCTI: + OPENSSL_free(tcti_nameconf); + if (!p) { + DBG("Setting TCTI to the ESAPI default\n"); + } else { + tcti_nameconf = OPENSSL_strdup(p); + DBG("Setting TCTI option to \"%s\"\n", tcti_nameconf); + } + return 1; + case TPM2TSS_SET_PARENTAUTH: + if (!p) { + DBG("Setting parent auth to empty auth.\n"); + parentauth.size = 0; + return 1; + } + DBG("Setting parent auth to password.\n"); + if (strlen((char *)p) > sizeof(parentauth.buffer) - 1) { + return 0; + } + parentauth.size = strlen((char *)p); + memcpy(&parentauth.buffer[0], p, parentauth.size); + return 1; + default: + break; + } + ERR(engine_ctrl, TPM2TSS_R_UNKNOWN_CTRL); + return 0; +} + +/** Load a TPM2TSS key + * + * This function implements the prototype for loading a key from a file. + * @param e The engine for this callback (unused). + * @param key_id The name of the file with the TPM key data. + * @param ui The ui functions for querying the user. + * @param cb_data Callback data. + */ +static EVP_PKEY * +loadkey(ENGINE *e, const char *key_id, UI_METHOD *ui, void *cb_data) +{ + (void)(e); + (void)(ui); + (void)(cb_data); + + TPM2_DATA *tpm2Data = NULL; + EVP_PKEY *pkey = NULL; + + DBG("Loading private key %s\n", key_id); + if (strncmp(key_id, "0x81", 4) == 0) { + uint32_t handle; + sscanf(key_id, "0x%x", &handle); + if (!tpm2tss_tpm2data_readtpm(handle, &tpm2Data)) { + ERR(loadkey, TPM2TSS_R_TPM2DATA_READ_FAILED); + goto error; + } + } else { + if (!tpm2tss_tpm2data_read(key_id, &tpm2Data)) { + ERR(loadkey, TPM2TSS_R_TPM2DATA_READ_FAILED); + goto error; + } + } + + if (tpm2Data->emptyAuth) { + tpm2Data->userauth.size = 0; + } else { + if (!get_auth("user key", ui, cb_data, &tpm2Data->userauth)) { + goto error; + } + } + + DBG("Loaded key uses alg-id %x\n", tpm2Data->pub.publicArea.type); + + switch (tpm2Data->pub.publicArea.type) { + case TPM2_ALG_RSA: + pkey = tpm2tss_rsa_makekey(tpm2Data); + break; + case TPM2_ALG_ECC: + pkey = tpm2tss_ecc_makekey(tpm2Data); + break; + default: + ERR(loadkey, TPM2TSS_R_UNKNOWN_ALG); + goto error; + } + if (!pkey) { + ERR(loadkey, TPM2TSS_R_CANNOT_MAKE_KEY); + goto error; + } + + DBG("TPM2 Key loaded\n"); + + return pkey; +error: + if (tpm2Data) + OPENSSL_free(tpm2Data); + return NULL; +} + +/** Initialize the tpm2tss engine + * + * Initialize the tpm2tss engine by calling each of the submodules' init + * functions for setting function pointer. + * @param e The engine context. + * @retval 1 on success + * @retval 0 on failure + */ +static int +init_engine(ENGINE *e) { + static int initialized = 0; + + DBG("Initializing\n"); + + if (initialized) { + DBG("Already initialized\n"); + return 1; + } + + int rc; + +#ifdef ENABLE_TCTIENVVAR + /* Set the default TCTI option from the environment */ + OPENSSL_free(tcti_nameconf); + if (getenv("TPM2TSSENGINE_TCTI")) { + tcti_nameconf = OPENSSL_strdup(getenv("TPM2TSSENGINE_TCTI")); + } +#endif + + rc = init_rand(e); + if (rc != 1) { + ERR(init_engine, TPM2TSS_R_SUBINIT_FAILED); + return rc; + } + + rc = init_rsa(e); + if (rc != 1) { + ERR(init_engine, TPM2TSS_R_SUBINIT_FAILED); + return rc; + } + + rc = init_ecc(e); + if (rc != 1) { + ERR(init_engine, TPM2TSS_R_SUBINIT_FAILED); + return rc; + } + + initialized = 1; + return 1; +} + +/** Destroys the engine context + * + * Unloads the strings of the tpm2tss engine. + * @param e The engine context (unused). + * @retval 1 for success + */ +static int +destroy_engine(ENGINE *e) +{ + (void)(e); + OPENSSL_free(tcti_nameconf); + ERR_unload_TPM2TSS_strings(); + return 1; +} + +/** OpenSSL's method to bind an engine. + * + * This initializes the name, id and function pointers of the engine. + * @param e The TPM engine to initialize + * @param id The identifier of the engine + * @retval 0 if binding failed + * @retval 1 on success + */ +static int +bind(ENGINE *e, const char *id) +{ + (void)(id); + + if (!ENGINE_set_id(e, engine_id)) { + DBG("ENGINE_set_id failed\n"); + goto end; + } + if (!ENGINE_set_name(e, engine_name)) { + DBG("ENGINE_set_name failed\n"); + goto end; + } + + /* The init function is not allways called so we initialize crypto methods + directly from bind. */ + if (!init_engine(e)) { + DBG("tpm2tss enigne initialization failed\n"); + goto end; + } + + if (!ENGINE_set_load_privkey_function(e, loadkey)) { + DBG("ENGINE_set_load_privkey_function failed\n"); + goto end; + } + + if (!ENGINE_set_destroy_function(e, destroy_engine)) { + DBG("ENGINE_set_destroy_function failed\n"); + goto end; + } + + if (!ENGINE_set_ctrl_function(e, engine_ctrl)) { + DBG("ENGINE_set_ctrl_function failed\n"); + goto end; + } + + if (!ENGINE_set_cmd_defns(e, cmd_defns)) { + DBG("ENGINE_set_cmd_defns failed\n"); + goto end; + } + + ERR_load_TPM2TSS_strings(); + return 1; + end: + return 0; +} + +IMPLEMENT_DYNAMIC_BIND_FN(bind) +IMPLEMENT_DYNAMIC_CHECK_FN() diff --git a/src/tpm2tss-genkey.c b/src/tpm2tss-genkey.c new file mode 100644 index 0000000..a731897 --- /dev/null +++ b/src/tpm2tss-genkey.c @@ -0,0 +1,415 @@ +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +/* This tool uses a different error reporting scheme than the lib. */ +#undef ERR +#define VERB(...) if (opt.verbose) fprintf(stderr, __VA_ARGS__) +#define ERR(...) fprintf(stderr, __VA_ARGS__) + +char *help = + "Usage: [options] \n" + "Arguments:\n" + " storage for the encrypted private key\n" + "Options:\n" + " -a, --alg public key algorithm (rsa, ecdsa) (default: rsa)\n" + " -c, --curve curve for ecc (default: nist_p256)\n" + " -e, --exponent exponent for rsa (default: 65537)\n" + " -h, --help print help\n" + " -u, --public import a key and read its public portion from this file\n" + " -r, --private import the sensitive key portion from this file\n" + " -o, --ownerpw password for the owner hierarchy (default: none)\n" + " -p, --password password for the created key (default: none)\n" + " -P, --parent specific handle for the parent key (default: none)\n" + " -s, --keysize key size in bits for rsa (default: 2048)\n" + " -v, --verbose print verbose messages\n" + " -W, --parentpw password for the parent key (default: none)\n" + " -t, --tcti tcti configuration string (default: none)\n" + "\n"; + +static const char *optstr = "a:c:e:hu:r:o:p:P:s:vW:t:"; + +static const struct option long_options[] = { + {"alg", required_argument, 0, 'a'}, + {"curve", required_argument, 0, 'c'}, + {"exponent", required_argument, 0, 'e'}, + {"help", no_argument, 0, 'h'}, + {"public", required_argument, 0, 'u'}, + {"private", required_argument, 0, 'r'}, + {"ownerpw", required_argument, 0, 'o'}, + {"password", required_argument, 0, 'p'}, + {"parent", required_argument, 0, 'P'}, + {"keysize", required_argument, 0, 's'}, + {"verbose", no_argument, 0, 'v'}, + {"parentpw", required_argument, 0, 'W'}, + {"tcti", required_argument, 0, 't'}, + {0, 0, 0, 0 } +}; + +static struct opt { + char *filename; + TPMI_ALG_PUBLIC alg; + TPMI_ECC_CURVE curve; + int exponent; + char *importpub; + char *importtpm; + char *ownerpw; + char *password; + TPM2_HANDLE parent; + char *parentpw; + int keysize; + int verbose; + char *tcti_conf; +} opt; + +/** Parse and set command line options. + * + * This function parses the command line options and sets the appropriate values + * in the opt struct. + * @param argc The argument count. + * @param argv The arguments. + * @retval 0 on success + * @retval 1 on failure + */ +int +parse_opts(int argc, char **argv) +{ + /* set the default values */ + opt.filename = NULL; + opt.alg = TPM2_ALG_RSA; + opt.curve = TPM2_ECC_NIST_P256; + opt.exponent = 65537; + opt.importpub = NULL; + opt.importtpm = NULL; + opt.ownerpw = NULL; + opt.password = NULL; + opt.parent = 0; + opt.parentpw = NULL; + opt.keysize = 2048; + opt.verbose = 0; + opt.tcti_conf = NULL; + + /* parse the options */ + int c; + int opt_idx = 0; + while (-1 != (c = getopt_long(argc, argv, optstr, + long_options, &opt_idx))) { + switch(c) { + case 'h': + printf("%s", help); + exit(0); + case 'v': + opt.verbose = 1; + break; + case 'a': + if (strcasecmp(optarg, "rsa") == 0) { + opt.alg = TPM2_ALG_RSA; + break; + } else if (strcasecmp(optarg, "ecdsa") == 0) { + opt.alg = TPM2_ALG_ECDSA; + break; + } else { + ERR("Unknown algorithm.\n"); + exit(1); + } + case 'c': + if (strcasecmp(optarg, "nist_p256") == 0) { + opt.curve = TPM2_ECC_NIST_P256; + break; + } else if (strcasecmp(optarg, "nist_p384") == 0) { + opt.curve = TPM2_ECC_NIST_P384; + break; + } else { + ERR("Unknown curve.\n"); + exit(1); + } + case 'e': + if (sscanf(optarg, "%i", &opt.exponent) != 1) { + ERR("Error parsing keysize.\n"); + exit(1); + } + break; + case 'u': + opt.importpub = optarg; + break; + case 'r': + opt.importtpm = optarg; + break; + case 'o': + opt.ownerpw = optarg; + break; + case 'p': + opt.password = optarg; + break; + case 'P': + if (sscanf(optarg, "%x", &opt.parent) != 1 && + sscanf(optarg, "0x%x", &opt.parent) != 1 && + sscanf(optarg, "%i", &opt.parent) != 1) { + ERR("Error parsing parent handle"); + exit(1); + } + break; + case 'W': + opt.parentpw = optarg; + break; + case 's': + if (sscanf(optarg, "%i", &opt.keysize) != 1) { + ERR("Error parsing keysize.\n"); + exit(1); + } + break; + case 't': + opt.tcti_conf = optarg; + break; + default: + ERR("Unknown option at index %i.\n\n", opt_idx); + ERR("%s", help); + exit(1); + } + } + + /* parse the non-option arguments */ + if (optind >= argc) { + ERR("Missing argument .\n\n"); + ERR("%s", help); + exit(1); + } + opt.filename = argv[optind]; + optind++; + + if (optind < argc) { + ERR("Unknown argument provided.\n\n"); + ERR("%s", help); + exit(1); + } + + if (!!opt.importpub != !!opt.importtpm) { + ERR("Import requires both --public and --private\n"); + return 1; + } + + return 0; +} + +/** Generate an RSA key + * + * This function calls out to generate an RSA key using the TPM. + * @retval TPM2_DATA data to be written to disk + * @retval NULL on failure + */ +static TPM2_DATA * +genkey_rsa() +{ + VERB("Generating RSA key using TPM\n"); + + RSA *rsa = NULL; + BIGNUM *e = BN_new(); + if (!e) { + ERR("out of memory\n"); + return NULL; + } + BN_set_word(e, opt.exponent); + + rsa = RSA_new(); + if (!rsa) { + ERR("out of memory\n"); + BN_free(e); + return NULL; + } + if (!tpm2tss_rsa_genkey(rsa, opt.keysize, e, opt.password, opt.parent)) { + BN_free(e); + RSA_free(rsa); + ERR("Error: Generating key failed\n"); + return NULL; + } + + VERB("Key generated\n"); + + TPM2_DATA *tpm2Data = OPENSSL_malloc(sizeof(*tpm2Data)); + if (tpm2Data == NULL) { + ERR("out of memory\n"); + BN_free(e); + RSA_free(rsa); + return NULL; + } + memcpy(tpm2Data, RSA_get_app_data(rsa), sizeof(*tpm2Data)); + + BN_free(e); + RSA_free(rsa); + + return tpm2Data; +} + +/** Generate an ECDSA key + * + * This function calls out to generate an ECDSA key using the TPM. + * @retval TPM2_DATA data to be written to disk + * @retval NULL on failure + */ +static TPM2_DATA * +genkey_ecdsa() +{ + EC_KEY *eckey = NULL; + + eckey = EC_KEY_new(); + if (!eckey) { + ERR("out of memory\n"); + return NULL; + } + if (!tpm2tss_ecc_genkey(eckey, opt.curve, opt.password, opt.parent)) { + EC_KEY_free(eckey); + ERR("Error: Generating key failed\n"); + return NULL; + } + + TPM2_DATA *tpm2Data = OPENSSL_malloc(sizeof(*tpm2Data)); + if (tpm2Data == NULL) { + ERR("out of memory\n"); + EC_KEY_free(eckey); + return NULL; + } + memcpy(tpm2Data, tpm2tss_ecc_getappdata(eckey), sizeof(*tpm2Data)); + + EC_KEY_free(eckey); + + return tpm2Data; +} + +/** Main function + * + * This function initializes OpenSSL and then calls the key generation + * functions. + * @param argc The argument count. + * @param argv The arguments. + * @retval 0 on success + * @retval 1 on failure + */ +int +main(int argc, char **argv) +{ + if (parse_opts(argc, argv) != 0) + exit(1); + + int r; + TPM2_DATA *tpm2Data = NULL; + +#if OPENSSL_VERSION_NUMBER < 0x1010000fL + OPENSSL_config(NULL); +#else + OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL); +#endif + + /* Initialize the tpm2-tss engine */ + ENGINE_load_dynamic(); + + /* Openssl 1.1.0 requires the lib-prefix for the engine_id */ + ENGINE *tpm_engine = ENGINE_by_id("tpm2tss"); + if (!tpm_engine) + tpm_engine = ENGINE_by_id("libtpm2tss"); + if (tpm_engine == NULL) { + ERR("Could not load tpm2tss engine\n"); + return 1; + } + + int init_res = ENGINE_init(tpm_engine); + VERB("Engine name: %s\nInit result: %d \n", ENGINE_get_name(tpm_engine), + init_res); + if (!init_res) + return 1; + + if (opt.ownerpw && + !ENGINE_ctrl(tpm_engine, TPM2TSS_SET_OWNERAUTH, 0, opt.ownerpw, NULL)) { + ERR("Could not set ownerauth\n"); + return 1; + } + + if (opt.parentpw && + !ENGINE_ctrl(tpm_engine, TPM2TSS_SET_PARENTAUTH, 0, opt.parentpw, NULL)) { + ERR("Could not set parentauth\n"); + return 1; + } + + if (opt.tcti_conf && + !ENGINE_ctrl(tpm_engine, TPM2TSS_SET_TCTI, 0, opt.tcti_conf, NULL)) { + ERR("Could not set parentauth\n"); + return 1; + } + + if (opt.importpub && opt.importtpm) { + VERB("Importing the TPM key\n"); + r = tpm2tss_tpm2data_importtpm(opt.importpub, opt.importtpm, opt.parent, + opt.password == NULL, &tpm2Data); + if (r != 1) + return 1; + } else switch (opt.alg) { + case TPM2_ALG_RSA: + VERB("Generating the rsa key\n"); + tpm2Data = genkey_rsa(); + break; + case TPM2_ALG_ECDSA: + VERB("Generating the ecdsa key\n"); + tpm2Data = genkey_ecdsa(); + break; + default: + break; + } + + if (tpm2Data == NULL) { + ERR("Key could not be generated.\n"); + return 1; + } + + /* Write the key to disk */ + VERB("Writing key to disk\n"); + + if (!tpm2tss_tpm2data_write(tpm2Data, opt.filename)) { + ERR("Error writing file\n"); + OPENSSL_free(tpm2Data); + return 1; + } + + OPENSSL_free(tpm2Data); + + VERB("*** SUCCESS ***\n"); + return 0; +} diff --git a/test/ecdh.sh b/test/ecdh.sh new file mode 100755 index 0000000..3462e74 --- /dev/null +++ b/test/ecdh.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +set -euf + +# Create a primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx +tpm2_createprimary --hierarchy=o \ + --key-algorithm=ecc \ + --hash-algorithm=sha256 \ + --key-context=${PARENT_CTX} + +# Create an ECDH key pair +echo "Generating ECDH key pair" +ECDH_TPM_PUBKEY=ecdhtpm.pub +ECDH_TPM_KEY=ecdhtpm +tpm2_create --key-auth=abc \ + --parent-context=${PARENT_CTX} \ + --key-algorithm=ecc256:ecdh-sha256 \ + --public=${ECDH_TPM_PUBKEY} \ + --private=${ECDH_TPM_KEY} \ + --attributes fixedparent\|fixedtpm\|decrypt\|sensitivedataorigin\|userwithauth\|noda +tpm2_flushcontext --transient-object + +# Load key to persistent handle +ECDH_CTX=ecdhkey.ctx +tpm2_load --parent-context=${PARENT_CTX} \ + --public=${ECDH_TPM_PUBKEY} \ + --private=${ECDH_TPM_KEY} \ + --key-context=${ECDH_CTX} +tpm2_flushcontext --transient-object + +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${ECDH_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Get public key of handle +ECDH_TPM_PUBKEY_PEM=ecdhtpm.pem +tpm2_readpublic --object-context=${HANDLE} --output=${ECDH_TPM_PUBKEY_PEM} --format=pem + +# Generate peer key pair +ECDH_PEER_PUBKEY=echdpeer.pub +ECDH_PEER_KEY=ecdhpeer +openssl ecparam -name prime256v1 -genkey -noout -out ${ECDH_PEER_KEY} +openssl ec -in ${ECDH_PEER_KEY} -pubout -out ${ECDH_PEER_PUBKEY} + +# Perform ECDH using the TPM key pair as the private key and the peer key pair as the public key +SECRET0=$(echo "abc" | openssl pkeyutl -derive -engine tpm2tss -keyform engine -inkey ${HANDLE} -peerkey ${ECDH_PEER_PUBKEY} -peerform pem -passin stdin | base64) +echo -e "TPM(prv) <-> PEER(pub): ${SECRET0}" + +# Perform ECDH with the peer key pair as the private key and the TPM key pair as the public key +SECRET1=$(openssl pkeyutl -derive -inkey ${ECDH_PEER_KEY} -peerkey ${ECDH_TPM_PUBKEY_PEM} -peerform pem | base64) +echo -e "TPM(pub) <-> PEER(prv): ${SECRET1}" + +# Release persistent HANDLE and remove files +tpm2_evictcontrol --object-context=${HANDLE} +rm ${ECDH_PEER_KEY} ${ECDH_PEER_PUBKEY} ${ECDH_TPM_PUBKEY} ${ECDH_TPM_KEY} ${ECDH_TPM_PUBKEY_PEM} ${ECDH_CTX} + +# Ensure tpm and peer generated secrets are the same +if [ "${SECRET0}" != "${SECRET1}" ]; then + echo "secrets don't match" + exit 1 +fi diff --git a/test/ecdsa-emptyauth.sh b/test/ecdsa-emptyauth.sh new file mode 100755 index 0000000..62757dd --- /dev/null +++ b/test/ecdsa-emptyauth.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata + +tpm2tss-genkey -a ecdsa -c nist_p256 mykey + +openssl pkeyutl -keyform engine -engine tpm2tss -inkey mykey -sign -in mydata -out mysig + +R="$(openssl pkeyutl -keyform engine -engine tpm2tss -inkey mykey -verify -in mydata -sigfile mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/ecdsa-handle-flush.sh b/test/ecdsa-handle-flush.sh new file mode 100755 index 0000000..18d8685 --- /dev/null +++ b/test/ecdsa-handle-flush.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata.txt + +# Create a Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=ecc \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object + +# Create an ECDSA key pair +echo "Generating ECDSA key pair" +TPM_ECDSA_PUBKEY=ecdsakey.pub +TPM_ECDSA_KEY=ecdsakey +tpm2_create --key-auth=abc \ + --parent-context=${PARENT_CTX} \ + --hash-algorithm=sha256 --key-algorithm=ecc \ + --public=${TPM_ECDSA_PUBKEY} --private=${TPM_ECDSA_KEY} \ + --attributes=sign\|decrypt\|fixedtpm\|fixedparent\|sensitivedataorigin\|userwithauth\|noda +tpm2_flushcontext --transient-object + +# Load Key to persistent handle +ECDSA_CTX=ecdsakey.ctx +tpm2_load --parent-context=${PARENT_CTX} \ + --public=${TPM_ECDSA_PUBKEY} --private=${TPM_ECDSA_KEY} \ + --key-context=${ECDSA_CTX} +tpm2_flushcontext --transient-object + +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${ECDSA_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Signing Data +R="$(echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey ${HANDLE} -sign -in mydata.txt -out mysig -passin stdin 2>&1 || true)" +if echo $R | grep "ErrorCode (0x000001c4)" > /dev/null; then + echo $R + exit 1 +fi +# Get public key of handle +tpm2_readpublic --object-context=${HANDLE} --output=mykey.pem --format=pem + +# Release persistent HANDLE +tpm2_evictcontrol --hierarchy=o --object-context=${HANDLE} + +R="$(openssl pkeyutl -pubin -inkey mykey.pem -verify -in mydata.txt -sigfile mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/ecdsa-restricted.sh b/test/ecdsa-restricted.sh new file mode 100755 index 0000000..5e7b6e1 --- /dev/null +++ b/test/ecdsa-restricted.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -eufx + +# Generate 2k + a bit of data +dd if=/dev/zero of=mydata.txt count=4 bs=512 status=none +echo -n "abcde12345abcde12345">>mydata.txt + +# Create a Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=ecc \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object + +# Create an ECDSA key pair +echo "Generating ECDSA key pair" +TPM_ECDSA_PUBKEY=ecdsakey.pub +TPM_ECDSA_KEY=ecdsakey +tpm2_create --parent-context=${PARENT_CTX} \ + --hash-algorithm=sha256 --key-algorithm=ecc256:ecdsa-sha256:null \ + --public=${TPM_ECDSA_PUBKEY} --private=${TPM_ECDSA_KEY} \ + --attributes=sign\|restricted\|fixedtpm\|fixedparent\|sensitivedataorigin\|userwithauth\|noda +tpm2_flushcontext --transient-object + +# Load Key to persistent handle +ECDSA_CTX=ecdsakey.ctx +tpm2_load --parent-context=${PARENT_CTX} \ + --public=${TPM_ECDSA_PUBKEY} --private=${TPM_ECDSA_KEY} \ + --key-context=${ECDSA_CTX} +tpm2_flushcontext --transient-object + +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${ECDSA_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +tpm2_readpublic --object-context=${HANDLE} + +# Digest & sign Data +openssl dgst -engine tpm2tss -keyform engine -sha256 -sign ${HANDLE} -out mysig mydata.txt + +# Get public key of handle +tpm2_readpublic --object-context=${HANDLE} --output=mykey.pem --format=pem + +# Release persistent HANDLE +tpm2_evictcontrol --hierarchy=o --object-context=${HANDLE} + +R="$(openssl dgst -verify mykey.pem -sha256 -signature mysig mydata.txt || true)" +if ! echo $R | grep "Verified OK" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/ecdsa.sh b/test/ecdsa.sh new file mode 100755 index 0000000..35c0647 --- /dev/null +++ b/test/ecdsa.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata + +tpm2tss-genkey -a ecdsa -c nist_p256 -p abc mykey + +echo "abc" | openssl pkeyutl -keyform engine -engine tpm2tss -inkey mykey -sign -in mydata -out mysig -passin stdin + +R="$(echo "abc" | openssl pkeyutl -keyform engine -engine tpm2tss -inkey mykey -verify -in mydata -sigfile mysig -passin stdin || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/error_tpm2-tss-engine-common.c b/test/error_tpm2-tss-engine-common.c new file mode 100644 index 0000000..775f602 --- /dev/null +++ b/test/error_tpm2-tss-engine-common.c @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: BSD-2 */ +/******************************************************************************* + * Copyright 2019, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + ******************************************************************************/ + +#include "tpm2-tss-engine.h" +#include "tpm2-tss-engine-common.h" + +#ifdef HAVE_EXECINFO +#include +#endif +#include +#include +#include +#include + +TSS2_RC +__wrap_Esys_Initialize() +{ + printf("Esys_Initialize called\n"); +#ifdef HAVE_EXECINFO + void* b[128]; + backtrace_symbols_fd(b, backtrace(b, sizeof(b)/sizeof(b[0])), STDOUT_FILENO); +#endif + return -1; +} + +void +check_tpm2tss_tpm2data_readtpm(void **state) +{ + (void)(state); + int i; + i = tpm2tss_tpm2data_readtpm(0, NULL); + assert_int_equal(i, 0); +} + +void +check_tpm2tss_tpm2data_read(void **state) +{ + (void)(state); + int i; + i = tpm2tss_tpm2data_read("", NULL); + assert_int_equal(i, 0); +} + +void +check_init_tpm_parent_via_api(void **state) +{ + (void)(state); + int i; + i = tpm2tss_rsa_genkey(NULL, 0, NULL, NULL, 0); + assert_int_equal(i, 0); +} + +void +check_init_tpm_parent(void **state) +{ + (void)(state); + TSS2_RC r; + ESYS_CONTEXT *e; + ESYS_TR t; + r = init_tpm_parent(&e, -1, &t); + assert_int_not_equal(r, TSS2_RC_SUCCESS); +} + +void +check_init_tpm_key(void **state) +{ + (void)(state); + int i; + TSS2_RC r; + i = tpm2tss_rsa_genkey(NULL, 0, NULL, NULL, 0); + assert_int_equal(i, 0); + + ESYS_CONTEXT *e; + ESYS_TR t; + TPM2_DATA td = { .privatetype = KEY_TYPE_HANDLE }; + r = init_tpm_key(&e, &t, &td); + assert_int_not_equal(r, TSS2_RC_SUCCESS); + //assert_int_equal(1, 0); +} + +int +main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(check_tpm2tss_tpm2data_readtpm), + cmocka_unit_test(check_tpm2tss_tpm2data_read), + cmocka_unit_test(check_init_tpm_parent_via_api), + cmocka_unit_test(check_init_tpm_parent), + cmocka_unit_test(check_init_tpm_key), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/failload.sh b/test/failload.sh new file mode 100755 index 0000000..c183dcc --- /dev/null +++ b/test/failload.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mykey +chmod ugo-rwx mykey + +R="$(openssl rsa -engine tpm2tss -inform engine -in mykey -pubout -outform pem -out mykey.pub 2>&1 || true)" +echo $R +if ! echo $R | grep "unable to load Private Key" >/dev/null; then + exit 1 +fi diff --git a/test/failwrite.sh b/test/failwrite.sh new file mode 100755 index 0000000..e160550 --- /dev/null +++ b/test/failwrite.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -eufx + +R="$(tpm2tss-genkey -a ecdsa -c nist_p256 -p abc /no/such/file/path 2>&1 || true)" +echo $R +if ! echo $R | grep "Error writing file" >/dev/null; then + exit 1 +fi diff --git a/test/neg-handle.pem b/test/neg-handle.pem new file mode 100644 index 0000000..95801ea --- /dev/null +++ b/test/neg-handle.pem @@ -0,0 +1,13 @@ +-----BEGIN TSS2 PRIVATE KEY----- +MIIB8gYGZ4EFCgEDoAMBAQECBIEAAAEEggEYARYAAQALAAYEcgAAABAAEAgAAAEA +AQEAyJBHMXSEunTQBWTX2uot2qnvMBEJbhuM4+/bv7Ltaz2zjFdxdSB5tLp4fJZQ +AoUggU3HmF8sOGYfHTFJeNZJRFqXdB9sotNWLrWUeMXrAxdDJitGli5n87YrCTDu +6/DbJYbw1sd4/QL0sqXgzLogU7VPJhc+el5DjjimEeN6oU99zfN1HZacPTs74h0Q +LPrL3BACc/lkg1q6ePREulRI/Atcy5g5hgApfjSB6kMrbOwzzkGiZVZpZBqfPaik +k0SjQqNZFYejfDt99PgKQHyPHfuEVrjS788jQKvRWoPTYUQCI6iJDcp5JLk0RbqV +gD68RWwhQVDCmUpq5ebP/f/47wSBwAC+ACDN2bcOjh1KxxE8YlJXVdmuwBiUL3mF +hLLNWV3HWHnoAAAQ3OnaC4u9p1bOSyUPcw7fUR4UTNbqD2cSwPPMNRslR5RhoNBP ++j6M2vlKP7UeSxZ/at8CZHtKWV+VS+Osy9Dn+wHdqa1YSvRCBgP1a75OI9jjQ+li +I64327Vq1ZEl0LIyWdCCWrISRMcVT7JPmGhtuAS4KdHztl58JV9mntQPclW3Rp4o +5M/74zf2eaTxZOBV+OxhPR77SSQQ+w== +-----END TSS2 PRIVATE KEY----- diff --git a/test/rand.sh b/test/rand.sh new file mode 100755 index 0000000..a8ee049 --- /dev/null +++ b/test/rand.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -eufx + +openssl rand -engine tpm2tss -hex 10 >/dev/null diff --git a/test/rsadecrypt.sh b/test/rsadecrypt.sh new file mode 100755 index 0000000..c4c58c3 --- /dev/null +++ b/test/rsadecrypt.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata + +tpm2tss-genkey -a rsa -s 2048 -p abc mykey + +echo "abc" | openssl rsa -engine tpm2tss -inform engine -in mykey -pubout -outform pem -out mykey.pub -passin stdin + +openssl pkeyutl -pubin -inkey mykey.pub -encrypt -in mydata -out mycipher +rm mydata + +echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -decrypt -in mycipher -out mydata -passin stdin +#this is a workaround because -decrypt sometimes exits 0 falsely +test "x$(cat mydata)" = "xabcde12345abcde12345" diff --git a/test/rsasign.sh b/test/rsasign.sh new file mode 100755 index 0000000..9cd4484 --- /dev/null +++ b/test/rsasign.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata + +tpm2tss-genkey -a rsa -s 2048 -p abc mykey + +echo "abc" | openssl rsa -engine tpm2tss -inform engine -in mykey -pubout -outform pem -out mykey.pub -passin stdin + +echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -sign -in mydata -out mysig -passin stdin + +#this is a workaround because -verify allways exits 1 +R="$(openssl pkeyutl -pubin -inkey mykey.pub -verify -in mydata -sigfile mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_importtpm.sh b/test/rsasign_importtpm.sh new file mode 100755 index 0000000..0133b65 --- /dev/null +++ b/test/rsasign_importtpm.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -eufx + +DIR=$(mktemp -d) +TPM_RSA_PUBKEY=${DIR}/rsakey.pub +TPM_RSA_KEY=${DIR}/rsakey +PARENT_CTX=${DIR}/primary_owner_key.ctx + +echo -n "abcde12345abcde12345">${DIR}/mydata + +tpm2_startup -c || true + +# Create primary key as persistent handle +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=ecc \ + --key-context=${PARENT_CTX} \ + --attributes="decrypt|fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted" +tpm2_flushcontext --transient-object + +# Create an RSA key pair +echo "Generating RSA key pair" +tpm2_create --key-auth=abc --parent-context=${PARENT_CTX} \ + --hash-algorithm=sha256 --key-algorithm=rsa \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --attributes="sign|decrypt|fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda" +tpm2_flushcontext --transient-object + +tpm2tss-genkey --public ${TPM_RSA_PUBKEY} --private ${TPM_RSA_KEY} --password abc ${DIR}/mykey + +echo "abc" | openssl rsa -engine tpm2tss -inform engine -in ${DIR}/mykey -pubout -outform pem -out ${DIR}/mykey.pub -passin stdin + +echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey ${DIR}/mykey -sign -in ${DIR}/mydata -out ${DIR}/mysig -passin stdin + +#this is a workaround because -verify allways exits 1 +R="$(openssl pkeyutl -pubin -inkey ${DIR}/mykey.pub -verify -in ${DIR}/mydata -sigfile ${DIR}/mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_importtpmparent.sh b/test/rsasign_importtpmparent.sh new file mode 100755 index 0000000..37c1daf --- /dev/null +++ b/test/rsasign_importtpmparent.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -eufx + +DIR=$(mktemp -d) +TPM_RSA_PUBKEY=${DIR}/rsakey.pub +TPM_RSA_KEY=${DIR}/rsakey +PARENT_CTX=${DIR}/primary_owner_key.ctx + +echo -n "abcde12345abcde12345">${DIR}/mydata + +tpm2_startup -c || true + +# Create primary key as persistent handle +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=rsa \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${PARENT_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Create an RSA key pair +echo "Generating RSA key pair" +tpm2_create --key-auth=abc --parent-context=${HANDLE} \ + --hash-algorithm=sha256 --key-algorithm=rsa \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --attributes="sign|decrypt|fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda" +tpm2_flushcontext --transient-object + +tpm2tss-genkey --public ${TPM_RSA_PUBKEY} --private ${TPM_RSA_KEY} --password abc --parent ${HANDLE} ${DIR}/mykey + +echo "abc" | openssl rsa -engine tpm2tss -inform engine -in ${DIR}/mykey -pubout -outform pem -out ${DIR}/mykey.pub -passin stdin + +echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey ${DIR}/mykey -sign -in ${DIR}/mydata -out ${DIR}/mysig -passin stdin + +# Release persistent HANDLE +tpm2_evictcontrol --hierarchy=o --object-context=${HANDLE} + +#this is a workaround because -verify allways exits 1 +R="$(openssl pkeyutl -pubin -inkey ${DIR}/mykey.pub -verify -in ${DIR}/mydata -sigfile ${DIR}/mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_parent.sh b/test/rsasign_parent.sh new file mode 100755 index 0000000..3788b50 --- /dev/null +++ b/test/rsasign_parent.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata.txt + +# Create an Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=rsa \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object + +# Load primary key to persistent handle +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${PARENT_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Generating a key underneath the persistent parent +tpm2tss-genkey -a rsa -s 2048 -p abc -P ${HANDLE} mykey + +echo "abc" | openssl rsa -engine tpm2tss -inform engine -in mykey -pubout -outform pem -out mykey.pub -passin stdin +cat mykey.pub + +echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey mykey -sign -in mydata.txt -out mysig -passin stdin + +# Release persistent HANDLE +tpm2_evictcontrol --hierarchy=o --object-context=${HANDLE} + +#this is a workaround because -verify allways exits 1 +R="$(openssl pkeyutl -pubin -inkey mykey.pub -verify -in mydata.txt -sigfile mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_parent_pass.sh b/test/rsasign_parent_pass.sh new file mode 100755 index 0000000..ded2c2a --- /dev/null +++ b/test/rsasign_parent_pass.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata.txt + +# Create an Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=rsa \ + --key-context=${PARENT_CTX} --key-auth=abc +tpm2_flushcontext --transient-object + +# Load primary key to persistent handle +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${PARENT_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Generating a key underneath the persistent, password protected, parent +tpm2tss-genkey -a rsa -s 2048 -p abc -P ${HANDLE} -W abc mykey + +cat > engine.conf </dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_persistent.sh b/test/rsasign_persistent.sh new file mode 100755 index 0000000..734b4bf --- /dev/null +++ b/test/rsasign_persistent.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata.txt + +# Create an Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=rsa \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object + +# Create an RSA key pair +echo "Generating RSA key pair" +TPM_RSA_PUBKEY=rsakey.pub +TPM_RSA_KEY=rsakey +tpm2_create --key-auth=abc \ + --parent-context=${PARENT_CTX} \ + --hash-algorithm=sha256 --key-algorithm=rsa \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --attributes=sign\|decrypt\|fixedtpm\|fixedparent\|sensitivedataorigin\|userwithauth\|noda +tpm2_flushcontext --transient-object + +# Load Key to persistent handle +RSA_CTX=rsakey.ctx +tpm2_load --parent-context=${PARENT_CTX} \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --key-context=${RSA_CTX} +tpm2_flushcontext --transient-object + +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${RSA_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Signing Data +echo "abc" | openssl pkeyutl -engine tpm2tss -keyform engine -inkey ${HANDLE} -sign -in mydata.txt -out mysig -passin stdin +# Get public key of handle +tpm2_readpublic --object-context=${HANDLE} --output=mykey.pem --format=pem + +# Release persistent HANDLE +tpm2_evictcontrol --hierarchy=o --object-context=${HANDLE} + +R="$(openssl pkeyutl -pubin -inkey mykey.pem -verify -in mydata.txt -sigfile mysig || true)" +if ! echo $R | grep "Signature Verified Successfully" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_persistent_emptyauth.sh b/test/rsasign_persistent_emptyauth.sh new file mode 100755 index 0000000..4558b48 --- /dev/null +++ b/test/rsasign_persistent_emptyauth.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -eufx + +echo -n "abcde12345abcde12345">mydata.txt + +# Create an Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=rsa \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object + +# Create an RSA key pair +echo "Generating RSA key pair" +TPM_RSA_PUBKEY=rsakey.pub +TPM_RSA_KEY=rsakey +tpm2_create --parent-context=${PARENT_CTX} \ + --hash-algorithm=sha256 --key-algorithm=rsa \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --attributes=sign\|decrypt\|fixedtpm\|fixedparent\|sensitivedataorigin\|userwithauth\|noda +tpm2_flushcontext --transient-object + +# Load Key to persistent handle +RSA_CTX=rsakey.ctx +tpm2_load --parent-context=${PARENT_CTX} \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --key-context=${RSA_CTX} +tpm2_flushcontext --transient-object + +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${RSA_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +# Signing Data +#Actually signing should not require an auth value +if ! openssl pkeyutl -engine tpm2tss -keyform engine -inkey ${HANDLE} -sign -in mydata.txt -out mysig -passin file:notexists; then +#The expect script is only here, because tpm2-tss <2.2 had some bug, and thus us asking for passwords when none were required. +expect </dev/null; then + echo $R + exit 1 +fi diff --git a/test/rsasign_restricted.sh b/test/rsasign_restricted.sh new file mode 100755 index 0000000..e5facc2 --- /dev/null +++ b/test/rsasign_restricted.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -eufx + +# Generate 2k + a bit of data +dd if=/dev/zero of=mydata.txt count=4 bs=512 status=none +echo -n "abcde12345abcde12345">>mydata.txt + +# Create a Primary key pair +echo "Generating primary key" +PARENT_CTX=primary_owner_key.ctx + +tpm2_createprimary --hierarchy=o --hash-algorithm=sha256 --key-algorithm=rsa \ + --key-context=${PARENT_CTX} +tpm2_flushcontext --transient-object + +# Create an RSA key pair +echo "Generating RSA key pair" +TPM_RSA_PUBKEY=rsakey.pub +TPM_RSA_KEY=rsakey +tpm2_create --parent-context=${PARENT_CTX} \ + --hash-algorithm=sha256 --key-algorithm=rsa:rsassa-sha256:null \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --attributes=sign\|restricted\|fixedtpm\|fixedparent\|sensitivedataorigin\|userwithauth\|noda +tpm2_flushcontext --transient-object + +# Load Key to persistent handle +RSA_CTX=rsakey.ctx +tpm2_load --parent-context=${PARENT_CTX} \ + --public=${TPM_RSA_PUBKEY} --private=${TPM_RSA_KEY} \ + --key-context=${RSA_CTX} +tpm2_flushcontext --transient-object + +HANDLE=$(tpm2_evictcontrol --hierarchy=o --object-context=${RSA_CTX} | cut -d ' ' -f 2 | head -n 1) +tpm2_flushcontext --transient-object + +tpm2_readpublic --object-context=${HANDLE} + +# Digest & sign Data +openssl dgst -engine tpm2tss -keyform engine -sha256 -sign ${HANDLE} -out mysig mydata.txt + +# Get public key of handle +tpm2_readpublic --object-context=${HANDLE} --output=mykey.pem --format=pem + +# Release persistent HANDLE +tpm2_evictcontrol --hierarchy=o --object-context=${HANDLE} + +R="$(openssl dgst -verify mykey.pem -sha256 -signature mysig mydata.txt || true)" +if ! echo $R | grep "Verified OK" >/dev/null; then + echo $R + exit 1 +fi diff --git a/test/sclient.sh b/test/sclient.sh new file mode 100755 index 0000000..d5e3459 --- /dev/null +++ b/test/sclient.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -eufx + +if openssl version | grep "OpenSSL 1.0.2" >/dev/null; then + echo "OpenSSL 1.0.2 does not load the certificate; private key mismatch ???" + exit 77 +fi + +echo -en "SSL CONNECTION WORKING\n">test.html + +function cleanup() +{ + kill -term $SERVER || true +} + +openssl ecparam -genkey -name prime256v1 -noout -out ca.key + +echo -e "\n\n\n\n\n\n\n" | openssl req -new -x509 -batch -extensions v3_ca -key ca.key -out ca.crt + +echo -e "\n\n\n\n\n\n\n\n\n" | openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr + +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt + +tpm2tss-genkey -a rsa client.tpm.key + +echo -e "\n\n\n\n\n\n\n\n\n" | openssl req -new -key client.tpm.key -keyform engine -engine tpm2tss -out client.csr + +openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt + +openssl s_server -cert server.crt -key server.key -accept 8443 -verify 1 -CAfile ca.crt -WWW & +SERVER=$! + +sleep 1 + +kill -0 $! + +trap "cleanup" EXIT + +# We have to sleep, such that the pipe stays open until the command is finished. +(echo -e "GET /test.html HTTP/1.1\r\n\r\n" && sleep 1) | openssl s_client -connect 127.0.0.1:8443 -cert client.crt -key client.tpm.key -engine tpm2tss -keyform engine -CAfile ca.crt + +echo "SUCCESS" diff --git a/test/sh_log_compiler.sh b/test/sh_log_compiler.sh new file mode 100755 index 0000000..1b5ea20 --- /dev/null +++ b/test/sh_log_compiler.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +export LANG=C +export OPENSSL_ENGINES="${OPENSSL_ENGINES:=$PWD/.libs}" +export LD_LIBRARY_PATH="$OPENSSL_ENGINES:${LD_LIBRARY_PATH-}" +export PATH="$PWD:$PATH" + +if [ -z "$2" ]; then + # no device passed + test_script="$(realpath "$1")" +else + test_script="$(realpath "$2")" + INTEGRATION_DEVICE=$1 +fi + +echo "Creating tpm2tss symlink" +ln -fs libtpm2tss.so .libs/tpm2tss.so + +tmp_dir="$(mktemp --directory)" +echo "Switching to temporary directory $tmp_dir" +cd "$tmp_dir" + +if [ -z "$INTEGRATION_DEVICE" ]; then + # No device is passed so the TPM simulator will be used. + for simulator in 'swtpm' 'tpm_server'; do + simulator_binary="$(command -v "$simulator")" && break + done + if [ -z "$simulator_binary" ]; then + echo 'ERROR: No TPM simulator was found on PATH' + exit 99 + fi + + for attempt in $(seq 9 -1 0); do + simulator_port="$(shuf --input-range 1024-65534 --head-count 1)" + echo "Starting simulator on port $simulator_port" + case "$simulator_binary" in + *swtpm) "$simulator_binary" socket --tpm2 --server port="$simulator_port" \ + --ctrl type=tcp,port="$(( simulator_port + 1 ))" \ + --flags not-need-init --tpmstate dir="$tmp_dir" \ + --seccomp "action=none" &;; + *tpm_server) "$simulator_binary" -port "$simulator_port" &;; + esac + simulator_pid="$!" + sleep 1 + + if ( ss --listening --tcp --ipv4 --processes | grep "$simulator_pid" | grep --quiet "$simulator_port" && + ss --listening --tcp --ipv4 --processes | grep "$simulator_pid" | grep --quiet "$(( simulator_port + 1 ))" ) + then + echo "Simulator with PID $simulator_pid started successfully" + break + else + echo "Failed to start simulator, the port might be in use" + kill "$simulator_pid" + + if [ "$attempt" -eq 0 ]; then + echo 'ERROR: Reached maximum number of tries to start simulator, giving up' + exit 99 + fi + fi + done + + case "$simulator_binary" in + *swtpm) export TPM2TSSENGINE_TCTI="swtpm:port=$simulator_port";; + *tpm_server) export TPM2TSSENGINE_TCTI="mssim:port=$simulator_port";; + esac + export TPM2TOOLS_TCTI="$TPM2TSSENGINE_TCTI" + + tpm2_startup --clear +else + # A physical TPM will be used for the integration test. + echo "Running the test with $INTEGRATION_DEVICE" + export TPM2TSSENGINE_TCTI="libtss2-tcti-device.so:$INTEGRATION_DEVICE" + export TPM2TOOLS_TCTI="$TPM2TSSENGINE_TCTI" +fi + +echo "Starting $test_script" +"$test_script" +test_status="$?" + +kill "$simulator_pid" +rm -rf "$tmp_dir" + +exit "$test_status" diff --git a/test/sserver.sh b/test/sserver.sh new file mode 100755 index 0000000..a54e879 --- /dev/null +++ b/test/sserver.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -eufx + +if openssl version | grep "OpenSSL 1.0.2" >/dev/null; then + echo "OpenSSL 1.0.2 does not load the certificate; private key mismatch ???" + exit 77 +fi + +echo -n "WORKING !!!">index.html + +function cleanup() +{ + kill -term $SERVER +} + +tpm2tss-genkey -a ecdsa mykey + +echo -e "\n\n\n\n\n\n\n" | openssl req -new -x509 -engine tpm2tss -key mykey -keyform engine -out mykey.crt + +openssl s_server -www -cert mykey.crt -key mykey -keyform engine -engine tpm2tss -accept 127.0.0.1:8444 & +SERVER=$! +trap "cleanup" EXIT + +sleep 1 + +echo "GET index.html" | openssl s_client -connect localhost:8444 diff --git a/test/tpm2-tss-engine-common.c b/test/tpm2-tss-engine-common.c new file mode 100644 index 0000000..e42ca56 --- /dev/null +++ b/test/tpm2-tss-engine-common.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: BSD-2 */ +/******************************************************************************* + * Copyright 2021, Erik Larsson + * All rights reserved. + ******************************************************************************/ + +#include "tpm2-tss-engine.h" + +#include +#include + +void +check_tpm2tss_tpm2data_read(void **state) +{ + (void)(state); + TPM2_DATA *tpm2Data = NULL; + int rc; + rc = tpm2tss_tpm2data_read(NEG_HANDLE_PEM, &tpm2Data); + assert_int_equal(rc, 1); + assert_int_equal(tpm2Data->parent, 0x81000001); +} + +int +main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(check_tpm2tss_tpm2data_read), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} -- Gitee