├── .ci ├── coverity.run ├── docker.env ├── docker.run └── get_deps.sh ├── .github └── workflows │ ├── codeql.yml │ └── main.yml ├── .lgtm.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Doxyfile.in ├── INSTALL.md ├── LICENSE ├── MAINTAINERS ├── Makefile.am ├── README.md ├── RELEASE.md ├── SECURITY.md ├── bootstrap ├── configure.ac ├── dist ├── dracut │ ├── README │ ├── cleanup-tpm2-totp.sh │ ├── module-setup.sh.in │ └── show-tpm2-totp.sh ├── initcpio │ ├── hooks │ │ ├── plymouth-tpm2-totp │ │ └── tpm2-totp │ └── install │ │ ├── plymouth-tpm2-totp.in │ │ ├── sd-plymouth-tpm2-totp.in │ │ ├── sd-tpm2-totp.in │ │ └── tpm2-totp.in ├── initramfs-tools │ ├── hooks │ │ └── tpm2-totp.in │ └── scripts │ │ └── init-premount │ │ └── tpm2-totp ├── plymouth-tpm2-totp.service.in ├── show-tpm2-totp ├── tpm2-totp.pc.in ├── tpm2-totp.service.in └── tpm2-totp.timer.in ├── git.mk ├── include └── tpm2-totp.h ├── m4 └── flags.m4 ├── man └── tpm2-totp.1.md ├── src ├── libtpm2-totp.c ├── plymouth-tpm2-totp.c └── tpm2-totp.c └── test ├── libtpm2-totp.c ├── libtpm2-totp.sh ├── plymouth-tpm2-totp.sh ├── sh_log_compiler.sh └── tpm2-totp.sh /.ci/coverity.run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: BSD-2 3 | 4 | set -eo pipefail 5 | 6 | echo "PROJECT=$PROJECT" 7 | 8 | if [ -z "$COVERITY_SCAN_TOKEN" ]; then 9 | echo "coverity.run invoked without COVERITY_SCAN_TOKEN set...exiting!" 10 | exit 1 11 | fi 12 | 13 | if [ -z "$COVERITY_SUBMISSION_EMAIL" ]; then 14 | echo "coverity.run invoked without COVERITY_SUBMISSION_EMAIL set...exiting!" 15 | exit 1 16 | fi 17 | 18 | # Sanity check, this should only be executing on the coverity_scan branch 19 | if [[ "$REPO_BRANCH" != *coverity_scan ]]; then 20 | echo "coverity.run invoked for non-coverity branch $REPO_BRANCH...exiting!" 21 | exit 1 22 | fi 23 | 24 | if [[ "$CC" == clang* ]]; then 25 | echo "Coverity scan branch detected, not running with clang...exiting!" 26 | exit 1 27 | fi 28 | 29 | # branch is coverity_scan 30 | echo "Running coverity build" 31 | 32 | # ensure coverity_scan tool is available to the container 33 | # We cannot package these in the docker image, as we would be distributing their software 34 | # for folks not coupled to our COVERITY_SCAN_TOKEN. 35 | if [ ! -f "$(pwd)/cov-analysis/bin/cov-build" ]; then 36 | curl --data-urlencode "project=$PROJECT" \ 37 | --data-urlencode "token=$COVERITY_SCAN_TOKEN" \ 38 | "https://scan.coverity.com/download/linux64" -o coverity_tool.tgz 39 | 40 | stat coverity_tool.tgz 41 | 42 | curl --data-urlencode "project=$PROJECT" \ 43 | --data-urlencode "token=$COVERITY_SCAN_TOKEN" \ 44 | --data-urlencode "md5=1" \ 45 | "https://scan.coverity.com/download/linux64" -o coverity_tool.md5 46 | 47 | stat coverity_tool.md5 48 | cat coverity_tool.md5 49 | md5sum coverity_tool.tgz 50 | echo "$(cat coverity_tool.md5)" coverity_tool.tgz | md5sum -c 51 | 52 | echo "unpacking cov-analysis" 53 | tar -xf coverity_tool.tgz 54 | mv cov-analysis-* cov-analysis 55 | fi 56 | 57 | export PATH=$PATH:$(pwd)/cov-analysis/bin 58 | 59 | echo "Which cov-build: $(which cov-build)" 60 | 61 | # get the deps to build with 62 | $DOCKER_BUILD_DIR/.ci/get_deps.sh "$(dirname $DOCKER_BUILD_DIR)" 63 | 64 | pushd "$DOCKER_BUILD_DIR" 65 | 66 | echo "Performing build with Coverity Scan" 67 | rm -rf cov-int 68 | ./bootstrap && ./configure --disable-defaultflags --enable-debug && make clean 69 | cov-build --dir $DOCKER_BUILD_DIR/cov-int make -j $(nproc) 70 | 71 | echo "Collecting Coverity data for submission" 72 | rm -fr README 73 | AUTHOR="$(git log -1 $HEAD --pretty="%aN")" 74 | AUTHOR_EMAIL="$(git log -1 $HEAD --pretty="%aE")" 75 | VERSION="$(git rev-parse HEAD)" 76 | echo "Name: $AUTHOR" >> README 77 | echo "Email: $AUTHOR_EMAIL" >> README 78 | echo "Project: $PROJECT" >> README 79 | echo "Build-Version: $VERSION" >> README 80 | echo "Description: $REPO_NAME $REPO_BRANCH" >> README 81 | echo "Submitted-by: $PROJECT CI" >> README 82 | echo "---README---" 83 | cat README 84 | echo "---EOF---" 85 | 86 | rm -f "$PROJECT-scan.tgz" 87 | tar -czf "$PROJECT-scan.tgz" README cov-int 88 | 89 | rm -rf README cov-int 90 | 91 | # upload the results 92 | echo "Testing for scan results..." 93 | scan_file=$(stat --printf='%n' "$PROJECT-scan.tgz") 94 | 95 | echo "Submitting data to Coverity" 96 | curl --form token="$COVERITY_SCAN_TOKEN" \ 97 | --form email="$COVERITY_SUBMISSION_EMAIL" \ 98 | --form project="$PROJECT" \ 99 | --form file=@"$scan_file" \ 100 | --form version="$VERSION" \ 101 | --form description="$REPO_NAME $REPO_BRANCH" \ 102 | "https://scan.coverity.com/builds?project=$PROJECT" 103 | 104 | rm -rf "$PROJECT-scan.tgz" 105 | 106 | popd 107 | 108 | exit 0 109 | -------------------------------------------------------------------------------- /.ci/docker.env: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-2 2 | 3 | PROJECT 4 | DOCKER_BUILD_DIR 5 | LD_LIBRARY_PATH=/usr/local/lib/ 6 | 7 | CC 8 | 9 | COVERITY_SCAN_TOKEN 10 | COVERITY_SUBMISSION_EMAIL 11 | 12 | PROJECT 13 | 14 | REPO_BRANCH 15 | REPO_NAME 16 | 17 | ENABLE_COVERAGE 18 | ENABLE_FUZZING 19 | DOCKER_IMAGE 20 | 21 | TPM2TSS_BRANCH 22 | TPM2TOOLS_BRANCH 23 | -------------------------------------------------------------------------------- /.ci/docker.run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: BSD-2 3 | 4 | set -exo pipefail 5 | 6 | $DOCKER_BUILD_DIR/.ci/get_deps.sh "$(dirname $DOCKER_BUILD_DIR)" 7 | 8 | pushd $DOCKER_BUILD_DIR 9 | 10 | SCAN_PREFIX="" 11 | CONFIGURE_OPTIONS="" 12 | 13 | if [ -d build ]; then 14 | rm -rf build 15 | fi 16 | 17 | ./bootstrap 18 | 19 | mkdir build 20 | pushd build 21 | 22 | if [ -z "$CC" -o "$CC" == "gcc" ]; then 23 | export CONFIGURE_OPTIONS+=" --disable-defaultflags --enable-code-coverage"; 24 | else 25 | export SCAN_PREFIX="scan-build --status-bugs" 26 | fi 27 | 28 | $SCAN_PREFIX ../configure $CONFIGURE_OPTIONS --enable-integration 29 | $SCAN_PREFIX make -j$(nproc) 30 | 31 | make -j$(nproc) check 32 | cat test-suite.log config.log 33 | ../configure $CONFIGURE_OPTIONS 34 | make -j$(nproc) distcheck 35 | cat config.log 36 | popd 37 | 38 | if [ "x$ENABLE_COVERAGE" = "xtrue" ]; then 39 | bash <(curl -s https://codecov.io/bash) 40 | fi 41 | 42 | popd 43 | -------------------------------------------------------------------------------- /.ci/get_deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: BSD-2 3 | 4 | set -exo pipefail 5 | 6 | if [[ $DOCKER_IMAGE == fedora* ]]; then 7 | yum -y install qrencode-devel liboath-devel plymouth-devel 8 | elif [[ $DOCKER_IMAGE == opensuse* ]]; then 9 | zypper -n in qrencode-devel liboath-devel plymouth-devel 10 | elif [[ $DOCKER_IMAGE == ubuntu* ]]; then 11 | apt-get update; apt-get -y install libqrencode-dev liboath-dev libplymouth-dev plymouth 12 | fi 13 | 14 | pushd "$1" 15 | 16 | if [ -z "$TPM2TSS_BRANCH" ]; then 17 | echo "TPM2TSS_BRANCH is unset, please specify TPM2TSS_BRANCH" 18 | exit 1 19 | fi 20 | 21 | if [ -z "$TPM2TOOLS_BRANCH" ]; then 22 | echo "TPM2TOOLS_BRANCH is unset, please specify TPM2TOOLS_BRANCH" 23 | exit 1 24 | fi 25 | 26 | # Install tpm2-tss 27 | if [ ! -d tpm2-tss ]; then 28 | 29 | git clone --depth=1 -b "${TPM2TSS_BRANCH}" "https://github.com/tpm2-software/tpm2-tss.git" 30 | pushd tpm2-tss 31 | ./bootstrap 32 | ./configure --enable-debug 33 | make -j$(nproc) 34 | make install 35 | popd 36 | else 37 | echo "tpm2-tss already installed, skipping..." 38 | fi 39 | 40 | # Install tpm2-tools 41 | if [ ! -d tpm2-tools ]; then 42 | git clone --depth=1 -b "${TPM2TOOLS_BRANCH}" "https://github.com/tpm2-software/tpm2-tools.git" 43 | pushd tpm2-tools 44 | ./bootstrap 45 | ./configure --enable-debug --disable-hardening 46 | make -j$(nproc) 47 | make install 48 | popd 49 | else 50 | echo "tpm2-tss already installed, skipping..." 51 | fi 52 | 53 | popd 54 | 55 | exit 0 56 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "12 6 * * 6" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ cpp ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Install Packages 30 | run: | 31 | sudo apt-get update 32 | sudo apt-get install --yes autoconf-archive libcurl4-openssl-dev libjson-c-dev libssl-dev acl 33 | 34 | - name: After Prepare 35 | run: | 36 | cd "$RUNNER_TEMP" 37 | mkdir installdir 38 | wget https://github.com/tpm2-software/tpm2-tss/archive/master.tar.gz 39 | git clone https://github.com/tpm2-software/tpm2-tss.git 40 | cd tpm2-tss 41 | ./bootstrap 42 | ./configure --prefix="$RUNNER_TEMP/installdir/usr" --disable-doxygen-doc 43 | make install 44 | export PKG_CONFIG_PATH="$RUNNER_TEMP/installdir/usr/lib/pkgconfig:$PKG_CONFIG_PATH" && echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH" >> $GITHUB_ENV 45 | export LD_LIBRARY_PATH="$RUNNER_TEMP/installdir/usr/lib:$LD_LIBRARY_PATH" && echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV 46 | 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v2 49 | with: 50 | languages: ${{ matrix.language }} 51 | queries: +security-and-quality 52 | 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v2 55 | 56 | - name: Perform CodeQL Analysis 57 | uses: github/codeql-action/analyze@v2 58 | with: 59 | category: "/language:${{ matrix.language }}" 60 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Linux Build Status 2 | on: 3 | [push, pull_request] 4 | jobs: 5 | build-test: 6 | runs-on: ubuntu-latest 7 | if: "!contains(github.ref, 'coverity_scan')" 8 | strategy: 9 | matrix: 10 | DOCKER_IMAGE: ["ubuntu-18.04", "ubuntu-20.04", "fedora-32", "opensuse-leap"] 11 | TPM2TSS_BRANCH: ["3.0.x"] 12 | TPM2TOOLS_BRANCH: ["4.0"] 13 | CC: ["gcc", "clang"] 14 | steps: 15 | - name: Check out repository 16 | uses: actions/checkout@v2 17 | - name: Launch Action 18 | uses: 19 | tpm2-software/ci/runCI@main 20 | with: 21 | DOCKER_IMAGE: "${{ matrix.DOCKER_IMAGE }}" 22 | TPM2TSS_BRANCH: "${{ matrix.TPM2TSS_BRANCH }}" 23 | TPM2TOOLS_BRANCH: "${{ matrix.TPM2TOOLS_BRANCH }}" 24 | CC: "${{ matrix.CC }}" 25 | PROJECT_NAME: ${{ github.event.repository.name }} 26 | - name: failure 27 | if: ${{ failure() }} 28 | run: cat build/test-suite.log || true 29 | coverage-test: 30 | runs-on: ubuntu-latest 31 | if: "!contains(github.ref, 'coverity_scan')" 32 | steps: 33 | - name: Check out repository 34 | uses: actions/checkout@v2 35 | - name: Launch Action 36 | uses: 37 | tpm2-software/ci/runCI@main 38 | with: 39 | DOCKER_IMAGE: ubuntu-18.04 40 | TPM2TSS_BRANCH: "3.0.x" 41 | TPM2TOOLS_BRANCH: "4.0" 42 | CC: gcc 43 | ENABLE_COVERAGE: true 44 | PROJECT_NAME: ${{ github.event.repository.name }} 45 | - name: failure 46 | if: ${{ failure() }} 47 | run: cat build/test-suite.log || true 48 | coverity-test: 49 | runs-on: ubuntu-latest 50 | environment: coverity 51 | if: contains(github.ref, 'coverity_scan') && github.event_name == 'push' 52 | steps: 53 | - name: Check out repository 54 | uses: actions/checkout@v2 55 | - name: Launch Coverity Scan Action 56 | uses: 57 | tpm2-software/ci/coverityScan@main 58 | with: 59 | PROJECT_NAME: ${{ github.event.repository.name }} 60 | ENABLE_COVERITY: true 61 | TPM2TSS_BRANCH: "3.0.x" 62 | TPM2TOOLS_BRANCH: "4.0" 63 | REPO_BRANCH: ${{ github.ref }} 64 | REPO_NAME: ${{ github.repository }} 65 | DOCKER_IMAGE: ubuntu-18.04 66 | CC: gcc 67 | COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} 68 | COVERITY_SUBMISSION_EMAIL: diabonas@gmx.de 69 | whitespace-check: 70 | runs-on: ubuntu-latest 71 | if: github.event_name == 'pull_request' && !contains(github.ref, 'coverity_scan') 72 | steps: 73 | - name: Check out repository 74 | uses: actions/checkout@v2 75 | - name: Perform Whitespace Check 76 | env: 77 | BASE_REF: ${{ github.base_ref }} 78 | run: git fetch origin "$BASE_REF" && git diff --check "origin/$BASE_REF" 79 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | cpp: 3 | prepare: 4 | packages: 5 | - autoconf-archive 6 | - libcurl4-openssl-dev 7 | - libjson-c-dev 8 | - libssl-dev 9 | - acl 10 | after_prepare: 11 | - cd "$LGTM_WORKSPACE" 12 | - mkdir installdir 13 | - wget https://github.com/tpm2-software/tpm2-tss/archive/master.tar.gz 14 | - git clone https://github.com/tpm2-software/tpm2-tss.git 15 | - cd tpm2-tss 16 | - ./bootstrap 17 | - ./configure --prefix="$LGTM_WORKSPACE/installdir/usr" --disable-doxygen-doc 18 | - make install 19 | - export PKG_CONFIG_PATH="$LGTM_WORKSPACE/installdir/usr/lib/pkgconfig:$PKG_CONFIG_PATH" 20 | - export LD_LIBRARY_PATH="$LGTM_WORKSPACE/installdir/usr/lib:$LD_LIBRARY_PATH" 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.3.0] - 2020-09-13 8 | ### Added 9 | - New option `--label` to specify the label to use in the TOTP authenticator app. 10 | - User-friendly error messages for common error conditions. 11 | - Support for running the integration tests with the swtpm simulator. 12 | 13 | ## [0.2.1] - 2019-12-28 14 | ### Fixed 15 | - Fix `show-tpm2-totp` installation location when using dracut without plymouth. 16 | - Add missing include `endian.h` to improve portability. 17 | - Fix warning for dracut udev rule. 18 | 19 | ## [0.2.0] - 2019-10-22 20 | ### Added 21 | - pkg-config file for libtpm2-totp. 22 | - New option `-T`/`--tcti` to specify the TCTI to be used. 23 | - New binary `plymouth-tpm2-totp` for integration with plymouth during boot. 24 | - Integration into initramfs images using mkinitcpio, dracut and initramfs-tools. 25 | - New option `--disable-defaultflags` to disable default compilation flags. 26 | - tpm2-totp(3) man page for libtpm2-totp. 27 | 28 | ### Fixed 29 | - Fix overlinking of libqrencode and libdl. 30 | 31 | ## [0.1.2] - 2019-07-25 32 | ### Changed 33 | - Include pkg-config dependecy on libtss2-mu in order to work with tpm2-tss 2.3 34 | - Fix compiler error on uninitialized variable 35 | - Fix format strings for 32bit architectures. 36 | 37 | ## [0.1.1] - 2019-04-04 38 | ### Changed 39 | - Removed SHA384 from default PCR banks since it's unsupported by many TPMs. 40 | 41 | ## [0.1.0] - 2019-03-25 42 | ### Added 43 | - Initial release of the an TPM2.0-based library and executable for machine to 44 | human authentication using the TCG's TPM Software Stack compliant tpm2-tss 45 | libraries. 46 | - libtpm2totp (the library) functional implementation for reuse. 47 | - tpm2-totp (CLI tool) executable wrapper for library. 48 | - man-pages are included. 49 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [MAINTAINERS](MAINTAINERS). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidelines for submitting bugs: 2 | All non security bugs should be filed on the Issues tracker: 3 | https://github.com/tpm2-software/tpm2-totp/issues 4 | 5 | Security sensitive bugs should follow the instructions in SECURITY.md. 6 | 7 | # Guideline for submitting changes: 8 | All changes to the source code must follow the coding standard used in the 9 | tpm2-tss project [here](https://github.com/tpm2-software/tpm2-tss/blob/master/doc/coding_standard_c.md). 10 | 11 | All changes should be introduced via github pull requests. This allows anyone to 12 | comment and provide feedback in lieu of having a mailing list. For pull requests 13 | opened by non-maintainers, any maintainer may review and merge that pull 14 | request. For maintainers, they either must have their pull request reviewed by 15 | another maintainer if possible, or leave the PR open for at least 24 hours, we 16 | consider this the window for comments. 17 | 18 | ## Patch requirements 19 | * All tests must pass on Travis CI for the merge to occur. 20 | * All changes must not introduce superfluous changes or whitespace errors. 21 | * All commits should adhere to the git commit message guidelines described 22 | here: https://chris.beams.io/posts/git-commit/ with the following exceptions. 23 | * We allow commit subject lines up to 80 characters. 24 | * All contributions must adhere to the Developers Certificate of Origin. The 25 | full text of the DCO is here: https://developercertificate.org/. Contributors 26 | must add a 'Signed-off-by' line to their commits. This indicates the 27 | submitters acceptance of the DCO. 28 | 29 | ## Guideline for merging changes 30 | 31 | Pull Requests MUST be assigned to an upcoming release tag. If a release milestone does 32 | not exist, the maintainer SHALL create it per the [RELEASE.md](RELEASE.md) instructions. 33 | When accepting and merging a change, the maintainer MUST edit the description field for 34 | the release milestone to add the CHANGELOG entry. 35 | 36 | Changes must be merged with the "rebase" option on github to avoid merge commits. 37 | This provides for a clear linear history. 38 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | ## GNU/Linux 4 | * GNU Autoconf 5 | * GNU Autoconf Archive 6 | * GNU Automake 7 | * GNU Libtool 8 | * C compiler 9 | * C library development libraries and header files 10 | * pkg-config 11 | * tpm2-tss >= 2.3 12 | * libqrencode 13 | * pandoc (optional, for man pages) 14 | * doxygen (optional, for man pages) 15 | * plymouth header files (optional, for initramfs integration) 16 | 17 | For the integration test suite: 18 | * liboath 19 | * [swtpm](https://github.com/stefanberger/swtpm) or [tpm_server](https://sourceforge.net/projects/ibmswtpm2/) 20 | * realpath 21 | * ss 22 | 23 | ## Ubuntu 24 | ``` 25 | sudo apt -y install \ 26 | build-essential \ 27 | autoconf \ 28 | autoconf-archive \ 29 | automake \ 30 | m4 \ 31 | libtool \ 32 | gcc \ 33 | pkg-config \ 34 | libqrencode-dev \ 35 | pandoc \ 36 | doxygen \ 37 | liboath-dev \ 38 | iproute2 \ 39 | plymouth \ 40 | libplymouth-dev 41 | git clone --depth=1 http://www.github.com/tpm2-software/tpm2-tss 42 | cd tpm2-tss 43 | ./bootstrap 44 | ./configure 45 | make -j$(nproc) 46 | sudo make install 47 | ``` 48 | 49 | # Building from source 50 | ``` 51 | ./bootstrap 52 | ./configure 53 | make -j$(nproc) 54 | make -j$(nproc) check 55 | sudo make install 56 | ``` 57 | 58 | # Configuration options 59 | You may pass the following options to `./configure` 60 | 61 | ## Debug messages 62 | This option will enable a lot of debug printing during the invocation of the 63 | library: 64 | ``` 65 | ./configure --enable-debug 66 | ``` 67 | 68 | ## Developer linking 69 | In order to link against a developer version of tpm2-tss (not installed): 70 | ``` 71 | ./configure \ 72 | PKG_CONFIG_PATH=${TPM2TSS}/lib:$PKG_CONFIG_PATH \ 73 | CFLAGS=-I${TPM2TSS}/include \ 74 | LDFLAGS=-L${TPM2TSS}/src/tss2-{tcti,mu,sys,esys}/.libs 75 | ``` 76 | 77 | # initramfs-tools and mkinitcpio integration 78 | To make sure that the hooks get installed to the correct directory, either use 79 | ``` 80 | ./configure --sysconfdir=/etc 81 | ``` 82 | or set the correct directory directly with the `--with-initramfstoolsdir`/ 83 | `--with-mkinitcpiodir` configuration option. 84 | 85 | # Post installation 86 | 87 | ## ldconfig 88 | You may need to run ldconfig after `make install` to update runtime bindings: 89 | ``` 90 | sudo ldconfig 91 | ``` 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Linux TPM2 & TSS2 Software 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Andreas Fuchs 2 | Juergen Repp (occasionally) 3 | Jonas Witschel 4 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2018 Fraunhofer SIT 3 | # All rights reserved. 4 | 5 | -include $(top_srcdir)/git.mk 6 | 7 | ### Initialize global variables used throughout the file ### 8 | INCLUDE_DIRS = -I$(srcdir)/include -I$(srcdir)/src 9 | ACLOCAL_AMFLAGS = -I m4 --install 10 | AM_CFLAGS = $(INCLUDE_DIRS) $(EXTRA_CFLAGS) $(TSS2_ESYS_CFLAGS) \ 11 | $(TSS2_MU_CFLAGS) $(CODE_COVERAGE_CFLAGS) 12 | AM_LDFLAGS = $(EXTRA_LDFLAGS) $(CODE_COVERAGE_LIBS) 13 | AM_LDADD = $(TSS2_ESYS_LIBS) $(TSS2_MU_LIBS) 14 | 15 | AM_DISTCHECK_CONFIGURE_FLAGS = --with-dracutmodulesdir='$$(libdir)/dracut/modules.d' \ 16 | --with-systemdsystemunitdir='$$(libdir)/systemd/system' 17 | 18 | # Initialize empty variables to be extended throughout 19 | bin_PROGRAMS = 20 | libexec_PROGRAMS = 21 | helpers_PROGRAMS = 22 | noinst_PROGRAMS = 23 | check_PROGRAMS = 24 | include_HEADERS = 25 | lib_LTLIBRARIES = 26 | noinst_LTLIBRARIES = 27 | EXTRA_DIST = 28 | DISTCLEANFILES = 29 | CLEANFILES = 30 | MOSTLYCLEANFILES = 31 | MAINTAINERCLEANFILES = \ 32 | $(DIST_ARCHIVES) \ 33 | AUTHORS 34 | GITIGNOREFILES = \ 35 | $(GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL) \ 36 | $(GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN) \ 37 | $(GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL) \ 38 | aminclude_static.am \ 39 | m4/ax_ac_append_to_file.m4 \ 40 | m4/ax_ac_print_to_file.m4 \ 41 | m4/ax_add_am_macro_static.m4 \ 42 | m4/ax_add_fortify_source.m4 \ 43 | m4/ax_am_macros_static.m4 \ 44 | m4/ax_check_compile_flag.m4 \ 45 | m4/ax_check_enable_debug.m4 \ 46 | m4/ax_check_gnu_make.m4 \ 47 | m4/ax_check_link_flag.m4 \ 48 | m4/ax_code_coverage.m4 \ 49 | m4/ax_file_escapes.m4 \ 50 | m4/ax_is_release.m4 \ 51 | m4/ax_prog_doxygen.m4 \ 52 | m4/ax_recursive_eval.m4 \ 53 | m4/pkg.m4 54 | 55 | ### Add ax_* rules ### 56 | # ax_code_coverage 57 | if AUTOCONF_CODE_COVERAGE_2019_01_06 58 | include $(top_srcdir)/aminclude_static.am 59 | clean-local: code-coverage-clean 60 | distclean-local: code-coverage-dist-clean 61 | else 62 | @CODE_COVERAGE_RULES@ 63 | endif 64 | 65 | # ax_prog_doxygen 66 | @DX_RULES@ 67 | MOSTLYCLEANFILES += $(DX_CLEANFILES) 68 | 69 | ### Library ### 70 | lib_LTLIBRARIES += libtpm2-totp.la 71 | include_HEADERS += include/tpm2-totp.h 72 | 73 | libtpm2_totp_la_SOURCES = src/libtpm2-totp.c 74 | libtpm2_totp_la_LIBADD = $(AM_LDADD) 75 | libtpm2_totp_la_LDFLAGS = $(AM_LDFLAGS) 76 | 77 | pkgconfig_DATA = dist/tpm2-totp.pc 78 | 79 | ### Executable ### 80 | bin_PROGRAMS += tpm2-totp 81 | 82 | tpm2_totp_SOURCES = src/tpm2-totp.c 83 | tpm2_totp_CFLAGS = $(AM_CFLAGS) $(TSS2_TCTILDR_CFLAGS) $(TSS2_RC_CFLAGS) $(QRENCODE_CFLAGS) 84 | tpm2_totp_LDADD = $(AM_LDADD) $(TSS2_TCTILDR_LIBS) $(TSS2_RC_LIBS) $(QRENCODE_LIBS) libtpm2-totp.la 85 | tpm2_totp_LDFLAGS = $(AM_LDFLAGS) 86 | 87 | if HAVE_PLYMOUTH 88 | helpers_PROGRAMS += plymouth-tpm2-totp 89 | plymouth_tpm2_totp_SOURCES = src/plymouth-tpm2-totp.c 90 | plymouth_tpm2_totp_CFLAGS = $(AM_CFLAGS) $(TSS2_TCTILDR_CFLAGS) $(PLY_BOOT_CLIENT_CFLAGS) 91 | plymouth_tpm2_totp_LDADD = $(AM_LDADD) $(TSS2_TCTILDR_LIBS) $(PLY_BOOT_CLIENT_LIBS) libtpm2-totp.la 92 | plymouth_tpm2_totp_LDFLAGS = $(AM_LDFLAGS) 93 | endif # HAVE_PLYMOUTH 94 | 95 | ### Tests ### 96 | TESTS = 97 | 98 | if INTEGRATION 99 | TESTS += $(TESTS_SHELL) 100 | 101 | if HAVE_PLYMOUTH 102 | TESTS += $(TESTS_PLYMOUTH) 103 | endif # HAVE_PLYMOUTH 104 | endif #INTEGRATION 105 | TESTS_SHELL = test/libtpm2-totp.sh \ 106 | test/tpm2-totp.sh 107 | TESTS_PLYMOUTH = test/plymouth-tpm2-totp.sh 108 | EXTRA_DIST += $(TESTS_SHELL) $(TESTS_PLYMOUTH) 109 | TEST_EXTENSIONS = .sh 110 | SH_LOG_COMPILER = $(srcdir)/test/sh_log_compiler.sh 111 | EXTRA_DIST += $(SH_LOG_COMPILER) 112 | 113 | if INTEGRATION 114 | check_PROGRAMS += libtpm2-totp 115 | 116 | libtpm2_totp_SOURCES = test/libtpm2-totp.c 117 | libtpm2_totp_CFLAGS = $(AM_CFLAGS) $(TSS2_TCTILDR_CFLAGS) $(OATH_CFLAGS) 118 | libtpm2_totp_LDADD = $(AM_LDADD) $(TSS2_TCTILDR_LIBS) $(OATH_LIBS) libtpm2-totp.la 119 | libtpm2_totp_LDFLAGS = $(AM_LDFLAGS) $(OATH_LDFLAGS) 120 | endif #INTEGRATION 121 | 122 | # Adding user and developer information 123 | EXTRA_DIST += \ 124 | CHANGELOG.md \ 125 | CONTRIBUTING.md \ 126 | INSTALL.md \ 127 | LICENSE \ 128 | README.md \ 129 | VERSION 130 | 131 | # Generate the AUTHORS file from git log 132 | AUTHORS: 133 | $(AM_V_GEN)git log --format='%aN <%aE>' | \ 134 | grep -v 'users.noreply.github.com' | sort -u > $@ 135 | EXTRA_DIST += AUTHORS 136 | CLEANFILES += AUTHORS 137 | 138 | if HAVE_PANDOC_MAN_PAGES 139 | ### Man Pages 140 | man1_MANS = \ 141 | man/man1/tpm2-totp.1 142 | endif 143 | 144 | if HAVE_PANDOC 145 | # If pandoc is enabled, we want to generate the manpages for the dist tarball 146 | EXTRA_DIST += \ 147 | $(man1_MANS) 148 | 149 | else 150 | # If pandoc is not enabled, we want to complain that you need pandoc for make dist, 151 | # so hook the target and complain. 152 | dist-hook: 153 | @(>&2 echo "You do not have pandoc, a requirement for the distribution of manpages") 154 | @exit 1 155 | endif 156 | 157 | man/man1/%.1: man/%.1.md 158 | $(AM_V_GEN)mkdir -p man/man1 && cat $< | $(PANDOC) -s -t man >$@ 159 | 160 | EXTRA_DIST += \ 161 | man/tpm2-totp.1.md 162 | CLEANFILES += \ 163 | $(man1_MANS) 164 | 165 | if HAVE_DOXYGEN_MAN_PAGES 166 | man3_MANS = doxygen-doc/man/man3/tpm2-totp.3 167 | endif # HAVE_DOXYGEN_MAN_PAGES 168 | 169 | if HAVE_DOXYGEN 170 | $(man3_MANS): doxygen-doc 171 | EXTRA_DIST += $(man3_MANS) 172 | else # HAVE_DOXYGEN 173 | dist-hook: 174 | @(>&2 echo "You do not have doxygen, a requirement for the distribution of manpages") 175 | @exit 1 176 | endif # HAVE_DOXYGEN 177 | 178 | ### initramfs hooks ### 179 | 180 | EXTRA_DIST += dist/show-tpm2-totp 181 | 182 | if HAVE_DRACUT 183 | helpers_SCRIPTS = dist/show-tpm2-totp 184 | dracut_SCRIPTS = dist/dracut/module-setup.sh dist/dracut/show-tpm2-totp.sh \ 185 | dist/dracut/cleanup-tpm2-totp.sh 186 | dracut_DATA = dist/dracut/README 187 | endif # HAVE_DRACUT 188 | EXTRA_DIST += dist/dracut/show-tpm2-totp.sh dist/dracut/cleanup-tpm2-totp.sh dist/dracut/README 189 | 190 | if HAVE_INITRAMFSTOOLS 191 | if HAVE_PLYMOUTH 192 | initramfstools_hooks_SCRIPTS = dist/initramfs-tools/hooks/tpm2-totp 193 | initramfstools_scripts_SCRIPTS = dist/initramfs-tools/scripts/init-premount/tpm2-totp 194 | endif # HAVE_PLYMOUTH 195 | endif # HAVE_INITRAMFSTOOLS 196 | EXTRA_DIST += dist/initramfs-tools/scripts/init-premount/tpm2-totp 197 | 198 | if HAVE_MKINITCPIO 199 | helpers_SCRIPTS = dist/show-tpm2-totp 200 | initcpio_install_DATA = dist/initcpio/install/tpm2-totp dist/initcpio/install/sd-tpm2-totp 201 | initcpio_hooks_DATA = dist/initcpio/hooks/tpm2-totp 202 | 203 | systemdsystemunit_DATA = dist/tpm2-totp.service dist/tpm2-totp.timer 204 | install-systemd-service-hook: 205 | mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sysinit.target.wants && \ 206 | cd $(DESTDIR)$(systemdsystemunitdir)/sysinit.target.wants && \ 207 | $(LN_S) ../tpm2-totp.service && \ 208 | $(LN_S) ../tpm2-totp.timer 209 | if HAVE_PLYMOUTH 210 | initcpio_install_DATA += dist/initcpio/install/plymouth-tpm2-totp dist/initcpio/install/sd-plymouth-tpm2-totp 211 | initcpio_hooks_DATA += dist/initcpio/hooks/plymouth-tpm2-totp 212 | 213 | systemdsystemunit_DATA += dist/plymouth-tpm2-totp.service 214 | install-plymouth-service-hook: 215 | mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sysinit.target.wants && \ 216 | cd $(DESTDIR)$(systemdsystemunitdir)/sysinit.target.wants && \ 217 | $(LN_S) ../plymouth-tpm2-totp.service 218 | else 219 | install-plymouth-service-hook: 220 | endif # HAVE_PLYMOUTH 221 | 222 | install-data-hook: install-systemd-service-hook install-plymouth-service-hook 223 | 224 | endif # HAVE_MKINITCPIO 225 | EXTRA_DIST += dist/initcpio/hooks/tpm2-totp dist/initcpio/hooks/plymouth-tpm2-totp 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Linux Build Status](https://github.com/tpm2-software/tpm2-totp/workflows/Linux%20Build%20Status/badge.svg)](https://github.com/tpm2-software/tpm2-totp/actions) 2 | [![Code Coverage](https://codecov.io/gh/tpm2-software/tpm2-totp/branch/master/graph/badge.svg)](https://codecov.io/gh/tpm2-software/tpm2-totp) 3 | [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/tpm2-software/tpm2-totp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tpm2-software/tpm2-totp/context:cpp) 4 | [![Coverity Scan](https://scan.coverity.com/projects/22811/badge.svg)](https://scan.coverity.com/projects/tpm2-totp) 5 | 6 | # Overview 7 | This is a reimplementation of Matthew Garrett's 8 | [tpmtotp](https://github.com/mjg59/tpmtotp) software for TPM 2.0 using the 9 | [tpm2-tss](https://github.com/tpm2-software/tpm2-tss) software stack. Its 10 | purpose is to attest the trustworthiness of a device against a human using 11 | time-based one-time passwords (TOTP), facilitating the Trusted Platform Module 12 | (TPM) to bind the TOTP secret to the known trustworthy system state. In 13 | addition to the original tpmtotp, given the new capabilities of in-TPM HMAC 14 | calculation, the tpm2-totp's secret HMAC keys do not have to be exported from 15 | the TPM to the CPU's RAM on boot anymore. Another addition is the ability to 16 | rebind an old secret to the current PCRs in case a software component was 17 | changed on purpose, using a user-defined password. 18 | 19 | # Operations 20 | ## Setup 21 | When the platform is in a known trustworthy state, the user will generate a 22 | tpm2-totp secret that is sealed to the current PCR values of the TPM. The 23 | secret is also exported (e.g. via QR-Code) so it can be recorded in a TOTP 24 | application (e.g. freeotp on Android phones). The secret is also stored inside 25 | the TPM's NV space. 26 | 27 | ## Boot 28 | During boot the OS sends the current time to the TPM. The TPM checks that the 29 | correct PCR values are present and calculates the HMAC of the time input. This 30 | result is the TOTP value that will be displayed to the user. The user can 31 | compare this value to the TOTP value of his/her external device (e.g. phone) and 32 | thus assert the unalteredness and trustworthiness of his/her device. 33 | 34 | ## Recovery 35 | If the TOTP secret on the external device gets lost, there is a way to recover 36 | the secret, if a password was set during its generation. In this case the same 37 | QR code will be displayed to the user again. 38 | 39 | If an update occurs that changes one of the PCR values (e.g. BIOS or Bootloader) 40 | then the secret can be resealed to the new PCR values using the password. Then 41 | it will be available again on the next boot. 42 | 43 | # Build and install instructions 44 | Standard installation using 45 | ``` 46 | ./bootstrap 47 | ./configure 48 | make 49 | make install 50 | ``` 51 | Followed by setting up the initrd, see below. 52 | 53 | Instructions on packages needed to build and install tpm2-totp and different 54 | build options are available in the [INSTALL](INSTALL.md) file. 55 | 56 | # Initramfs integration 57 | The project includes hooks for [dracut](https://dracut.wiki.kernel.org/), 58 | [initramfs-tools](https://wiki.debian.org/initramfs-tools) and 59 | [mkinitcpio](https://wiki.archlinux.org/index.php/Mkinitcpio) to display 60 | the TOTP during boot using [Plymouth](https://www.freedesktop.org/wiki/Software/Plymouth/). 61 | They are automatically installed if the corresponding tool is found on the 62 | system (also see [INSTALL](INSTALL.md) regarding necessary configuration 63 | options). To use them, install tpm2-totp and initialize a TOTP secret, then enable 64 | the tpm2-totp hook in your initramfs generator and rebuild the initramfs. 65 | 66 | # Usage 67 | 68 | ## Setup 69 | The TOTP secret can be initialized with and without password. It is recommended to 70 | set a password `-P` in order to enable recovery options. Further, it is strongly 71 | recommended to provide the password via stdin, rather than directly as a 72 | command line option, to protect it from other processes, shell history, etc. 73 | Also the PCRs and PCR banks can be selected `-p` and `-b`. Default values are 74 | PCRs `0,2,4` and banks `SHA1, SHA256`. 75 | ``` 76 | tpm2-totp init 77 | 78 | tpm2-totp -P - init 79 | verysecret 80 | # or (recommended) 81 | gpg --decrypt /path/to/password.gpg | tpm2-totp -P - init 82 | # or (discouraged) 83 | tpm2-totp -P verysecret init 84 | 85 | tpm2-totp -P - -p 0,1,2,3,4,5,6 init 86 | tpm2-totp -p 0,1,2,3,4,5,6 -b SHA1,SHA256 init 87 | ``` 88 | 89 | ## Boot 90 | During boot the TOTP value for the current time, together with the current time 91 | should be shown to the user, e.g. using plymouth from mkinitrd or from dracut. 92 | The command to be executed is: 93 | ``` 94 | tpm2-totp show 95 | tpm2-totp -t show 96 | ``` 97 | 98 | ## Recovery 99 | In order to recover the QR code: 100 | ``` 101 | tpm2-totp -P - recover 102 | ``` 103 | In order to reseal the secret: 104 | ``` 105 | tpm2-totp -P - reseal 106 | tpm2-totp -P - -p 1,3,5,6 reseal 107 | ``` 108 | 109 | ## Deletion 110 | In order to delete the created NV index: 111 | ``` 112 | tpm2-totp clean 113 | ``` 114 | 115 | ## NV index 116 | All command additionally take the `-N` option to specify the NV index to be 117 | used. By default, 0x018094AF is used and recommended. 118 | ``` 119 | tpm2-totp -N 0x01800001 -P - init 120 | tpm2-totp -N 0x01800001 show 121 | tpm2-totp -N 0x01800001 -P - recover 122 | tpm2-totp -N 0x01800001 -P - reseal 123 | ``` 124 | 125 | # Limitations 126 | Whilst tpm2-totp provided the added security (in comparison to tpm-totp) that 127 | the key will not leave the TPM during the calculate operation, the time source 128 | is still not trustworthy and thus an attacker might in some situations be able 129 | to calculate a set of TOTP values for the future. Depending on the size of the 130 | possible attack window this can be very large though. 131 | 132 | It is not yet possible to specify specific PCR values independent of the 133 | currently set PCR values. This would allow disabling the password-less calculate 134 | operation after booting the device. This makes most sense, once a TSS2 FAPI 135 | is available that will enable an interface to a canonical PCR event log. 136 | 137 | Currently, an empty owner password is assumed. 138 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process: 2 | This document describes the general process that maintainers must follow when 3 | making a release of the `tpm2-totp` library and cli-tool. 4 | 5 | # Milestones 6 | 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), 7 | 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 8 | the CHANGELOG for that release. See [CHANGELOG Update](#CHANGELOG Update) for details. 9 | 10 | # Version Numbers 11 | This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 12 | 13 | In summary: Given a version number MAJOR.MINOR.PATCH, increment the: 14 | 1. MAJOR version when you make incompatible API changes, 15 | 2. MINOR version when you add functionality in a backwards-compatible manner, and 16 | 3. PATCH version when you make backwards-compatible bug fixes. 17 | Additional labels for pre-release and build metadata are available as extensions 18 | to the MAJOR.MINOR.PATCH format. 19 | 20 | ## Version String 21 | The version string is set for the rest of the autotools bits by autoconf. 22 | Autoconf gets this string from the `AC_INIT` macro in the configure.ac file. 23 | Once you decide on the next version number (using the scheme above) you must set 24 | it manually in configure.ac. The version string must be in the form `A.B.C` 25 | where `A`, `B` and `C` are integers representing the major, minor and micro 26 | components of the version number. 27 | 28 | ## Release Candidates 29 | In the run up to a release the maintainers may create tags to identify progress 30 | toward the release. In these cases we will append a string to the release number 31 | to indicate progress using the abbreviation `rc` for 'release candidate'. This 32 | string will take the form of `_rcX`. We append an incremental digit `X` in case 33 | more than one release candidate is necessary to communicate progress as 34 | development moves forward. 35 | 36 | # CHANGELOG Update 37 | Before tagging the repository with the release version, the maintainer MUST 38 | update the CHANGELOG file with the contents from the description field from the 39 | corresponding release milestone and update any missing version string details in 40 | the CHANGELOG and milestone entry. 41 | 42 | # Git Tags 43 | When a release is made a tag is created in the git repo identifying the release 44 | by the [version string](#Version String). The tag should be pushed to upstream 45 | git repo as the last step in the release process. 46 | **NOTE** tags for release candidates will be deleted from the git repository 47 | after a release with the corresponding version number has been made. 48 | **NOTE** release (not release candidate) tags should be considered immutable. 49 | 50 | ## Signed tags 51 | Git supports GPG signed tags and releases will have tags signed by a maintainer. 52 | For details on how to sign and verify git tags see: 53 | https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work. 54 | 55 | # Release tarballs 56 | We use the git tag as a way to mark the point of the release in the projects 57 | history. We do not however encourage users to build from git unless they intend 58 | to modify the source code and contribute to the project. For the end user we 59 | provide release tarballs following the GNU conventions as closely as possible. 60 | 61 | To make a release tarball use the `distcheck` make target. 62 | This target includes a number of sanity checks that are extremely helpful. 63 | For more information on `automake` and release tarballs see: 64 | https://www.gnu.org/software/automake/manual/html_node/Dist.html#Dist 65 | 66 | ## Hosting Releases on Github 67 | Github automagically generates a page in their UI that maps git tags to 68 | 'releases' (even if the tag isn't for a release). Additionally they support 69 | hosting release tarballs through this same interface. The release tarball 70 | created in the previous step must be posted to github using the release 71 | interface. Additionally, this tarball must be accompanied by a detached GPG 72 | signature. The Debian wiki has an excellent description of how to post a signed 73 | release to Github here: 74 | https://wiki.debian.org/Creating%20signed%20GitHub%20releases 75 | **NOTE** release candidates must be taken down after a release with the 76 | corresponding version number is available. 77 | 78 | ## Signing Release Tarballs 79 | Signatures must be generated using the `--detach-sign` and `--armor` options to 80 | the `gpg` command. 81 | 82 | ## Verifying Signatures 83 | Verifying the signature on a release tarball requires the project maintainers 84 | public keys be installed in the GPG keyring of the verifier. With both the 85 | release tarball and signature file in the same directory the following command 86 | will verify the signature: 87 | ``` 88 | $ gpg --verify tpm2-totp-X.Y.Z.tar.gz.asc 89 | ``` 90 | 91 | ## Signing Keys 92 | The GPG keys used to sign a release tag and the associated tarball must be the 93 | same. Additionally they must: 94 | * belong to a project maintainer 95 | * be discoverable using a public GPG key server 96 | * be associated with the maintainers github account 97 | (https://help.github.com/articles/adding-a-new-gpg-key-to-your-github-account/) 98 | 99 | # Announcements 100 | Release candidates and proper releases should be announced on the mailing list: 101 | - https://lists.linuxfoundation.org/mailman/listinfo/tpm2 102 | 103 | This announcement should be accompanied by a link to the release page on Github 104 | as well as a link to the CHANGELOG.md accompanying the release. 105 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Currently supported versions: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | any | :white_check_mark: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | ### Reporting 14 | 15 | Security vulnerabilities can be disclosed in one of two ways: 16 | - 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. 17 | - Email: A descirption *should be emailed* to **all** members of the [MAINTAINERS](MAINTAINERS) file to coordinate the 18 | disclosure of the vulnerability. 19 | 20 | ### Tracking 21 | 22 | When a maintainer is notified of a security vulnerability, they *must* create a GitHub security advisory 23 | per the instructions at: 24 | 25 | - 26 | 27 | Maintainers *should* use the optional feature through GitHub to request a CVE be issued, alternatively RedHat has provided CVE's 28 | in the past and *may* be used, but preference is on GitHub as the issuing CNA. 29 | 30 | ### Publishing 31 | 32 | Once ready, maintainers should publish the security vulnerability as outlined in: 33 | 34 | - 35 | 36 | As well as ensuring the publishing of the CVE, maintainers *shal*l have new release versions ready to publish at the same time as 37 | the CVE. Maintainers *should* should strive to adhere to a sub 60 say turn around from report to release. 38 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | git describe --tags --always --dirty > VERSION 6 | 7 | autoreconf --install --sym 8 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # Copyright (c) 2018 Fraunhofer SIT 3 | # All rights reserved. 4 | 5 | AC_PREREQ([2.68]) 6 | 7 | AC_INIT([tpm2-totp], 8 | [m4_esyscmd_s([cat ./VERSION])], 9 | [https://github.com/tpm2-software/tpm2-totp/issues], 10 | [], 11 | [https://github.com/tpm2-software/tpm2-totp]) 12 | dnl Avoid setting CFLAGS to anything by default; we use AC_CFLAGS below for this. 13 | : ${CFLAGS=""} 14 | 15 | dnl Let's be FHS-conform by default. 16 | if test "$prefix" = '/usr'; then 17 | test "$sysconfdir" = '${prefix}/etc' && sysconfdir="/etc" 18 | test "$sharedstatedir" = '${prefix}/com' && sharedstatedir="/var" 19 | test "$localstatedir" = '${prefix}/var' && localstatedir="/var" 20 | fi 21 | 22 | AC_CONFIG_MACRO_DIR([m4]) 23 | AC_CONFIG_SRCDIR([src/tpm2-totp.c]) 24 | AC_CONFIG_AUX_DIR([build-aux]) 25 | 26 | # propagate configure arguments to distcheck 27 | AC_SUBST([DISTCHECK_CONFIGURE_FLAGS],[$ac_configure_args]) 28 | 29 | AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Wno-portability]) 30 | #Backward compatible setting of "silent-rules" 31 | m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) 32 | AM_MAINTAINER_MODE([enable]) 33 | 34 | AX_IS_RELEASE([dash-version]) 35 | AX_CHECK_ENABLE_DEBUG([info]) 36 | 37 | AC_USE_SYSTEM_EXTENSIONS 38 | 39 | AC_PROG_CC 40 | LT_INIT() 41 | 42 | PKG_INSTALLDIR() 43 | 44 | helpersdir="$libexecdir/tpm2-totp" 45 | AC_SUBST([helpersdir]) 46 | AX_RECURSIVE_EVAL([$helpersdir], [HELPERSDIR]) 47 | AC_SUBST([HELPERSDIR]) 48 | 49 | AC_CONFIG_FILES([Makefile Doxyfile dist/tpm2-totp.pc]) 50 | 51 | AC_ARG_ENABLE([defaultflags], 52 | [AS_HELP_STRING([--disable-defaultflags], 53 | [Disable default preprocessor, compiler, and linker flags.])],, 54 | [enable_defaultflags=yes]) 55 | AS_IF([test "x$enable_defaultflags" = "xyes"], 56 | [ 57 | AX_ADD_COMPILER_FLAG([-std=c99]) 58 | AX_ADD_COMPILER_FLAG([-Wall]) 59 | AX_ADD_COMPILER_FLAG([-Wextra]) 60 | AX_ADD_COMPILER_FLAG([-Wformat-security]) 61 | AS_IF([test "x$ax_is_release" = "xno"], [AX_ADD_COMPILER_FLAG([-Werror])]) 62 | AX_ADD_COMPILER_FLAG([-fstack-protector-all]) 63 | AX_ADD_COMPILER_FLAG([-fpic]) 64 | AX_ADD_COMPILER_FLAG([-fPIC]) 65 | AX_ADD_COMPILER_FLAG([-O2]) 66 | AX_ADD_FORTIFY_SOURCE 67 | 68 | # work around GCC bug #53119 69 | # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119 70 | AX_ADD_COMPILER_FLAG([-Wno-missing-braces]) 71 | 72 | AX_ADD_LINK_FLAG([-Wl,--no-undefined]) 73 | AX_ADD_LINK_FLAG([-Wl,-z,noexecstack]) 74 | AX_ADD_LINK_FLAG([-Wl,-z,now]) 75 | AX_ADD_LINK_FLAG([-Wl,-z,relro]) 76 | ]) 77 | 78 | AX_CODE_COVERAGE 79 | m4_ifdef([_AX_CODE_COVERAGE_RULES], 80 | [AM_CONDITIONAL(AUTOCONF_CODE_COVERAGE_2019_01_06, [true])], 81 | [AM_CONDITIONAL(AUTOCONF_CODE_COVERAGE_2019_01_06, [false])]) 82 | AX_ADD_AM_MACRO_STATIC([]) 83 | 84 | PKG_PROG_PKG_CONFIG([0.25]) 85 | PKG_CHECK_MODULES([TSS2_ESYS],[tss2-esys]) 86 | PKG_CHECK_MODULES([TSS2_MU],[tss2-mu]) 87 | PKG_CHECK_MODULES([TSS2_TCTILDR],[tss2-tctildr]) 88 | PKG_CHECK_MODULES([TSS2_RC],[tss2-rc]) 89 | PKG_CHECK_VAR([TSS2_TCTI_DEVICE_LIBDIR], [tss2-tcti-device], [libdir], , 90 | [AC_MSG_ERROR([Required library tss2-tcti-device not found])]) 91 | AC_SUBST([TSS2_TCTI_DEVICE_LIBDIR]) 92 | PKG_CHECK_MODULES([QRENCODE],[libqrencode]) 93 | 94 | DX_DOXYGEN_FEATURE(ON) 95 | DX_DOT_FEATURE(OFF) 96 | DX_HTML_FEATURE(OFF) 97 | DX_CHM_FEATURE(OFF) 98 | DX_CHI_FEATURE(OFF) 99 | DX_MAN_FEATURE(ON) 100 | DX_RTF_FEATURE(OFF) 101 | DX_XML_FEATURE(OFF) 102 | DX_PDF_FEATURE(OFF) 103 | DX_PS_FEATURE(OFF) 104 | DX_INIT_DOXYGEN([$PACKAGE_NAME], [Doxyfile], [doxygen-doc]) 105 | AS_IF([test -z "$DX_DOXYGEN"], 106 | [AC_MSG_WARN([Required executable doxygen not found, man pages will not be built])]) 107 | AM_CONDITIONAL([HAVE_DOXYGEN],[test -n "$DX_DOXYGEN"]) 108 | AM_CONDITIONAL([HAVE_DOXYGEN_MAN_PAGES],[test -d "${srcdir}/doxygen-doc/man/man3" -o -n "$DX_DOXYGEN"]) 109 | 110 | AC_PATH_PROG([PANDOC], [pandoc]) 111 | AS_IF([test -z "$PANDOC"], 112 | [AC_MSG_WARN([Required executable pandoc not found, man pages will not be built])]) 113 | AM_CONDITIONAL([HAVE_PANDOC],[test -n "$PANDOC"]) 114 | AM_CONDITIONAL([HAVE_PANDOC_MAN_PAGES],[test -d "${srcdir}/man/man1" -o -n "$PANDOC"]) 115 | 116 | AC_ARG_WITH([dracutmodulesdir], 117 | AS_HELP_STRING([--with-dracutmodulesdir=DIR], [directory for dracut hooks]),, 118 | [PKG_CHECK_VAR([with_dracutmodulesdir], [dracut], [dracutmodulesdir])]) 119 | AM_CONDITIONAL(HAVE_DRACUT, [test -n "$with_dracutmodulesdir" -a "x$with_dracutmodulesdir" != xno]) 120 | AM_COND_IF([HAVE_DRACUT], [AC_SUBST([dracutdir], [$with_dracutmodulesdir/70tpm2-totp])]) 121 | AC_CONFIG_FILES([dist/dracut/module-setup.sh]) 122 | 123 | AC_CHECK_PROG([lsinitramfs], [lsinitramfs], [yes]) 124 | AC_ARG_WITH([initramfstoolsdir], 125 | AS_HELP_STRING([--with-initramfstoolsdir=DIR], [directory for initramfs-tools scripts]),, 126 | [AS_IF([test "x$lsinitramfs" = xyes], [with_initramfstoolsdir=$sysconfdir/initramfs-tools])]) 127 | AM_CONDITIONAL(HAVE_INITRAMFSTOOLS, [test -n "$with_initramfstoolsdir" -a "x$with_initramfstoolsdir" != xno]) 128 | AM_COND_IF([HAVE_INITRAMFSTOOLS], 129 | [AC_SUBST([initramfstools_hooksdir], [$with_initramfstoolsdir/hooks]) 130 | AC_SUBST([initramfstools_scriptsdir], [$with_initramfstoolsdir/scripts/init-premount]) 131 | ]) 132 | AC_CONFIG_FILES([dist/initramfs-tools/hooks/tpm2-totp]) 133 | 134 | AC_CHECK_PROG([mkinitcpio], [mkinitcpio], [yes]) 135 | AC_ARG_WITH([mkinitcpiodir], 136 | AS_HELP_STRING([--with-mkinitcpiodir=DIR], [directory for mkinitcpio hooks]),, 137 | [AS_IF([test "x$mkinitcpio" = xyes], [with_mkinitcpiodir=$sysconfdir/initcpio])]) 138 | AM_CONDITIONAL(HAVE_MKINITCPIO, [test -n "$with_mkinitcpiodir" -a "x$with_mkinitcpiodir" != xno]) 139 | AM_COND_IF([HAVE_MKINITCPIO], 140 | [AC_SUBST([initcpio_installdir], [$with_mkinitcpiodir/install]) 141 | AC_SUBST([initcpio_hooksdir], [$with_mkinitcpiodir/hooks]) 142 | ]) 143 | AC_CONFIG_FILES([dist/initcpio/install/tpm2-totp dist/initcpio/install/plymouth-tpm2-totp]) 144 | 145 | AC_ARG_WITH([udevdir], 146 | AS_HELP_STRING([--with-udevdir=DIR], [udev directory]),, 147 | [PKG_CHECK_VAR([with_udevdir], [udev], [udevdir],, [with_udevdir="$libdir/udev"])]) 148 | AC_SUBST([UDEVDIR], [$with_udevdir]) 149 | 150 | AC_ARG_WITH([systemdsystemunitdir], 151 | AS_HELP_STRING([--with-systemdsystemunit=DIR], [systemd system unit directory]),, 152 | [PKG_CHECK_VAR([with_systemdsystemunitdir], [systemd], [systemdsystemunitdir],, 153 | [with_systemdsystemunitdir="$libdir/systemd/system"])]) 154 | AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) 155 | 156 | AC_CONFIG_FILES([dist/initcpio/install/sd-tpm2-totp dist/tpm2-totp.service dist/tpm2-totp.timer]) 157 | 158 | AC_ARG_ENABLE([plymouth], 159 | AS_HELP_STRING([--disable-plymouth], [Disable plymouth support])) 160 | AS_IF([test "x$enable_plymouth" != "xno"], 161 | [PKG_CHECK_MODULES([PLY_BOOT_CLIENT], [ply-boot-client], 162 | [have_plymouth=yes], [have_plymouth=no])], 163 | [have_plymouth=no]) 164 | AM_CONDITIONAL([HAVE_PLYMOUTH], [test "x$have_plymouth" = "xyes"]) 165 | AM_COND_IF([HAVE_PLYMOUTH], 166 | [PKG_CHECK_VAR([PLYMOUTHPLUGINSDIR], [ply-splash-core], [pluginsdir]) 167 | AC_SUBST([PLYMOUTHPLUGINSDIR]) 168 | ], 169 | [AS_IF([test "x$enable_plymouth" = "xyes"], 170 | [AC_MSG_ERROR([plymouth requested but not found])]) 171 | ]) 172 | 173 | AC_CONFIG_FILES([dist/initcpio/install/sd-plymouth-tpm2-totp dist/plymouth-tpm2-totp.service]) 174 | 175 | AC_ARG_ENABLE([integration], 176 | [AS_HELP_STRING([--enable-integration], 177 | [build integration tests against TPM])],, 178 | [enable_integration=no]) 179 | AM_CONDITIONAL([INTEGRATION], [test "x$enable_integration" != xno]) 180 | AS_IF([test "x$enable_integration" != xno], 181 | [PKG_CHECK_MODULES([OATH],[liboath]) 182 | AC_CHECK_PROG([swtpm], [swtpm], [yes]) 183 | AC_CHECK_PROG([tpm_server], [tpm_server], [yes]) 184 | AS_IF([test "x$swtpm" != xyes && test "x$tpm_server" != xyes], 185 | [AC_MSG_ERROR([Integration tests require either the swtpm or the tpm_server executable])]) 186 | AC_CHECK_PROG([realpath], [realpath], [yes]) 187 | AS_IF([test "x$realpath" != xyes], 188 | [AC_MSG_ERROR([Integration tests require the realpath executable])]) 189 | AC_CHECK_PROG([ss], [ss], [yes]) 190 | AS_IF([test "x$ss" != xyes], 191 | [AC_MSG_ERROR([Integration tests require the ss executable])]) 192 | 193 | AM_COND_IF([HAVE_PLYMOUTH], 194 | [AC_CHECK_PROG([plymouthd], [plymouthd], [yes]) 195 | AS_IF([test "x$plymouthd" != xyes], 196 | [AC_MSG_ERROR([Integration tests require the plymouthd executable])]) 197 | AC_CHECK_PROG([fakeroot], [fakeroot], [yes]) 198 | AS_IF([test "x$fakeroot" != xyes], 199 | [AC_MSG_WARN([Executable fakeroot not found, integration tests must be run as root])]) 200 | AC_CHECK_PROG([pgrep], [pgrep], [yes]) 201 | AS_IF([test "x$pgrep" != xyes], 202 | [AC_MSG_ERROR([Integration tests require the pgrep executable])]) 203 | AC_CHECK_PROG([timeout], [timeout], [yes]) 204 | AS_IF([test "x$timeout" != xyes], 205 | [AC_MSG_ERROR([Integration tests require the timeout executable])]) 206 | ]) 207 | ]) 208 | 209 | AC_OUTPUT 210 | 211 | AC_MSG_RESULT([ 212 | $PACKAGE_NAME $VERSION 213 | doxygen: $DX_DOXYGEN 214 | pandoc: $PANDOC 215 | dracut: $with_dracutmodulesdir 216 | initramfs-tools: $with_initramfstoolsdir 217 | mkinitcpio: $with_mkinitcpiodir 218 | plymouth: $have_plymouth 219 | ]) 220 | -------------------------------------------------------------------------------- /dist/dracut/README: -------------------------------------------------------------------------------- 1 | This dracut module displays a time-based one-time password (TOTP) sealed to a 2 | Trusted Platform Module (TPM) to ensure that the boot process has not been 3 | tampered with. To set this up, a secret needs to be generated first and sealed 4 | to the TPM using 'tpm2-totp init'. 5 | 6 | This stores the secret in the TPM and displays it to the user so that it can 7 | be recorded on a different device (e.g. a TOTP app). When the hook is run, the 8 | TOTP is calculated and displayed together with the current time so that it can 9 | be compared with the output of the second device. This will only be successful 10 | and show a matching output if the boot process has not changed (new UEFI 11 | firmware, different boot loader, ...). 12 | 13 | When using a custom NV index with the '--nvindex index' option of tpm2-totp, 14 | this index needs to be specified as 'rd.tpm2totp.nvindex=index' on the kernel 15 | command line. 16 | 17 | Note that calculating the TOTP requires some entropy, which might be scarce 18 | directly after startup. If the boot process appears to be stuck, it might help 19 | to press some random keys to gather more entropy. A better alternative on modern 20 | processors is to enable the use of the hardware random number generator (RNG) 21 | by adding 'random.trust_cpu=on' to the kernel command line or by loading the 22 | 'rngd' dracut module. 23 | -------------------------------------------------------------------------------- /dist/dracut/cleanup-tpm2-totp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | kill "$show_tpm2_totp_pid" 2>/dev/null 3 | -------------------------------------------------------------------------------- /dist/dracut/module-setup.sh.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check() { 4 | if [ -n "$hostonly" ]; then 5 | if tpm2-totp show >/dev/null 2>&1; then 6 | return 0 7 | else 8 | dinfo "dracut module 'tpm2-totp' will not be installed because no TOTP is configured; run 'tpm2-totp init'!" 9 | fi 10 | fi 11 | return 255 12 | } 13 | 14 | install() { 15 | inst_libdir_file 'libtss2-tcti-device.so*' 16 | if dracut_module_included "plymouth" && \ 17 | find_binary @HELPERSDIR@/plymouth-tpm2-totp; then 18 | inst @HELPERSDIR@/plymouth-tpm2-totp /bin/show-tpm2-totp 19 | inst_library @PLYMOUTHPLUGINSDIR@/label.so 20 | inst_simple "$(fc-match --format '%{file}')" 21 | else 22 | inst @HELPERSDIR@/show-tpm2-totp /bin/show-tpm2-totp 23 | inst tpm2-totp 24 | inst date 25 | inst_hook cleanup 70 "$moddir/cleanup-tpm2-totp.sh" 26 | fi 27 | inst_hook pre-udev 70 "$moddir/show-tpm2-totp.sh" 28 | dracut_need_initqueue 29 | } 30 | 31 | installkernel() { 32 | instmods =drivers/char/tpm 33 | } 34 | -------------------------------------------------------------------------------- /dist/dracut/show-tpm2-totp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /lib/dracut-lib.sh 3 | nvindex="$(getarg rd.tpm2-totp.nvindex)" 4 | printf 'KERNEL=="tpm0", RUN+="/sbin/initqueue --settled --onetime /bin/show-tpm2-totp %s & show_tpm2_totp_pid=$$!"\n' "${nvindex:+--nvindex "$nvindex"}" > /etc/udev/rules.d/80-tpm2-totp.rules 5 | unset nvindex 6 | -------------------------------------------------------------------------------- /dist/initcpio/hooks/plymouth-tpm2-totp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ash 2 | 3 | run_hook() { 4 | plymouth-tpm2-totp ${tpm2_totp_nvindex:+--nvindex "$tpm2_totp_nvindex"} & 5 | } 6 | 7 | # vim: set ft=sh ts=4 sw=4 et: 8 | -------------------------------------------------------------------------------- /dist/initcpio/hooks/tpm2-totp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ash 2 | 3 | run_hook() { 4 | show-tpm2-totp ${tpm2_totp_nvindex:+--nvindex "$tpm2_totp_nvindex"} & 5 | show_tpm2_totp_pid=$! 6 | } 7 | 8 | run_cleanuphook() { 9 | kill "$show_tpm2_totp_pid" 2>/dev/null 10 | } 11 | 12 | # vim: set ft=sh ts=4 sw=4 et: 13 | -------------------------------------------------------------------------------- /dist/initcpio/install/plymouth-tpm2-totp.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | build() { 4 | local mod 5 | 6 | if [[ $TPM_MODULES ]]; then 7 | for mod in $TPM_MODULES; do 8 | add_module "$mod" 9 | done 10 | else 11 | add_all_modules /tpm/ 12 | fi 13 | 14 | add_binary @PLYMOUTHPLUGINSDIR@/label.so 15 | add_file "$(fc-match --format '%{file}')" 16 | 17 | add_binary @HELPERSDIR@/plymouth-tpm2-totp /usr/bin/plymouth-tpm2-totp 18 | add_binary @TSS2_TCTI_DEVICE_LIBDIR@/libtss2-tcti-device.so.0 19 | 20 | add_runscript 21 | } 22 | 23 | help() { 24 | cat </dev/null; then \ 179 | echo "$$x already includes git.mk"; \ 180 | else \ 181 | failed=; \ 182 | echo "Updating $$x"; \ 183 | { cat $$x; \ 184 | echo ''; \ 185 | echo '-include $$(top_srcdir)/git.mk'; \ 186 | } > $$x.tmp || failed=1; \ 187 | if test x$$failed = x; then \ 188 | mv $$x.tmp $$x || failed=1; \ 189 | fi; \ 190 | if test x$$failed = x; then : else \ 191 | echo "Failed updating $$x"; >&2 \ 192 | any_failed=1; \ 193 | fi; \ 194 | fi; done; test -z "$$any_failed" 195 | 196 | git-mk-update: 197 | wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk 198 | 199 | .PHONY: git-all git-mk-install git-mk-update 200 | 201 | 202 | 203 | ############################################################################### 204 | # Actual .gitignore generation: 205 | ############################################################################### 206 | 207 | $(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk $(top_srcdir)/configure.ac 208 | @echo "git.mk: Generating $@" 209 | @{ \ 210 | if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \ 211 | for x in \ 212 | $(DOC_MODULE)-decl-list.txt \ 213 | $(DOC_MODULE)-decl.txt \ 214 | tmpl/$(DOC_MODULE)-unused.sgml \ 215 | "tmpl/*.bak" \ 216 | $(REPORT_FILES) \ 217 | $(DOC_MODULE).pdf \ 218 | xml html \ 219 | ; do echo "/$$x"; done; \ 220 | FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \ 221 | case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \ 222 | if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-types"; then \ 223 | echo "/$(DOC_MODULE).types"; \ 224 | fi; \ 225 | if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-sections"; then \ 226 | echo "/$(DOC_MODULE)-sections.txt"; \ 227 | fi; \ 228 | if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ 229 | for x in \ 230 | $(SETUP_FILES) \ 231 | $(DOC_MODULE).types \ 232 | ; do echo "/$$x"; done; \ 233 | fi; \ 234 | fi; \ 235 | if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \ 236 | for lc in $(DOC_LINGUAS); do \ 237 | for x in \ 238 | $(if $(DOC_MODULE),$(DOC_MODULE).xml) \ 239 | $(DOC_PAGES) \ 240 | $(DOC_INCLUDES) \ 241 | ; do echo "/$$lc/$$x"; done; \ 242 | done; \ 243 | for x in \ 244 | $(_DOC_OMF_ALL) \ 245 | $(_DOC_DSK_ALL) \ 246 | $(_DOC_HTML_ALL) \ 247 | $(_DOC_MOFILES) \ 248 | $(DOC_H_FILE) \ 249 | "*/.xml2po.mo" \ 250 | "*/*.omf.out" \ 251 | ; do echo /$$x; done; \ 252 | fi; \ 253 | if test "x$(HOTDOC)" = x; then :; else \ 254 | $(foreach project, $(HOTDOC_PROJECTS),echo "/$(call HOTDOC_TARGET,$(project))"; \ 255 | echo "/$(shell $(call HOTDOC_PROJECT_COMMAND,$(project)) --get-conf-path output)" ; \ 256 | echo "/$(shell $(call HOTDOC_PROJECT_COMMAND,$(project)) --get-private-folder)" ; \ 257 | ) \ 258 | for x in \ 259 | .hotdoc.d \ 260 | ; do echo "/$$x"; done; \ 261 | fi; \ 262 | if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \ 263 | for lc in $(HELP_LINGUAS); do \ 264 | for x in \ 265 | $(HELP_FILES) \ 266 | "$$lc.stamp" \ 267 | "$$lc.mo" \ 268 | ; do echo "/$$lc/$$x"; done; \ 269 | done; \ 270 | fi; \ 271 | if test "x$(gsettings_SCHEMAS)" = x; then :; else \ 272 | for x in \ 273 | $(gsettings_SCHEMAS:.xml=.valid) \ 274 | $(gsettings__enum_file) \ 275 | ; do echo "/$$x"; done; \ 276 | fi; \ 277 | if test "x$(appdata_XML)" = x; then :; else \ 278 | for x in \ 279 | $(appdata_XML:.xml=.valid) \ 280 | ; do echo "/$$x"; done; \ 281 | fi; \ 282 | if test "x$(appstream_XML)" = x; then :; else \ 283 | for x in \ 284 | $(appstream_XML:.xml=.valid) \ 285 | ; do echo "/$$x"; done; \ 286 | fi; \ 287 | if test -f $(srcdir)/po/Makefile.in.in; then \ 288 | for x in \ 289 | ABOUT-NLS \ 290 | po/Makefile.in.in \ 291 | po/Makefile.in.in~ \ 292 | po/Makefile.in \ 293 | po/Makefile \ 294 | po/Makevars.template \ 295 | po/POTFILES \ 296 | po/Rules-quot \ 297 | po/stamp-it \ 298 | po/stamp-po \ 299 | po/.intltool-merge-cache \ 300 | "po/*.gmo" \ 301 | "po/*.header" \ 302 | "po/*.mo" \ 303 | "po/*.sed" \ 304 | "po/*.sin" \ 305 | po/$(GETTEXT_PACKAGE).pot \ 306 | intltool-extract.in \ 307 | intltool-merge.in \ 308 | intltool-update.in \ 309 | ; do echo "/$$x"; done; \ 310 | fi; \ 311 | if test -f $(srcdir)/configure; then \ 312 | for x in \ 313 | autom4te.cache \ 314 | configure \ 315 | config.h \ 316 | stamp-h1 \ 317 | libtool \ 318 | config.lt \ 319 | ; do echo "/$$x"; done; \ 320 | fi; \ 321 | if test "x$(DEJATOOL)" = x; then :; else \ 322 | for x in \ 323 | $(DEJATOOL) \ 324 | ; do echo "/$$x.sum"; echo "/$$x.log"; done; \ 325 | echo /site.exp; \ 326 | fi; \ 327 | if test "x$(am__dirstamp)" = x; then :; else \ 328 | echo "$(am__dirstamp)"; \ 329 | fi; \ 330 | if test "x$(findstring libtool,$(LTCOMPILE))" = x -a "x$(findstring libtool,$(LTCXXCOMPILE))" = x -a "x$(GTKDOC_RUN)" = x; then :; else \ 331 | for x in \ 332 | "*.lo" \ 333 | ".libs" "_libs" \ 334 | ; do echo "$$x"; done; \ 335 | fi; \ 336 | for x in \ 337 | .gitignore \ 338 | $(GITIGNOREFILES) \ 339 | $(CLEANFILES) \ 340 | $(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \ 341 | $(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \ 342 | $(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \ 343 | so_locations \ 344 | $(MOSTLYCLEANFILES) \ 345 | $(TEST_LOGS) \ 346 | $(TEST_LOGS:.log=.trs) \ 347 | $(TEST_SUITE_LOG) \ 348 | $(TESTS:=.test) \ 349 | "*.gcda" \ 350 | "*.gcno" \ 351 | $(DISTCLEANFILES) \ 352 | $(am__CONFIG_DISTCLEAN_FILES) \ 353 | $(CONFIG_CLEAN_FILES) \ 354 | TAGS ID GTAGS GRTAGS GSYMS GPATH tags \ 355 | "*.tab.c" \ 356 | $(MAINTAINERCLEANFILES) \ 357 | $(BUILT_SOURCES) \ 358 | $(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \ 359 | $(filter %_vala.stamp,$(DIST_COMMON)) \ 360 | $(filter %.vapi,$(DIST_COMMON)) \ 361 | $(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \ 362 | Makefile \ 363 | Makefile.in \ 364 | "*.orig" \ 365 | "*.rej" \ 366 | "*.bak" \ 367 | "*~" \ 368 | ".*.sw[nop]" \ 369 | ".dirstamp" \ 370 | ; do echo "/$$x"; done; \ 371 | for x in \ 372 | "*.$(OBJEXT)" \ 373 | $(DEPDIR) \ 374 | ; do echo "$$x"; done; \ 375 | } | \ 376 | sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \ 377 | sed 's@/[.]/@/@g' | \ 378 | LC_ALL=C sort | uniq > $@.tmp && \ 379 | mv $@.tmp $@; 380 | 381 | all: $(srcdir)/.gitignore gitignore-recurse-maybe 382 | gitignore: $(srcdir)/.gitignore gitignore-recurse 383 | 384 | gitignore-recurse-maybe: 385 | @for subdir in $(DIST_SUBDIRS); do \ 386 | case " $(SUBDIRS) " in \ 387 | *" $$subdir "*) :;; \ 388 | *) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \ 389 | esac; \ 390 | done 391 | gitignore-recurse: 392 | @for subdir in $(DIST_SUBDIRS); do \ 393 | test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \ 394 | done 395 | 396 | maintainer-clean: gitignore-clean 397 | gitignore-clean: 398 | -rm -f $(srcdir)/.gitignore 399 | 400 | .PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe 401 | -------------------------------------------------------------------------------- /include/tpm2-totp.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-3-Clause */ 2 | /******************************************************************************* 3 | * Copyright 2018, Fraunhofer SIT 4 | * All rights reserved. 5 | *******************************************************************************/ 6 | 7 | #ifndef TPM2_TOTP_H 8 | #define TPM2_TOTP_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define TPM2TOTP_BANK_SHA1 (1 << 0) 15 | #define TPM2TOTP_BANK_SHA256 (1 << 1) 16 | #define TPM2TOTP_BANK_SHA384 (1 << 2) 17 | 18 | int 19 | tpm2totp_generateKey(uint32_t pcrs, uint32_t banks, const char *password, 20 | TSS2_TCTI_CONTEXT *tcti_context, 21 | uint8_t **secret, size_t *secret_size, 22 | uint8_t **keyBlob, size_t *keyBlob_size); 23 | 24 | int 25 | tpm2totp_reseal(const uint8_t *keyBlob, size_t keyBlob_size, 26 | const char *password, uint32_t pcrs, uint32_t banks, 27 | TSS2_TCTI_CONTEXT *tcti_context, 28 | uint8_t **newBlob, size_t *newBlob_size); 29 | 30 | int 31 | tpm2totp_storeKey_nv(const uint8_t *keyBlob, size_t keyBlob_size, uint32_t nv, 32 | TSS2_TCTI_CONTEXT *tcti_context); 33 | 34 | int 35 | tpm2totp_loadKey_nv(uint32_t nv, TSS2_TCTI_CONTEXT *tcti_context, 36 | uint8_t **keyBlob, size_t *keyBlob_size); 37 | 38 | int 39 | tpm2totp_deleteKey_nv(uint32_t nv, TSS2_TCTI_CONTEXT *tcti_context); 40 | 41 | int 42 | tpm2totp_calculate(const uint8_t *keyBlob, size_t keyBlob_size, 43 | TSS2_TCTI_CONTEXT *tcti_context, 44 | time_t *now, uint64_t *otp); 45 | 46 | int 47 | tpm2totp_getSecret(const uint8_t *keyBlob, size_t keyBlob_size, 48 | const char *password, TSS2_TCTI_CONTEXT *tcti_context, 49 | uint8_t **secret, size_t *secret_size); 50 | 51 | #endif /* TPM2_TOTP_H */ 52 | -------------------------------------------------------------------------------- /m4/flags.m4: -------------------------------------------------------------------------------- 1 | dnl AX_ADD_COMPILER_FLAG: 2 | dnl A macro to add a CFLAG to the EXTRA_CFLAGS variable. This macro will 3 | dnl check to be sure the compiler supports the flag. Flags can be made 4 | dnl mandatory (configure will fail). 5 | dnl $1: C compiler flag to add to EXTRA_CFLAGS. 6 | dnl $2: Set to "required" to cause configure failure if flag not supported. 7 | AC_DEFUN([AX_ADD_COMPILER_FLAG],[ 8 | AX_CHECK_COMPILE_FLAG([$1],[ 9 | EXTRA_CFLAGS="$EXTRA_CFLAGS $1" 10 | AC_SUBST([EXTRA_CFLAGS])],[ 11 | AS_IF([test x$2 != xrequired],[ 12 | AC_MSG_WARN([Optional CFLAG "$1" not supported by your compiler, continuing.])],[ 13 | AC_MSG_ERROR([Required CFLAG "$1" not supported by your compiler, aborting.])] 14 | )],[ 15 | -Wall -Werror] 16 | )] 17 | ) 18 | dnl AX_ADD_PREPROC_FLAG: 19 | dnl Add the provided preprocessor flag to the EXTRA_CFLAGS variable. This 20 | dnl macro will check to be sure the preprocessor supports the flag. 21 | dnl The flag can be made mandatory by providing the string 'required' as 22 | dnl the second parameter. 23 | dnl $1: Preprocessor flag to add to EXTRA_CFLAGS. 24 | dnl $2: Set to "required" t ocause configure failure if preprocesor flag 25 | dnl is not supported. 26 | AC_DEFUN([AX_ADD_PREPROC_FLAG],[ 27 | AX_CHECK_PREPROC_FLAG([$1],[ 28 | EXTRA_CFLAGS="$EXTRA_CFLAGS $1" 29 | AC_SUBST([EXTRA_CFLAGS])],[ 30 | AS_IF([test x$2 != xrequired],[ 31 | AC_MSG_WARN([Optional preprocessor flag "$1" not supported by your compiler, continuing.])],[ 32 | AC_MSG_ERROR([Required preprocessor flag "$1" not supported by your compiler, aborting.])] 33 | )],[ 34 | -Wall -Werror] 35 | )] 36 | ) 37 | dnl AX_ADD_LINK_FLAG: 38 | dnl A macro to add a LDLAG to the EXTRA_LDFLAGS variable. This macro will 39 | dnl check to be sure the linker supports the flag. Flags can be made 40 | dnl mandatory (configure will fail). 41 | dnl $1: linker flag to add to EXTRA_LDFLAGS. 42 | dnl $2: Set to "required" to cause configure failure if flag not supported. 43 | AC_DEFUN([AX_ADD_LINK_FLAG],[ 44 | AX_CHECK_LINK_FLAG([$1],[ 45 | EXTRA_LDFLAGS="$EXTRA_LDFLAGS $1" 46 | AC_SUBST([EXTRA_LDFLAGS])],[ 47 | AS_IF([test x$2 != xrequired],[ 48 | AC_MSG_WARN([Optional LDFLAG "$1" not supported by your linker, continuing.])],[ 49 | AC_MSG_ERROR([Required LDFLAG "$1" not supported by your linker, aborting.])] 50 | )] 51 | )] 52 | ) 53 | -------------------------------------------------------------------------------- /man/tpm2-totp.1.md: -------------------------------------------------------------------------------- 1 | % tpm2-totp(1) tpm2-totp | General Commands Manual 2 | % 3 | % DECEMBER 2018 4 | 5 | # NAME 6 | **tpm2-totp**(1) -- initialize or calculate and show TPM based TOTPs 7 | 8 | # SYNOPSIS 9 | 10 | **tpm2-totp** [*options*] 11 | 12 | # DESCRIPTION 13 | 14 | **tpm2-totp** creates a key inside a TPM 2.0 that can be used to calculate 15 | time-based onetime passwords (TOTPs) to demonstrate to the user that a platform 16 | was not altered during his/her abscense and thus still trustworthy. 17 | 18 | # ARGUMENTS 19 | 20 | The `tpm2-totp` command expects one of five command and provides a set of 21 | options. 22 | 23 | ## COMMANDS 24 | 25 | * `init`: 26 | Generate and store a new TOTP secret. 27 | Possible options: `-b`, `-l`, `-N`, `-p`, `-P`, `-T` 28 | 29 | * `show`: 30 | Calculate and show a TOTP value. 31 | Possible options: `-N`, `-t`, `-T` 32 | 33 | * `reseal`: 34 | Reseal TOTP secret to new PCRs, banks or values. 35 | Possible options: `-b`, `-N`, `-p`, `-P` (required), `-T` 36 | 37 | * `recover`: 38 | Recover the TOTP secret and display it again. 39 | Possible Options: `-N`, `-P` (required), `-T` 40 | 41 | * `clean`: 42 | Delete the consumed NV index. 43 | Possible Options: `-N`, `-T` 44 | 45 | ## OPTIONS 46 | 47 | * `-b [,[,...]]`, `--banks [,[,...]]`: 48 | Selected PCR banks (default: SHA1,SHA256) 49 | 50 | * `-h`, `--help`: 51 | Print help 52 | 53 | * `-l`, `--label`: 54 | Label to use for display in the TOTP authenticator app (default: TPM2-TOTP) 55 | 56 | * `-N `, `--nvindex `: 57 | TPM NV index to store data (default: 0x018094AF) 58 | 59 | * `-p [,[,...]]`, `--pcrs [,[,...]]`: 60 | Selected PCR registers (default: 0,2,4,6) 61 | 62 | * `-P `, `--password `: 63 | Password for the secret (default: none) (commands: init, recover, reseal) 64 | Read from stdin if `-` (recommended). 65 | Must not contain `\0`. 66 | 67 | * `-t`, `--time`: 68 | Display the date/time of the TOTP calculation (commands: show) 69 | 70 | * `-T [:]`, `--tcti [:]`: 71 | Select the TCTI to use. *tcti-name* is the name of the TCTI library. 72 | If present, the configuration string *tcti-config* is passed verbatim to the 73 | chosen TCTI library. 74 | 75 | The TCTI can additionally be specified using the environment variable 76 | `TPM2TOTP_TCTI`. If both the command line option and the environment 77 | variable are present, the command line option is used. 78 | 79 | If no TCTI is specified, the default TCTI configured on the system is used. 80 | 81 | * `-v`, `--verbose`: 82 | Print verbose messages 83 | 84 | # EXAMPLES 85 | 86 | ## Setup 87 | The TOTP secret can be initialized with and without password. It is recommended to 88 | set a password `-P` in order to enable recovery options. Further, it is strongly 89 | recommended to provide the password via stdin, rather than directly as a 90 | command line option, to protect it from other processes, shell history, etc. 91 | Also the PCRs and PCR banks can be selected `-p` and `-b`. Default values are 92 | PCRs `0,2,4` and banks `SHA1, SHA256`. 93 | ``` 94 | tpm2-totp init 95 | 96 | tpm2-totp -P - init 97 | verysecret 98 | # or (recommended) 99 | gpg --decrypt /path/to/password.gpg | tpm2-totp -P - init 100 | # or (discouraged) 101 | tpm2-totp -P verysecret init 102 | 103 | tpm2-totp -P - -p 0,1,2,3,4,5,6 init 104 | tpm2-totp -p 0,1,2,3,4,5,6 -b SHA1,SHA256 init 105 | ``` 106 | 107 | ## Boot 108 | During boot the TOTP value for the current time, together with the current time 109 | should be shown to the user, e.g. using plymouth from mkinitrd or from dracut. 110 | The command to be executed is: 111 | ``` 112 | tpm2-totp show 113 | tpm2-totp -t show 114 | ``` 115 | 116 | ## Recovery 117 | In order to recover the QR code: 118 | ``` 119 | tpm2-totp -P - recover 120 | ``` 121 | In order to reseal the secret: 122 | ``` 123 | tpm2-totp -P - reseal 124 | tpm2-totp -P - -p 1,3,5,6 reseal 125 | ``` 126 | 127 | ## Deletion 128 | In order to delete the created NV index: 129 | ``` 130 | tpm2-totp clean 131 | ``` 132 | 133 | ## NV index 134 | All command additionally take the `-N` option to specify the NV index to be 135 | used. By default, 0x018094AF is used and recommended. 136 | ``` 137 | tpm2-totp -N 0x01800001 -P - init 138 | tpm2-totp -N 0x01800001 show 139 | tpm2-totp -N 0x01800001 -P - recover 140 | tpm2-totp -N 0x01800001 -P - reseal 141 | ``` 142 | 143 | ## TCTI configuration 144 | All commands take the `-T` option or the `TPM2TOTP_TCTI` environment variable 145 | to specify the TCTI to be used. If the TCTI is not specified explicitly, the 146 | default TCTI configured on the system is used. To e.g. use the TPM simulator 147 | bound to a given port, use 148 | ``` 149 | tpm2-totp -T mssim:port=2321 init 150 | ``` 151 | 152 | # RETURNS 153 | 154 | 0 on success or 1 on failure. 155 | 156 | # AUTHOR 157 | 158 | Written by Andreas Fuchs. 159 | 160 | # COPYRIGHT 161 | 162 | tpm2tss is Copyright (C) 2018 Fraunhofer SIT. License BSD 3-clause. 163 | 164 | # SEE ALSO 165 | 166 | tpm2totp_generateKey(3) 167 | -------------------------------------------------------------------------------- /src/libtpm2-totp.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-3-Clause */ 2 | /******************************************************************************* 3 | * Copyright 2018, Fraunhofer SIT 4 | * All rights reserved. 5 | *******************************************************************************/ 6 | 7 | #define _DEFAULT_SOURCE 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | /* RFC 6238 TOTP defines */ 21 | #define TIMESTEPSIZE 30 22 | #define SECRETLEN 20 23 | 24 | #define DEFAULT_PCRS (0b000000000000000000010101) 25 | #define DEFAULT_BANKS (0b11) 26 | #define DEFAULT_NV 0x018094AF 27 | 28 | const TPM2B_DIGEST ownerauth = { .size = 0 }; 29 | 30 | #ifdef NDEBUG 31 | #define dbg(m, ...) 32 | #else 33 | #define dbg(m, ...) fprintf(stderr, m "\n", ##__VA_ARGS__) 34 | #endif 35 | 36 | #define chkrc(rc, cmd) if (rc != TSS2_RC_SUCCESS) { cmd; } 37 | 38 | #define TPM2B_PUBLIC_PRIMARY_TEMPLATE { .size = 0, \ 39 | .publicArea = { \ 40 | .type = TPM2_ALG_ECC, \ 41 | .nameAlg = TPM2_ALG_SHA256, \ 42 | .objectAttributes = ( TPMA_OBJECT_USERWITHAUTH | \ 43 | TPMA_OBJECT_RESTRICTED | \ 44 | TPMA_OBJECT_DECRYPT | \ 45 | TPMA_OBJECT_NODA | \ 46 | TPMA_OBJECT_FIXEDTPM | \ 47 | TPMA_OBJECT_FIXEDPARENT | \ 48 | TPMA_OBJECT_SENSITIVEDATAORIGIN ), \ 49 | .authPolicy = { .size = 0, }, \ 50 | .parameters.eccDetail = { \ 51 | .symmetric = { .algorithm = TPM2_ALG_AES, \ 52 | .keyBits.aes = 128, .mode.aes = TPM2_ALG_CFB, }, \ 53 | .scheme = { .scheme = TPM2_ALG_NULL, .details = {} }, \ 54 | .curveID = TPM2_ECC_NIST_P256, \ 55 | .kdf = { .scheme = TPM2_ALG_NULL, .details = {} }, }, \ 56 | .unique.ecc = { .x.size = 0, .y.size = 0 } \ 57 | } } 58 | 59 | #define TPM2B_PUBLIC_KEY_TEMPLATE_UNSEAL { .size = 0, \ 60 | .publicArea = { \ 61 | .type = TPM2_ALG_KEYEDHASH, \ 62 | .nameAlg = TPM2_ALG_SHA256, \ 63 | .objectAttributes = ( TPMA_OBJECT_USERWITHAUTH ), \ 64 | .authPolicy = { .size = 0, .buffer = { 0 } }, \ 65 | .parameters.keyedHashDetail.scheme = { .scheme = TPM2_ALG_NULL, \ 66 | .details = { .hmac = { .hashAlg = TPM2_ALG_SHA1 } } }, \ 67 | .unique.keyedHash = { .size = 0, .buffer = { 0 }, }, \ 68 | } } 69 | 70 | #define TPM2B_PUBLIC_KEY_TEMPLATE_HMAC { .size = 0, \ 71 | .publicArea = { \ 72 | .type = TPM2_ALG_KEYEDHASH, \ 73 | .nameAlg = TPM2_ALG_SHA256, \ 74 | .objectAttributes = ( TPMA_OBJECT_SIGN_ENCRYPT ), \ 75 | .authPolicy = { .size = 0, .buffer = { 0 } }, \ 76 | .parameters.keyedHashDetail.scheme = { .scheme = TPM2_ALG_HMAC, \ 77 | .details = { .hmac = { .hashAlg = TPM2_ALG_SHA1 } } }, \ 78 | .unique.keyedHash = { .size = 0, .buffer = { 0 }, }, \ 79 | } } 80 | 81 | #define TPM2B_SENSITIVE_CREATE_TEMPLATE { .size = 0, \ 82 | .sensitive = { \ 83 | .userAuth = { .size = 0, .buffer = { 0 } }, \ 84 | .data = { .size = 0, .buffer = { 0 } }, \ 85 | } }; 86 | 87 | TPM2B_PUBLIC primaryPublic = TPM2B_PUBLIC_PRIMARY_TEMPLATE; 88 | TPM2B_SENSITIVE_CREATE primarySensitive = TPM2B_SENSITIVE_CREATE_TEMPLATE; 89 | 90 | TPM2B_DATA allOutsideInfo = { .size = 0, }; 91 | TPML_PCR_SELECTION allCreationPCR = { .count = 0 }; 92 | 93 | TPM2B_AUTH emptyAuth = { .size = 0, }; 94 | 95 | /** @defgroup tpm2-totp libtpm2-totp 96 | * Attest the trustworthiness of a device against a human using time-based one-time passwords. 97 | * @{ 98 | */ 99 | 100 | /** Generate a key. 101 | * 102 | * @param[in] pcrs PCRs the key should be sealed against. 103 | * @param[in] banks PCR banks the key should be sealed against. 104 | * @param[in] password Optional password to recover or reseal the secret. 105 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 106 | * @param[out] secret Generated secret. 107 | * @param[out] secret_size Size of the secret. 108 | * @param[out] keyBlob Generated key. 109 | * @param[out] keyBlob_size Size of the generated key. 110 | * @retval 0 on success. 111 | * @retval -1 on undefined/general failure. 112 | * @retval TSS2_RC response code for failures relayed from the TSS library. 113 | */ 114 | int 115 | tpm2totp_generateKey(uint32_t pcrs, uint32_t banks, const char *password, 116 | TSS2_TCTI_CONTEXT *tcti_context, 117 | uint8_t **secret, size_t *secret_size, 118 | uint8_t **keyBlob, size_t *keyBlob_size) 119 | { 120 | if (secret == NULL || secret_size == NULL || 121 | keyBlob == NULL || keyBlob_size == NULL) { 122 | return -1; 123 | } 124 | 125 | TPM2B_DIGEST *t, *policyDigest; 126 | ESYS_CONTEXT *ctx = NULL; 127 | ESYS_TR primary, session; 128 | TSS2_RC rc; 129 | 130 | size_t off = 0; 131 | 132 | TPMT_SYM_DEF sym = {.algorithm = TPM2_ALG_AES, 133 | .keyBits = {.aes = 128}, 134 | .mode = {.aes = TPM2_ALG_CFB} 135 | }; 136 | 137 | TPM2B_PUBLIC keyInPublicHmac = TPM2B_PUBLIC_KEY_TEMPLATE_HMAC; 138 | TPM2B_PUBLIC keyInPublicSeal = TPM2B_PUBLIC_KEY_TEMPLATE_UNSEAL; 139 | TPM2B_SENSITIVE_CREATE keySensitive = TPM2B_SENSITIVE_CREATE_TEMPLATE; 140 | TPM2B_PUBLIC *keyPublicHmac = NULL; 141 | TPM2B_PRIVATE *keyPrivateHmac = NULL; 142 | TPM2B_PUBLIC *keyPublicSeal = NULL; 143 | TPM2B_PRIVATE *keyPrivateSeal = NULL; 144 | 145 | TPML_PCR_SELECTION *pcrcheck, pcrsel = { .count = 0 }; 146 | 147 | if (pcrs == 0) pcrs = DEFAULT_PCRS; 148 | if (banks == 0) banks = DEFAULT_BANKS; 149 | 150 | if ((banks & TPM2TOTP_BANK_SHA1)) { 151 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA1; 152 | pcrsel.count++; 153 | } 154 | if ((banks & TPM2TOTP_BANK_SHA256)) { 155 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA256; 156 | pcrsel.count++; 157 | } 158 | if ((banks & TPM2TOTP_BANK_SHA384)) { 159 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA384; 160 | pcrsel.count++; 161 | } 162 | 163 | for (size_t i = 0; i < pcrsel.count; i++) { 164 | pcrsel.pcrSelections[i].sizeofSelect = 3; 165 | pcrsel.pcrSelections[i].pcrSelect[0] = pcrs & 0xff; 166 | pcrsel.pcrSelections[i].pcrSelect[1] = pcrs >>8 & 0xff; 167 | pcrsel.pcrSelections[i].pcrSelect[2] = pcrs >>16 & 0xff; 168 | } 169 | 170 | *secret_size = 0; 171 | *secret = malloc(SECRETLEN); 172 | if (!*secret) { 173 | return -1; 174 | } 175 | 176 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 177 | chkrc(rc, goto error); 178 | 179 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 180 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 181 | 182 | while (*secret_size < SECRETLEN) { 183 | dbg("Calling Esys_GetRandom for %zu bytes", SECRETLEN - *secret_size); 184 | rc = Esys_GetRandom(ctx, 185 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 186 | SECRETLEN - *secret_size, &t); 187 | chkrc(rc, goto error); 188 | 189 | memcpy(&(*secret)[*secret_size], &t->buffer[0], t->size); 190 | *secret_size += t->size; 191 | free(t); 192 | } 193 | 194 | dbg("Calling Esys_CreatePrimary"); 195 | rc = Esys_CreatePrimary(ctx, ESYS_TR_RH_OWNER, 196 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 197 | &primarySensitive, &primaryPublic, 198 | &allOutsideInfo, &allCreationPCR, 199 | &primary, NULL, NULL, NULL, NULL); 200 | chkrc(rc, goto error); 201 | 202 | rc = Esys_PCR_Read(ctx, 203 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 204 | &pcrsel, NULL, &pcrcheck, NULL); 205 | chkrc(rc, goto error); 206 | 207 | if (pcrcheck->count == 0) { 208 | dbg("No active banks selected"); 209 | return -1; 210 | } 211 | free(pcrcheck); 212 | 213 | rc = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE, 214 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 215 | NULL, TPM2_SE_POLICY, &sym, TPM2_ALG_SHA256, 216 | &session); 217 | chkrc(rc, goto error); 218 | 219 | rc = Esys_PolicyPCR(ctx, session, 220 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 221 | NULL, &pcrsel); 222 | chkrc(rc, Esys_FlushContext(ctx, session); goto error); 223 | 224 | rc = Esys_PolicyGetDigest(ctx, session, 225 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 226 | &policyDigest); 227 | Esys_FlushContext(ctx, session); 228 | chkrc(rc, goto error); 229 | 230 | keyInPublicHmac.publicArea.authPolicy = *policyDigest; 231 | free(policyDigest); 232 | 233 | keySensitive.sensitive.data.size = *secret_size; 234 | memcpy(&keySensitive.sensitive.data.buffer[0], &(*secret)[0], 235 | *secret_size); 236 | 237 | rc = Esys_Create(ctx, primary, 238 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 239 | &keySensitive, &keyInPublicHmac, 240 | &allOutsideInfo, &allCreationPCR, 241 | &keyPrivateHmac, &keyPublicHmac, NULL, NULL, NULL); 242 | chkrc(rc, Esys_FlushContext(ctx, primary); goto error); 243 | 244 | if (password && strlen(password) > 0) { 245 | keySensitive.sensitive.userAuth.size = strlen(password); 246 | if (keySensitive.sensitive.userAuth.size) 247 | memcpy(&keySensitive.sensitive.userAuth.buffer[0], password, 248 | keySensitive.sensitive.userAuth.size); 249 | 250 | rc = Esys_Create(ctx, primary, 251 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 252 | &keySensitive, &keyInPublicSeal, 253 | &allOutsideInfo, &allCreationPCR, 254 | &keyPrivateSeal, &keyPublicSeal, NULL, NULL, NULL); 255 | chkrc(rc, Esys_FlushContext(ctx, primary); goto error); 256 | } 257 | 258 | Esys_FlushContext(ctx, primary); 259 | Esys_Finalize(&ctx); 260 | 261 | *keyBlob_size = 4 + 4; 262 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(keyPublicHmac, NULL, -1, keyBlob_size); 263 | chkrc(rc, goto error); 264 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(keyPrivateHmac, NULL, -1, keyBlob_size); 265 | chkrc(rc, goto error); 266 | if (password && strlen(password) > 0) { 267 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(keyPublicSeal, NULL, -1, keyBlob_size); 268 | chkrc(rc, goto error); 269 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(keyPrivateSeal, NULL, -1, keyBlob_size); 270 | chkrc(rc, goto error); 271 | } 272 | 273 | *keyBlob = malloc(*keyBlob_size); 274 | 275 | rc = Tss2_MU_UINT32_Marshal(pcrs, *keyBlob, *keyBlob_size, &off); 276 | chkrc(rc, goto error_marshall); 277 | rc = Tss2_MU_UINT32_Marshal(banks, *keyBlob, *keyBlob_size, &off); 278 | chkrc(rc, goto error_marshall); 279 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(keyPublicHmac, 280 | *keyBlob, *keyBlob_size, &off); 281 | chkrc(rc, goto error_marshall); 282 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(keyPrivateHmac, 283 | *keyBlob, *keyBlob_size, &off); 284 | chkrc(rc, goto error_marshall); 285 | if (password && strlen(password) > 0) { 286 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(keyPublicSeal, 287 | *keyBlob, *keyBlob_size, &off); 288 | chkrc(rc, goto error_marshall); 289 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(keyPrivateSeal, 290 | *keyBlob, *keyBlob_size, &off); 291 | chkrc(rc, goto error_marshall); 292 | } 293 | 294 | return 0; 295 | 296 | error_marshall: 297 | free(*keyBlob); 298 | *keyBlob = NULL; 299 | return (rc)? (int)rc : -1; 300 | 301 | error: 302 | free(keyPublicHmac); 303 | free(keyPrivateHmac); 304 | free(keyPublicSeal); 305 | free(keyPrivateSeal); 306 | Esys_Finalize(&ctx); 307 | free(*secret); 308 | *secret = NULL; 309 | *secret_size = 0; 310 | return (rc)? (int)rc : -1; 311 | } 312 | 313 | /** Reseal a key to new PCR values. 314 | * 315 | * @param[in] keyBlob Original key. 316 | * @param[in] keyBlob_size Size of the key. 317 | * @param[in] password Password of the key. 318 | * @param[in] pcrs PCRs the key should be sealed against. 319 | * @param[in] banks PCR banks the key should be sealed against. 320 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 321 | * @param[out] newBlob New key. 322 | * @param[out] newBlob_size Size of the new key. 323 | * @retval 0 on success. 324 | * @retval -1 on undefined/general failure. 325 | * @retval -10 on empty password. 326 | * @retval -20 when no password-protected recovery copy of the secret has been stored. 327 | * @retval TSS2_RC response code for failures relayed from the TSS library. 328 | */ 329 | int 330 | tpm2totp_reseal(const uint8_t *keyBlob, size_t keyBlob_size, 331 | const char *password, uint32_t pcrs, uint32_t banks, 332 | TSS2_TCTI_CONTEXT *tcti_context, 333 | uint8_t **newBlob, size_t *newBlob_size) 334 | { 335 | if (keyBlob == NULL || !password || newBlob == NULL || newBlob_size == NULL) { 336 | return -1; 337 | } 338 | if (!strlen(password)) { 339 | dbg("Password required."); 340 | return -10; 341 | } 342 | 343 | ESYS_CONTEXT *ctx = NULL; 344 | ESYS_TR primary = ESYS_TR_NONE, key, session; 345 | TSS2_RC rc; 346 | size_t off = 0; 347 | TPM2B_SENSITIVE_DATA *secret2b = NULL; 348 | TPM2B_AUTH auth; 349 | TPM2B_DIGEST *policyDigest; 350 | 351 | TPM2B_PUBLIC keyInPublicHmac = TPM2B_PUBLIC_KEY_TEMPLATE_HMAC; 352 | TPM2B_SENSITIVE_CREATE keySensitive = TPM2B_SENSITIVE_CREATE_TEMPLATE; 353 | TPM2B_PUBLIC keyPublicSeal = { .size = 0 }; 354 | TPM2B_PRIVATE keyPrivateSeal = { .size = 0 }; 355 | TPM2B_PUBLIC *keyPublicHmac = NULL; 356 | TPM2B_PRIVATE *keyPrivateHmac = NULL; 357 | 358 | TPML_PCR_SELECTION *pcrcheck, pcrsel = { .count = 0 }; 359 | 360 | TPMT_SYM_DEF sym = {.algorithm = TPM2_ALG_AES, 361 | .keyBits = {.aes = 128}, 362 | .mode = {.aes = TPM2_ALG_CFB} 363 | }; 364 | 365 | if (pcrs == 0) pcrs = DEFAULT_PCRS; 366 | if (banks == 0) banks = DEFAULT_BANKS; 367 | 368 | if ((banks & TPM2TOTP_BANK_SHA1)) { 369 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA1; 370 | pcrsel.count++; 371 | } 372 | if ((banks & TPM2TOTP_BANK_SHA256)) { 373 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA256; 374 | pcrsel.count++; 375 | } 376 | if ((banks & TPM2TOTP_BANK_SHA384)) { 377 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA384; 378 | pcrsel.count++; 379 | } 380 | 381 | for (size_t i = 0; i < pcrsel.count; i++) { 382 | pcrsel.pcrSelections[i].sizeofSelect = 3; 383 | pcrsel.pcrSelections[i].pcrSelect[0] = pcrs & 0xff; 384 | pcrsel.pcrSelections[i].pcrSelect[1] = pcrs >>8 & 0xff; 385 | pcrsel.pcrSelections[i].pcrSelect[2] = pcrs >>16 & 0xff; 386 | } 387 | 388 | auth.size = strlen(password); 389 | memcpy(&auth.buffer[0], password, auth.size); 390 | 391 | /* We skip over the pcrs and banks from NV because they are not trustworthy */ 392 | rc = Tss2_MU_UINT32_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 393 | chkrc(rc, goto error); 394 | rc = Tss2_MU_UINT32_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 395 | chkrc(rc, goto error); 396 | 397 | rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 398 | chkrc(rc, goto error); 399 | rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 400 | chkrc(rc, goto error); 401 | 402 | if (off == keyBlob_size) { 403 | dbg("No unseal blob included."); 404 | return -20; 405 | } 406 | 407 | rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(keyBlob, keyBlob_size, &off, 408 | &keyPublicSeal); 409 | chkrc(rc, goto error); 410 | rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(keyBlob, keyBlob_size, &off, 411 | &keyPrivateSeal); 412 | chkrc(rc, goto error); 413 | 414 | if (off != keyBlob_size) { 415 | dbg("bad blob size"); 416 | return -1; 417 | } 418 | 419 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 420 | chkrc(rc, goto error); 421 | 422 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 423 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 424 | 425 | rc = Esys_CreatePrimary(ctx, ESYS_TR_RH_OWNER, 426 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 427 | &primarySensitive, &primaryPublic, 428 | &allOutsideInfo, &allCreationPCR, 429 | &primary, NULL, NULL, NULL, NULL); 430 | chkrc(rc, goto error); 431 | 432 | rc = Esys_Load(ctx, primary, 433 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 434 | &keyPrivateSeal, &keyPublicSeal, 435 | &key); 436 | chkrc(rc, goto error); 437 | 438 | Esys_TR_SetAuth(ctx, key, &auth); 439 | 440 | rc = Esys_Unseal(ctx, key, 441 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 442 | &secret2b); 443 | Esys_FlushContext(ctx, key); 444 | chkrc(rc, goto error); 445 | 446 | rc = Esys_PCR_Read(ctx, 447 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 448 | &pcrsel, NULL, &pcrcheck, NULL); 449 | chkrc(rc, goto error); 450 | 451 | if (pcrcheck->count == 0) { 452 | dbg("No active banks selected"); 453 | return -1; 454 | } 455 | free(pcrcheck); 456 | 457 | rc = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE, 458 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 459 | NULL, TPM2_SE_POLICY, &sym, TPM2_ALG_SHA256, 460 | &session); 461 | chkrc(rc, goto error); 462 | 463 | rc = Esys_PolicyPCR(ctx, session, 464 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 465 | NULL, &pcrsel); 466 | chkrc(rc, Esys_FlushContext(ctx, session); goto error); 467 | 468 | rc = Esys_PolicyGetDigest(ctx, session, 469 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 470 | &policyDigest); 471 | Esys_FlushContext(ctx, session); 472 | chkrc(rc, goto error); 473 | 474 | keyInPublicHmac.publicArea.authPolicy = *policyDigest; 475 | free(policyDigest); 476 | 477 | keySensitive.sensitive.data.size = secret2b->size; 478 | memcpy(&keySensitive.sensitive.data.buffer[0], &secret2b->buffer[0], 479 | keySensitive.sensitive.data.size); 480 | free(secret2b); 481 | 482 | rc = Esys_Create(ctx, primary, 483 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 484 | &keySensitive, &keyInPublicHmac, 485 | &allOutsideInfo, &allCreationPCR, 486 | &keyPrivateHmac, &keyPublicHmac, NULL, NULL, NULL); 487 | chkrc(rc, goto error); 488 | Esys_FlushContext(ctx, primary); 489 | Esys_Finalize(&ctx); 490 | 491 | *newBlob_size = 4 + 4; 492 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(keyPublicHmac, NULL, -1, newBlob_size); 493 | chkrc(rc, goto error); 494 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(keyPrivateHmac, NULL, -1, newBlob_size); 495 | chkrc(rc, goto error); 496 | if (password && strlen(password) > 0) { 497 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(&keyPublicSeal, NULL, -1, newBlob_size); 498 | chkrc(rc, goto error); 499 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(&keyPrivateSeal, NULL, -1, newBlob_size); 500 | chkrc(rc, goto error); 501 | } 502 | 503 | *newBlob = malloc(*newBlob_size); 504 | off = 0; 505 | 506 | rc = Tss2_MU_UINT32_Marshal(pcrs, *newBlob, *newBlob_size, &off); 507 | chkrc(rc, goto error_marshall); 508 | rc = Tss2_MU_UINT32_Marshal(banks, *newBlob, *newBlob_size, &off); 509 | chkrc(rc, goto error_marshall); 510 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(keyPublicHmac, 511 | *newBlob, *newBlob_size, &off); 512 | chkrc(rc, goto error_marshall); 513 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(keyPrivateHmac, 514 | *newBlob, *newBlob_size, &off); 515 | chkrc(rc, goto error_marshall); 516 | rc = Tss2_MU_TPM2B_PUBLIC_Marshal(&keyPublicSeal, 517 | *newBlob, *newBlob_size, &off); 518 | chkrc(rc, goto error_marshall); 519 | rc = Tss2_MU_TPM2B_PRIVATE_Marshal(&keyPrivateSeal, 520 | *newBlob, *newBlob_size, &off); 521 | chkrc(rc, goto error_marshall); 522 | 523 | free(keyPublicHmac); 524 | free(keyPrivateHmac); 525 | 526 | return 0; 527 | 528 | error_marshall: 529 | free(keyPublicHmac); 530 | free(keyPrivateHmac); 531 | free(*newBlob); 532 | *newBlob = 0; 533 | *newBlob_size = 0; 534 | 535 | return (rc)? (int)rc : -1; 536 | 537 | error: 538 | free(keyPublicHmac); 539 | free(keyPrivateHmac); 540 | if (primary != ESYS_TR_NONE) Esys_FlushContext(ctx, primary); 541 | Esys_Finalize(&ctx); 542 | return (rc)? (int)rc : -1; 543 | } 544 | 545 | /** Store a key in a NV index. 546 | * 547 | * @param[in] keyBlob Key to store to NVRAM. 548 | * @param[in] keyBlob_size Size of the key. 549 | * @param[in] nv NV index to store the key. 550 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 551 | * @retval 0 on success. 552 | * @retval -1 on undefined/general failure. 553 | * @retval TSS2_RC response code for failures relayed from the TSS library. 554 | */ 555 | int 556 | tpm2totp_storeKey_nv(const uint8_t *keyBlob, size_t keyBlob_size, uint32_t nv, 557 | TSS2_TCTI_CONTEXT *tcti_context) 558 | { 559 | if (!keyBlob) 560 | return -1; 561 | 562 | TSS2_RC rc; 563 | ESYS_CONTEXT *ctx; 564 | ESYS_TR nvHandle; 565 | 566 | if (!nv) nv = DEFAULT_NV; /* Some random handle from owner space */ 567 | 568 | TPM2B_NV_PUBLIC publicInfo = { .size = 0, 569 | .nvPublic = { 570 | .nvIndex = nv, 571 | .nameAlg = TPM2_ALG_SHA1, 572 | .attributes = (TPMA_NV_OWNERWRITE | 573 | TPMA_NV_AUTHWRITE | 574 | TPMA_NV_WRITE_STCLEAR | 575 | TPMA_NV_READ_STCLEAR | 576 | TPMA_NV_AUTHREAD | 577 | TPMA_NV_OWNERREAD ), 578 | .authPolicy = { .size = 0, .buffer = {}, }, 579 | .dataSize = keyBlob_size, 580 | } }; 581 | 582 | TPM2B_MAX_NV_BUFFER blob = { .size = keyBlob_size }; 583 | if (blob.size > sizeof(blob.buffer)) { 584 | dbg("keyBlob too large"); 585 | return -1; 586 | } 587 | memcpy(&blob.buffer[0], keyBlob, blob.size); 588 | 589 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 590 | chkrc(rc, return rc); 591 | 592 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 593 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 594 | 595 | rc = Esys_NV_DefineSpace(ctx, ESYS_TR_RH_OWNER, 596 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 597 | &emptyAuth, &publicInfo, &nvHandle); 598 | chkrc(rc, goto error); 599 | 600 | rc = Esys_NV_Write(ctx, nvHandle, nvHandle, 601 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 602 | &blob, 0/*=offset*/); 603 | Esys_TR_Close(ctx, &nvHandle); 604 | chkrc(rc, goto error); 605 | 606 | Esys_Finalize(&ctx); 607 | 608 | return 0; 609 | 610 | error: 611 | Esys_Finalize(&ctx); 612 | 613 | return (rc)? (int)rc : -1; 614 | } 615 | 616 | /** Load a key from a NV index. 617 | * 618 | * @param[in] nv NV index of the key. 619 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 620 | * @param[out] keyBlob Loaded key. 621 | * @param[out] keyBlob_size Size of the key. 622 | * @retval 0 on success. 623 | * @retval -1 on undefined/general failure. 624 | * @retval TSS2_RC response code for failures relayed from the TSS library. 625 | */ 626 | int 627 | tpm2totp_loadKey_nv(uint32_t nv, TSS2_TCTI_CONTEXT *tcti_context, 628 | uint8_t **keyBlob, size_t *keyBlob_size) 629 | { 630 | TSS2_RC rc; 631 | ESYS_CONTEXT *ctx; 632 | ESYS_TR nvHandle; 633 | TPM2B_MAX_NV_BUFFER *blob; 634 | TPM2B_NV_PUBLIC *publicInfo; 635 | 636 | if (!nv) nv = DEFAULT_NV; /* Some random handle from owner space */ 637 | 638 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 639 | chkrc(rc, return rc); 640 | 641 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 642 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 643 | 644 | rc = Esys_TR_FromTPMPublic(ctx, nv, 645 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 646 | &nvHandle); 647 | chkrc(rc, goto error); 648 | 649 | rc = Esys_NV_ReadPublic(ctx, nvHandle, 650 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 651 | &publicInfo, NULL); 652 | chkrc(rc, goto error); 653 | 654 | rc = Esys_NV_Read(ctx, nvHandle, nvHandle, 655 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 656 | publicInfo->nvPublic.dataSize, 0/*=offset*/, &blob); 657 | Esys_TR_Close(ctx, &nvHandle); 658 | free(publicInfo); 659 | chkrc(rc, goto error); 660 | 661 | Esys_Finalize(&ctx); 662 | 663 | *keyBlob_size = blob->size; 664 | *keyBlob = malloc(blob->size); 665 | memcpy(*keyBlob, &blob->buffer[0], *keyBlob_size); 666 | 667 | return 0; 668 | 669 | error: 670 | Esys_Finalize(&ctx); 671 | 672 | return (rc)? (int)rc : -1; 673 | } 674 | 675 | 676 | /** Delete a key from a NV index. 677 | * 678 | * @param[in] nv NV index to delete. 679 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 680 | * @retval 0 on success. 681 | * @retval -1 on undefined/general failure. 682 | * @retval TSS2_RC response code for failures relayed from the TSS library. 683 | */ 684 | int 685 | tpm2totp_deleteKey_nv(uint32_t nv, TSS2_TCTI_CONTEXT *tcti_context) 686 | { 687 | TSS2_RC rc; 688 | ESYS_CONTEXT *ctx; 689 | ESYS_TR nvHandle; 690 | 691 | if (!nv) nv = DEFAULT_NV; /* Some random handle from owner space */ 692 | 693 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 694 | chkrc(rc, return rc); 695 | 696 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 697 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 698 | 699 | rc = Esys_TR_FromTPMPublic(ctx, nv, 700 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 701 | &nvHandle); 702 | chkrc(rc, goto error); 703 | 704 | rc = Esys_NV_UndefineSpace(ctx, ESYS_TR_RH_OWNER, nvHandle, 705 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE); 706 | chkrc(rc, Esys_TR_Close(ctx, &nvHandle); goto error); 707 | 708 | Esys_Finalize(&ctx); 709 | 710 | return 0; 711 | 712 | error: 713 | 714 | Esys_Finalize(&ctx); 715 | 716 | return (rc)? (int)rc : -1; 717 | } 718 | 719 | /** Calculate a time-based one-time password for a key. 720 | * 721 | * @param[in] keyBlob Key to generate the TOTP. 722 | * @param[in] keyBlob_size Size of the key. 723 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 724 | * @param[out] nowp Current time. 725 | * @param[out] otp Calculated TOTP. 726 | * @retval 0 on success. 727 | * @retval -1 on undefined/general failure. 728 | * @retval TSS2_RC response code for failures relayed from the TSS library. 729 | */ 730 | int 731 | tpm2totp_calculate(const uint8_t *keyBlob, size_t keyBlob_size, 732 | TSS2_TCTI_CONTEXT *tcti_context, 733 | time_t *nowp, uint64_t *otp) 734 | { 735 | if (keyBlob == NULL || otp == NULL) { 736 | return -1; 737 | } 738 | 739 | ESYS_CONTEXT *ctx = NULL; 740 | ESYS_TR primary, key, session; 741 | TSS2_RC rc; 742 | TPM2B_PUBLIC keyPublic = { .size=0 }; 743 | TPM2B_PRIVATE keyPrivate = { .size=0 }; 744 | size_t off = 0; 745 | TPM2B_DIGEST *output; 746 | uint32_t pcrs; 747 | uint32_t banks; 748 | time_t now; 749 | uint64_t tmp; 750 | int offset; 751 | 752 | TPM2B_MAX_BUFFER input; 753 | 754 | TPML_PCR_SELECTION pcrsel = { .count = 0 }; 755 | 756 | TPMT_SYM_DEF sym = {.algorithm = TPM2_ALG_AES, 757 | .keyBits = {.aes = 128}, 758 | .mode = {.aes = TPM2_ALG_CFB} 759 | }; 760 | 761 | rc = Tss2_MU_UINT32_Unmarshal(keyBlob, keyBlob_size, &off, &pcrs); 762 | chkrc(rc, goto error); 763 | rc = Tss2_MU_UINT32_Unmarshal(keyBlob, keyBlob_size, &off, &banks); 764 | chkrc(rc, goto error); 765 | rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(keyBlob, keyBlob_size, &off, &keyPublic); 766 | chkrc(rc, goto error); 767 | rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(keyBlob, keyBlob_size, &off, 768 | &keyPrivate); 769 | chkrc(rc, goto error); 770 | 771 | if (off != keyBlob_size) { 772 | rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 773 | chkrc(rc, goto error); 774 | rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 775 | chkrc(rc, goto error); 776 | } 777 | 778 | if (off != keyBlob_size) { 779 | dbg("bad blob size"); 780 | return -1; 781 | } 782 | 783 | if ((banks & TPM2TOTP_BANK_SHA1)) { 784 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA1; 785 | pcrsel.count++; 786 | } 787 | if ((banks & TPM2TOTP_BANK_SHA256)) { 788 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA256; 789 | pcrsel.count++; 790 | } 791 | if ((banks & TPM2TOTP_BANK_SHA384)) { 792 | pcrsel.pcrSelections[pcrsel.count].hash = TPM2_ALG_SHA384; 793 | pcrsel.count++; 794 | } 795 | 796 | for (size_t i = 0; i < pcrsel.count; i++) { 797 | pcrsel.pcrSelections[i].sizeofSelect = 3; 798 | pcrsel.pcrSelections[i].pcrSelect[0] = pcrs & 0xff; 799 | pcrsel.pcrSelections[i].pcrSelect[1] = pcrs >>8 & 0xff; 800 | pcrsel.pcrSelections[i].pcrSelect[2] = pcrs >>16 & 0xff; 801 | } 802 | 803 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 804 | chkrc(rc, goto error); 805 | 806 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 807 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 808 | 809 | rc = Esys_CreatePrimary(ctx, ESYS_TR_RH_OWNER, 810 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 811 | &primarySensitive, &primaryPublic, 812 | &allOutsideInfo, &allCreationPCR, 813 | &primary, NULL, NULL, NULL, NULL); 814 | chkrc(rc, goto error); 815 | 816 | rc = Esys_Load(ctx, primary, 817 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 818 | &keyPrivate, &keyPublic, 819 | &key); 820 | Esys_FlushContext(ctx, primary); 821 | chkrc(rc, goto error); 822 | 823 | rc = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE, 824 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 825 | NULL, TPM2_SE_POLICY, &sym, TPM2_ALG_SHA256, 826 | &session); 827 | chkrc(rc, goto error); 828 | 829 | rc = Esys_PolicyPCR(ctx, session, 830 | ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 831 | NULL, &pcrsel); 832 | chkrc(rc, Esys_FlushContext(ctx, session); goto error); 833 | 834 | /* Construct the RFC 6238 input */ 835 | now = time(NULL); 836 | tmp = now / TIMESTEPSIZE; 837 | tmp = htobe64(tmp); 838 | input.size = sizeof(tmp); 839 | memcpy(&input.buffer[0], ((void*)&tmp), input.size); 840 | 841 | rc = Esys_HMAC(ctx, key, 842 | session, ESYS_TR_NONE, ESYS_TR_NONE, 843 | &input, TPM2_ALG_SHA1, &output); 844 | Esys_FlushContext(ctx, session); 845 | Esys_FlushContext(ctx, key); 846 | chkrc(rc, goto error); 847 | 848 | Esys_Finalize(&ctx); 849 | 850 | if (output->size != 20) { 851 | free(output); 852 | goto error; 853 | } 854 | 855 | /* Perform the RFC 6238 -> RFC 4226 HOTP truncing */ 856 | offset = output->buffer[output->size - 1] & 0x0f; 857 | 858 | *otp = ((uint32_t)output->buffer[offset] & 0x7f) << 24 859 | | ((uint32_t)output->buffer[offset+1] & 0xff) << 16 860 | | ((uint32_t)output->buffer[offset+2] & 0xff) << 8 861 | | ((uint32_t)output->buffer[offset+3] & 0xff); 862 | *otp %= (1000000); 863 | 864 | free(output); 865 | 866 | if (nowp) *nowp = now; 867 | 868 | return 0; 869 | error: 870 | Esys_Finalize(&ctx); 871 | return (rc)? (int)rc : -1; 872 | } 873 | 874 | /** Recover a secret from a key. 875 | * 876 | * @param[in] keyBlob Key to recover the secret from. 877 | * @param[in] keyBlob_size Size of the key. 878 | * @param[in] password Password of the key. 879 | * @param[in] tcti_context Optional TCTI context to select TPM to use. 880 | * @param[out] secret Recovered secret. 881 | * @param[out] secret_size Size of the secret. 882 | * @retval 0 on success. 883 | * @retval -1 on undefined/general failure. 884 | * @retval -10 on empty password. 885 | * @retval -20 when no password-protected recovery copy of the secret has been stored. 886 | * @retval TSS2_RC response code for failures relayed from the TSS library. 887 | */ 888 | int 889 | tpm2totp_getSecret(const uint8_t *keyBlob, size_t keyBlob_size, 890 | const char *password, TSS2_TCTI_CONTEXT *tcti_context, 891 | uint8_t **secret, size_t *secret_size) 892 | { 893 | if (keyBlob == NULL || !password || secret == NULL || secret_size == NULL) { 894 | return -1; 895 | } 896 | if (!strlen(password)) { 897 | dbg("Password required."); 898 | return -10; 899 | } 900 | 901 | ESYS_CONTEXT *ctx = NULL; 902 | ESYS_TR primary, key; 903 | TSS2_RC rc; 904 | TPM2B_PUBLIC keyPublic = { .size=0 }; 905 | TPM2B_PRIVATE keyPrivate = { .size=0 }; 906 | size_t off = 4 + 4; /* Skipping over pcrs and banks */ 907 | TPM2B_SENSITIVE_DATA *secret2b; 908 | TPM2B_AUTH auth; 909 | 910 | auth.size = strlen(password); 911 | memcpy(&auth.buffer[0], password, auth.size); 912 | 913 | rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 914 | chkrc(rc, goto error); 915 | rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(keyBlob, keyBlob_size, &off, NULL); 916 | chkrc(rc, goto error); 917 | 918 | if (off == keyBlob_size) { 919 | dbg("No unseal blob included."); 920 | return -20; 921 | } 922 | 923 | rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(keyBlob, keyBlob_size, &off, &keyPublic); 924 | chkrc(rc, goto error); 925 | rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(keyBlob, keyBlob_size, &off, 926 | &keyPrivate); 927 | chkrc(rc, goto error); 928 | 929 | if (off != keyBlob_size) { 930 | dbg("bad blob size"); 931 | return -1; 932 | } 933 | 934 | rc = Esys_Initialize(&ctx, tcti_context, NULL); 935 | chkrc(rc, goto error); 936 | 937 | rc = Esys_Startup(ctx, TPM2_SU_CLEAR); 938 | if (rc != TPM2_RC_INITIALIZE) chkrc(rc, goto error); 939 | 940 | rc = Esys_CreatePrimary(ctx, ESYS_TR_RH_OWNER, 941 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 942 | &primarySensitive, &primaryPublic, 943 | &allOutsideInfo, &allCreationPCR, 944 | &primary, NULL, NULL, NULL, NULL); 945 | chkrc(rc, goto error); 946 | 947 | rc = Esys_Load(ctx, primary, 948 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 949 | &keyPrivate, &keyPublic, 950 | &key); 951 | Esys_FlushContext(ctx, primary); 952 | chkrc(rc, goto error); 953 | 954 | Esys_TR_SetAuth(ctx, key, &auth); 955 | 956 | rc = Esys_Unseal(ctx, key, 957 | ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, 958 | &secret2b); 959 | Esys_FlushContext(ctx, key); 960 | chkrc(rc, goto error); 961 | 962 | Esys_Finalize(&ctx); 963 | 964 | *secret = malloc(secret2b->size); 965 | if (!*secret) goto error; 966 | 967 | *secret_size = secret2b->size; 968 | memcpy(&(*secret)[0], &secret2b->buffer[0], *secret_size); 969 | 970 | return 0; 971 | error: 972 | Esys_Finalize(&ctx); 973 | return (rc)? (int)rc : -1; 974 | } 975 | 976 | /** @} */ 977 | -------------------------------------------------------------------------------- /src/plymouth-tpm2-totp.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-3-Clause */ 2 | /******************************************************************************* 3 | * Copyright 2019, Jonas Witschel 4 | * All rights reserved. 5 | *******************************************************************************/ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define VERB(...) if (opt.verbose) fprintf(stderr, __VA_ARGS__) 17 | #define ERR(...) fprintf(stderr, __VA_ARGS__) 18 | 19 | #define chkrc(rc, cmd) if (rc != TSS2_RC_SUCCESS) {\ 20 | ERR("ERROR in %s (%s:%i): 0x%08x\n", __func__, __FILE__, __LINE__, rc); cmd; } 21 | 22 | #define TPM2TOTP_ENV_TCTI "TPM2TOTP_TCTI" 23 | 24 | typedef struct { 25 | ply_boot_client_t *boot_client; 26 | ply_event_loop_t *event_loop; 27 | TSS2_TCTI_CONTEXT *tcti_context; 28 | uint8_t *key_blob; 29 | size_t key_blob_size; 30 | } state_t; 31 | 32 | char *help = 33 | "Usage: [options]\n" 34 | "Options:\n" 35 | " -h, --help print help\n" 36 | " -N, --nvindex TPM NV index to store data (default: 0x018094AF)\n" 37 | " -t, --time Show the time used for calculation\n" 38 | " -T, --tcti TCTI to use\n" 39 | " -v, --verbose print verbose messages\n" 40 | "\n"; 41 | 42 | static const char *optstr = "hN:tT:v"; 43 | 44 | static const struct option long_options[] = { 45 | {"help", no_argument, 0, 'h'}, 46 | {"nvindex", required_argument, 0, 'N'}, 47 | {"time", no_argument, 0, 't'}, 48 | {"tcti", required_argument, 0, 'T'}, 49 | {"verbose", no_argument, 0, 'v'}, 50 | {0, 0, 0, 0 } 51 | }; 52 | 53 | static struct opt { 54 | int nvindex; 55 | int time; 56 | char *tcti; 57 | int verbose; 58 | } opt; 59 | 60 | /** Parse and set command line options. 61 | * 62 | * This function parses the command line options and sets the appropriate values 63 | * in the opt struct. 64 | * @param argc The argument count. 65 | * @param argv The arguments. 66 | * @retval 0 on success 67 | * @retval 1 on failure 68 | */ 69 | int 70 | parse_opts(int argc, char **argv) 71 | { 72 | /* set the default values */ 73 | opt.nvindex = 0; 74 | opt.tcti = NULL; 75 | opt.time = 0; 76 | opt.verbose = 0; 77 | 78 | /* parse the options */ 79 | int c; 80 | int opt_idx = 0; 81 | while (-1 != (c = getopt_long(argc, argv, optstr, 82 | long_options, &opt_idx))) { 83 | switch(c) { 84 | case 'h': 85 | printf("%s", help); 86 | exit(0); 87 | case 'N': 88 | if (sscanf(optarg, "0x%x", &opt.nvindex) != 1 89 | && sscanf(optarg, "%i", &opt.nvindex) != 1) { 90 | ERR("Error parsing nvindex.\n"); 91 | return -1; 92 | } 93 | break; 94 | case 't': 95 | opt.time = 1; 96 | break; 97 | case 'T': 98 | opt.tcti = optarg; 99 | break; 100 | case 'v': 101 | opt.verbose = 1; 102 | break; 103 | default: 104 | ERR("Unknown option at index %i.\n\n", opt_idx); 105 | ERR("%s", help); 106 | return -1; 107 | } 108 | } 109 | 110 | if (optind < argc) { 111 | ERR("Unknown argument provided.\n\n"); 112 | ERR("%s", help); 113 | return -1; 114 | } 115 | return 0; 116 | } 117 | 118 | /** Exit the plymouth event loop after plymouth quits. 119 | * 120 | * This function is called when plymouth quits after boot and exits the main 121 | * event loop so that the program quits. 122 | * @param event_loop The plymouth event loop. 123 | * @param boot_client The plymouth boot client. 124 | */ 125 | void 126 | on_disconnect(void *event_loop, ply_boot_client_t *boot_client __attribute__((unused))) 127 | { 128 | ply_event_loop_exit(event_loop, 0); 129 | } 130 | 131 | /** Display the TOTP. 132 | * 133 | * This function calculates and displays the TOTP using plymouth. If the 134 | * calcuation is successful, the function is rescheduled in the plymouth event 135 | * loop to run after the next full 30 seconds, otherwise the event loop is 136 | * stopped with a non-zero return code. 137 | * @param state a struct containing the boot client, TCTI context and key. 138 | * @param event_loop The plymouth event loop. 139 | */ 140 | void 141 | display_totp(state_t *state, ply_event_loop_t *event_loop) 142 | { 143 | int rc; 144 | uint64_t totp; 145 | time_t now; 146 | struct tm now_local; 147 | char timestr[30] = ""; 148 | char totpstr[40] = ""; 149 | 150 | rc = tpm2totp_calculate(state->key_blob, state->key_blob_size, 151 | state->tcti_context, &now, &totp); 152 | 153 | if (rc == TSS2_RC_SUCCESS) { 154 | if (opt.time) { 155 | localtime_r(&now, &now_local); 156 | if (strftime(timestr, sizeof(timestr)-1, "%F %T: ", &now_local) == 0) { 157 | timestr[0] = '\0'; 158 | } 159 | } 160 | snprintf(totpstr, sizeof(totpstr)-1, "%s%06" PRIu64, timestr, totp); 161 | 162 | ply_boot_client_tell_daemon_to_display_message(state->boot_client, totpstr, 163 | NULL, NULL, NULL); 164 | 165 | ply_event_loop_watch_for_timeout(event_loop, 30-(now % 30), 166 | (ply_event_loop_timeout_handler_t) display_totp, 167 | state); 168 | } else { 169 | ERR("Couldn't calculate TOTP.\n"); 170 | ply_boot_client_tell_daemon_to_display_message(state->boot_client, 171 | "TPM failure", NULL, NULL, NULL); 172 | ply_event_loop_exit(event_loop, 1); 173 | } 174 | } 175 | 176 | /** Main function 177 | * 178 | * This function connects to plymouth, loads the key from the TPM and calls 179 | * the function to display the TOTP. 180 | * @param argc The argument count. 181 | * @param argv The arguments. 182 | * @retval 0 on success 183 | * @retval 1 on failure 184 | */ 185 | int 186 | main(int argc, char **argv) 187 | { 188 | state_t state = { 0, }; 189 | int rc; 190 | 191 | if (parse_opts(argc, argv) != 0) { 192 | return 1; 193 | } 194 | 195 | state.event_loop = ply_event_loop_new(); 196 | state.boot_client = ply_boot_client_new(); 197 | 198 | if (!ply_boot_client_connect(state.boot_client, on_disconnect, state.event_loop)) { 199 | ERR("plymouth daemon not running.\n"); 200 | goto err; 201 | } 202 | 203 | ply_boot_client_attach_to_event_loop(state.boot_client, state.event_loop); 204 | 205 | if (!opt.tcti) { 206 | opt.tcti = getenv(TPM2TOTP_ENV_TCTI); 207 | } 208 | rc = Tss2_TctiLdr_Initialize(opt.tcti, &state.tcti_context); 209 | chkrc(rc, goto err); 210 | 211 | rc = tpm2totp_loadKey_nv(opt.nvindex, state.tcti_context, &state.key_blob, &state.key_blob_size); 212 | chkrc(rc, goto err); 213 | 214 | display_totp(&state, state.event_loop); 215 | 216 | rc = ply_event_loop_run(state.event_loop); 217 | 218 | free(state.key_blob); 219 | ply_boot_client_free(state.boot_client); 220 | ply_event_loop_free(state.event_loop); 221 | Tss2_TctiLdr_Finalize(&state.tcti_context); 222 | return rc; 223 | 224 | err: 225 | /* The event loop needs to be run once so that it can be freed cleanly */ 226 | ply_event_loop_exit(state.event_loop, 1); 227 | ply_event_loop_run(state.event_loop); 228 | 229 | free(state.key_blob); 230 | ply_boot_client_free(state.boot_client); 231 | ply_event_loop_free(state.event_loop); 232 | Tss2_TctiLdr_Finalize(&state.tcti_context); 233 | return 1; 234 | } 235 | -------------------------------------------------------------------------------- /src/tpm2-totp.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-3-Clause */ 2 | /******************************************************************************* 3 | * Copyright 2018, Fraunhofer SIT 4 | * Copyright 2018, Jonas Witschel 5 | * All rights reserved. 6 | *******************************************************************************/ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define VERB(...) if (opt.verbose) fprintf(stderr, __VA_ARGS__) 21 | #define ERR(...) fprintf(stderr, __VA_ARGS__) 22 | 23 | #define chkrc(rc, cmd) if (rc != TSS2_RC_SUCCESS) {\ 24 | const char* error_text = decode_totp_rc(rc); \ 25 | if (error_text) {\ 26 | ERR("%s\n", error_text);\ 27 | } else {\ 28 | ERR("ERROR in %s (%s:%i): 0x%x - %s\n", __func__, __FILE__, __LINE__, rc, Tss2_RC_Decode(rc));\ 29 | }\ 30 | cmd; } 31 | 32 | #define TPM2TOTP_ENV_TCTI "TPM2TOTP_TCTI" 33 | 34 | char *help = 35 | "Usage: [options] {init|show|reseal|recover|clean}\n" 36 | "Options:\n" 37 | " -h, --help print help\n" 38 | " -b, --banks Selected PCR banks (default: SHA1,SHA256)\n" 39 | " -l, --label Label to use for display in the TOTP authenticator app (default: TPM2-TOTP)\n" 40 | " -N, --nvindex TPM NV index to store data (default: 0x018094AF)\n" 41 | " -P, --password Password for recovery/resealing (default: None). Read from stdin if '-' (recommended).\n" 42 | " -p, --pcrs Selected PCR registers (default: 0,2,4,6)\n" 43 | " -t, --time Show the time used for calculation\n" 44 | " -T, --tcti TCTI to use\n" 45 | " -v, --verbose print verbose messages\n" 46 | "\n"; 47 | 48 | static const char *optstr = "hb:N:P:p:tT:l:v"; 49 | 50 | static const struct option long_options[] = { 51 | {"help", no_argument, 0, 'h'}, 52 | {"banks", required_argument, 0, 'b'}, 53 | {"nvindex", required_argument, 0, 'N'}, 54 | {"password", required_argument, 0, 'P'}, 55 | {"pcrs", required_argument, 0, 'p'}, 56 | {"time", no_argument, 0, 't'}, 57 | {"tcti", required_argument, 0, 'T'}, 58 | {"label", required_argument, 0, 'l'}, 59 | {"verbose", no_argument, 0, 'v'}, 60 | {0, 0, 0, 0 } 61 | }; 62 | 63 | static struct opt { 64 | enum { CMD_NONE, CMD_INIT, CMD_SHOW, CMD_RESEAL, CMD_RECOVER, CMD_CLEAN } cmd; 65 | int banks; 66 | int nvindex; 67 | char *password; 68 | int pcrs; 69 | int time; 70 | char *tcti; 71 | char *label; 72 | int verbose; 73 | } opt; 74 | 75 | const char* 76 | decode_totp_rc(int rc) 77 | { 78 | switch(rc) { 79 | case -10: 80 | return "No recovery password for the TOTP secret was given."; 81 | break; 82 | case -20: 83 | return "The TOTP secret has not been stored with a recovery password and thus cannot be retrieved."; 84 | break; 85 | case TPM2_RC_NV_DEFINED: 86 | return "A TOTP secret is already stored, use 'show' to calculate and show the TOTP or 'clean' to delete it."; 87 | break; 88 | case (TPM2_RC_HANDLE | TPM2_RC_1): 89 | return "No TOTP secret is currently stored, use 'init' to generate and store one."; 90 | break; 91 | case (TPM2_RC_POLICY_FAIL | TPM2_RC_9): 92 | return "The system state has changed, no TOTP could be calculated."; 93 | break; 94 | case (TPM2_RC_AUTH_FAIL | TPM2_RC_9): 95 | return "Wrong recovery password for the TOTP secret."; 96 | break; 97 | case TPM2_RC_LOCKOUT: 98 | return "The password has been entered wrongly too many times and the TPM is in lockout mode."; 99 | break; 100 | default: 101 | return NULL; 102 | } 103 | } 104 | 105 | int 106 | parse_banks(char *str, int *banks) 107 | { 108 | char *token; 109 | char *saveptr; 110 | 111 | *banks = 0; 112 | 113 | token = strtok_r(str, ",", &saveptr); 114 | if (!token) { 115 | return -1; 116 | } 117 | while (token) { 118 | if (strcmp(token, "SHA1") == 0) { 119 | *banks |= TPM2TOTP_BANK_SHA1; 120 | } else if (strcmp(token, "SHA256") == 0) { 121 | *banks |= TPM2TOTP_BANK_SHA256; 122 | } else if (strcmp(token, "SHA384") == 0) { 123 | *banks |= TPM2TOTP_BANK_SHA384; 124 | } else { 125 | return -1; 126 | } 127 | token = strtok_r(NULL, ",", &saveptr); 128 | } 129 | 130 | return 0; 131 | } 132 | 133 | int 134 | parse_pcrs(char *str, int *pcrs) 135 | { 136 | char *token; 137 | char *saveptr; 138 | char *endptr; 139 | long pcr; 140 | 141 | *pcrs = 0; 142 | 143 | if (!str) { 144 | return -1; 145 | } 146 | token = strtok_r(str, ",", &saveptr); 147 | if (!token) { 148 | return -1; 149 | } 150 | while (token) { 151 | errno = 0; 152 | pcr = strtoul(token, &endptr, 0); 153 | if (errno || endptr == token || *endptr != '\0') { 154 | return -1; 155 | } else { 156 | *pcrs |= 1 << pcr; 157 | } 158 | token = strtok_r(NULL, ",", &saveptr); 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | /** Parse and set command line options. 165 | * 166 | * This function parses the command line options and sets the appropriate values 167 | * in the opt struct. 168 | * @param argc The argument count. 169 | * @param argv The arguments. 170 | * @retval 0 on success 171 | * @retval 1 on failure 172 | */ 173 | int 174 | parse_opts(int argc, char **argv) 175 | { 176 | /* set the default values */ 177 | opt.cmd = CMD_NONE; 178 | opt.banks = 0; 179 | opt.nvindex = 0; 180 | opt.password = ""; 181 | opt.pcrs = 0; 182 | opt.time = 0; 183 | opt.verbose = 0; 184 | opt.label = "TPM2-TOTP"; 185 | 186 | /* parse the options */ 187 | int c; 188 | int opt_idx = 0; 189 | while (-1 != (c = getopt_long(argc, argv, optstr, 190 | long_options, &opt_idx))) { 191 | switch(c) { 192 | case 'h': 193 | printf("%s", help); 194 | exit(0); 195 | case 'b': 196 | if (parse_banks(optarg, &opt.banks) != 0) { 197 | ERR("Error parsing banks.\n"); 198 | return -1; 199 | } 200 | break; 201 | case 'N': 202 | if (sscanf(optarg, "0x%x", &opt.nvindex) != 1 203 | && sscanf(optarg, "%i", &opt.nvindex) != 1) { 204 | ERR("Error parsing nvindex.\n"); 205 | return -1; 206 | } 207 | break; 208 | case 'P': 209 | if (!strcmp(optarg, "-")) { 210 | int c; 211 | char *buf = NULL; 212 | size_t buf_size = 0; 213 | while ((c = getc(stdin)) != EOF) { 214 | char *buf_tmp = (char *)realloc(buf, buf_size + 2); /* + 2 for \0 termination */ 215 | if (buf_tmp == NULL) { 216 | ERR("Error reading password from stdin. Out of memory after %lu bytes allocated.\n", buf_size); 217 | free(buf); 218 | return -1; 219 | } 220 | buf = buf_tmp; 221 | if (c == '\0') { 222 | ERR("Error reading password from stdin. Must not contain '\\0'.\n"); 223 | free(buf); 224 | return -1; 225 | } 226 | buf[buf_size++] = c; 227 | } 228 | if (buf == NULL) { 229 | ERR("Error reading password from stdin. Empty file.\n"); 230 | return -1; 231 | } else { 232 | buf[buf_size] = '\0'; 233 | opt.password = buf; 234 | } 235 | } else { 236 | opt.password = optarg; 237 | } 238 | break; 239 | case 'p': 240 | if (parse_pcrs(optarg, &opt.pcrs) != 0) { 241 | ERR("Error parsing pcrs.\n"); 242 | return -1; 243 | } 244 | break; 245 | case 't': 246 | opt.time = 1; 247 | break; 248 | case 'T': 249 | opt.tcti = optarg; 250 | break; 251 | case 'l': 252 | opt.label = optarg; 253 | break; 254 | case 'v': 255 | opt.verbose = 1; 256 | break; 257 | default: 258 | ERR("Unknown option at index %i.\n\n", opt_idx); 259 | ERR("%s", help); 260 | return -1; 261 | } 262 | } 263 | 264 | /* parse the non-option arguments */ 265 | if (optind >= argc) { 266 | ERR("Missing command: init, show, reseal, recover, clean.\n\n"); 267 | ERR("%s", help); 268 | return -1; 269 | } 270 | if (!strcmp(argv[optind], "init")) { 271 | opt.cmd = CMD_INIT; 272 | } else if (!strcmp(argv[optind], "show")) { 273 | opt.cmd = CMD_SHOW; 274 | } else if (!strcmp(argv[optind], "reseal")) { 275 | opt.cmd = CMD_RESEAL; 276 | } else if (!strcmp(argv[optind], "recover")) { 277 | opt.cmd = CMD_RECOVER; 278 | } else if (!strcmp(argv[optind], "clean")) { 279 | opt.cmd = CMD_CLEAN; 280 | } else { 281 | ERR("Unknown command: init, show, reseal, recover, clean.\n\n"); 282 | ERR("%s", help); 283 | return -1; 284 | } 285 | optind++; 286 | 287 | if (optind < argc) { 288 | ERR("Unknown argument provided.\n\n"); 289 | ERR("%s", help); 290 | return -1; 291 | } 292 | return 0; 293 | } 294 | 295 | static char * 296 | base32enc(const uint8_t *in, size_t in_size) { 297 | static unsigned char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 298 | 299 | size_t i = 0, j = 0; 300 | size_t out_size = ((in_size + 4) / 5) * 8; 301 | unsigned char *r = malloc(out_size + 1); 302 | 303 | while (1) { 304 | r[i++] = in[j] >> 3 & 0x1F; 305 | r[i++] = in[j] << 2 & 0x1F; 306 | if (++j >= in_size) break; else i--; 307 | r[i++] |= in[j] >> 6 & 0x1F; 308 | r[i++] = in[j] >> 1 & 0x1F; 309 | r[i++] = in[j] << 4 & 0x1F; 310 | if (++j >= in_size) break; else i--; 311 | r[i++] |= in[j] >> 4 & 0x1F; 312 | r[i++] = in[j] << 1 & 0x1F; 313 | if (++j >= in_size) break; else i--; 314 | r[i++] |= in[j] >> 7 & 0x1F; 315 | r[i++] = in[j] >> 2 & 0x1F; 316 | r[i++] = in[j] << 3 & 0x1F; 317 | if (++j >= in_size) break; else i--; 318 | r[i++] |= in[j] >> 5 & 0x1F; 319 | r[i++] = in[j] & 0x1F; 320 | if (++j >= in_size) break; 321 | } 322 | for (j = 0; j < i; j++) { 323 | r[j] = base32[r[j]]; 324 | } 325 | while (i < out_size) { 326 | r[i++] = '='; 327 | } 328 | r[i] = 0; 329 | return (char *)r; 330 | } 331 | 332 | char * 333 | qrencode(const char *url) 334 | { 335 | QRcode *qrcode = QRcode_encodeString(url, 0/*=version*/, QR_ECLEVEL_L, 336 | QR_MODE_8, 1/*=case*/); 337 | if (!qrcode) { ERR("QRcode failed."); return NULL; } 338 | 339 | char *qrpic = malloc(/* Margins top / bot*/ 2 * ( 340 | (qrcode->width+2) * 2 - 2 + 341 | strlen("\e[47m%*s\e[0m\n") ) + 342 | /* lines */ qrcode->width * ( 343 | strlen("\e[47m ") * (qrcode->width + 1) + 344 | strlen("\e[47m \e[0m\n") 345 | ) + 1 /* \0 */); 346 | size_t idx = 0; 347 | idx += sprintf(&qrpic[idx], "\e[47m%*s\e[0m\n", 2*(qrcode->width+2), ""); 348 | for (int y = 0; y < qrcode->width; y++) { 349 | idx += sprintf(&qrpic[idx], "\e[47m "); 350 | for (int x = 0; x < qrcode->width; x++) { 351 | if (qrcode->data[y*qrcode->width + x] & 0x01) { 352 | idx += sprintf(&qrpic[idx], "\e[40m "); 353 | } else { 354 | idx += sprintf(&qrpic[idx], "\e[47m "); 355 | } 356 | } 357 | idx += sprintf(&qrpic[idx], "\e[47m \e[0m\n"); 358 | } 359 | idx += sprintf(&qrpic[idx], "\e[47m%*s\e[0m\n", 2*(qrcode->width+2), ""); 360 | (void)(idx); 361 | free(qrcode); 362 | return qrpic; 363 | } 364 | 365 | #define URL_PREFIX "otpauth://totp/%s?secret=" 366 | 367 | static int 368 | tpm2totp_qrencode( 369 | const char * const totp_name, 370 | const uint8_t * const secret, 371 | const size_t secret_size 372 | ) 373 | { 374 | const char * const base32key = base32enc(secret, secret_size); 375 | const size_t url_len = 1 376 | + strlen(base32key) 377 | + strlen(totp_name) 378 | + strlen(URL_PREFIX); 379 | char * const url = calloc(1, url_len); 380 | snprintf(url, url_len, URL_PREFIX "%s", totp_name, base32key); 381 | free((void*) base32key); 382 | 383 | const char * const qrpic = qrencode(url); 384 | if (!qrpic) { 385 | free((void*) url); 386 | return -1; 387 | } 388 | 389 | printf("%s\n", qrpic); 390 | printf("%s\n", url); 391 | free((void*) qrpic); 392 | free((void*) url); 393 | return 0; 394 | } 395 | 396 | /** Main function 397 | * 398 | * This function initializes OpenSSL and then calls the key generation 399 | * functions. 400 | * @param argc The argument count. 401 | * @param argv The arguments. 402 | * @retval 0 on success 403 | * @retval 1 on failure 404 | */ 405 | int 406 | main(int argc, char **argv) 407 | { 408 | int rc; 409 | uint8_t *secret, *keyBlob, *newBlob; 410 | size_t secret_size, keyBlob_size, newBlob_size; 411 | uint64_t totp; 412 | time_t now; 413 | struct tm now_local; 414 | char timestr[100] = { 0, }; 415 | TSS2_TCTI_CONTEXT *tcti_context = NULL; 416 | 417 | if (parse_opts(argc, argv) != 0) { 418 | goto err; 419 | } 420 | 421 | if (!opt.tcti) { 422 | opt.tcti = getenv(TPM2TOTP_ENV_TCTI); 423 | } 424 | rc = Tss2_TctiLdr_Initialize(opt.tcti, &tcti_context); 425 | chkrc(rc, goto err); 426 | 427 | switch(opt.cmd) { 428 | case CMD_INIT: 429 | 430 | rc = tpm2totp_generateKey(opt.pcrs, opt.banks, opt.password, tcti_context, 431 | &secret, &secret_size, 432 | &keyBlob, &keyBlob_size); 433 | chkrc(rc, goto err); 434 | 435 | rc = tpm2totp_storeKey_nv(keyBlob, keyBlob_size, opt.nvindex, tcti_context); 436 | free(keyBlob); 437 | chkrc(rc, goto err); 438 | 439 | if (tpm2totp_qrencode(opt.label, secret, secret_size) < 0) 440 | goto err; 441 | 442 | break; 443 | case CMD_SHOW: 444 | rc = tpm2totp_loadKey_nv(opt.nvindex, tcti_context, &keyBlob, &keyBlob_size); 445 | chkrc(rc, goto err); 446 | 447 | rc = tpm2totp_calculate(keyBlob, keyBlob_size, tcti_context, &now, &totp); 448 | free(keyBlob); 449 | chkrc(rc, goto err); 450 | if (opt.time) { 451 | localtime_r(&now, &now_local); 452 | rc = !strftime(timestr, sizeof(timestr)-1, "%Y-%m-%d %H:%M:%S: ", 453 | &now_local); 454 | chkrc(rc, goto err); 455 | } 456 | printf("%s%06" PRIu64, timestr, totp); 457 | break; 458 | case CMD_RESEAL: 459 | rc = tpm2totp_loadKey_nv(opt.nvindex, tcti_context, &keyBlob, &keyBlob_size); 460 | chkrc(rc, goto err); 461 | 462 | rc = tpm2totp_reseal(keyBlob, keyBlob_size, opt.password, opt.pcrs, 463 | opt.banks, tcti_context, &newBlob, &newBlob_size); 464 | free(keyBlob); 465 | chkrc(rc, goto err); 466 | 467 | //TODO: Are your sure ? 468 | rc = tpm2totp_deleteKey_nv(opt.nvindex, tcti_context); 469 | chkrc(rc, goto err); 470 | 471 | rc = tpm2totp_storeKey_nv(newBlob, newBlob_size, opt.nvindex, 472 | tcti_context); 473 | free(newBlob); 474 | chkrc(rc, goto err); 475 | break; 476 | case CMD_RECOVER: 477 | rc = tpm2totp_loadKey_nv(opt.nvindex, tcti_context, 478 | &keyBlob, &keyBlob_size); 479 | chkrc(rc, goto err); 480 | 481 | rc = tpm2totp_getSecret(keyBlob, keyBlob_size, opt.password, tcti_context, 482 | &secret, &secret_size); 483 | free(keyBlob); 484 | chkrc(rc, goto err); 485 | 486 | if (tpm2totp_qrencode(opt.label, secret, secret_size) < 0) 487 | goto err; 488 | 489 | break; 490 | case CMD_CLEAN: 491 | //TODO: Are your sure ? 492 | rc = tpm2totp_deleteKey_nv(opt.nvindex, tcti_context); 493 | chkrc(rc, goto err); 494 | break; 495 | default: 496 | goto err; 497 | } 498 | 499 | Tss2_TctiLdr_Finalize(&tcti_context); 500 | return 0; 501 | 502 | err: 503 | Tss2_TctiLdr_Finalize(&tcti_context); 504 | return 1; 505 | } 506 | -------------------------------------------------------------------------------- /test/libtpm2-totp.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-3-Clause */ 2 | /******************************************************************************* 3 | * Copyright 2018, Fraunhofer SIT 4 | * All rights reserved. 5 | *******************************************************************************/ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define chkrc(rc, cmd) if (rc != TSS2_RC_SUCCESS) {\ 15 | fprintf(stderr, "ERROR in %s:%i: 0x%08x\n", __FILE__, __LINE__, rc); cmd; } 16 | 17 | #define TPM2TOTP_ENV_TCTI "TPM2TOTP_TCTI" 18 | 19 | #define PWD "hallo" 20 | 21 | int 22 | main(int argc, char **argv) 23 | { 24 | (void)(argc); (void)(argv); 25 | 26 | int rc; 27 | uint8_t *secret = NULL; 28 | uint8_t *keyBlob = NULL; 29 | uint8_t *newBlob = NULL; 30 | size_t secret_size, keyBlob_size, newBlob_size; 31 | uint64_t totp; 32 | char totp_string[7], totp_check[7]; 33 | time_t now; 34 | TSS2_TCTI_CONTEXT *tcti_context; 35 | 36 | /**********/ 37 | 38 | rc = Tss2_TctiLdr_Initialize(getenv(TPM2TOTP_ENV_TCTI), &tcti_context); 39 | chkrc(rc, goto err); 40 | 41 | /**********/ 42 | 43 | rc = tpm2totp_generateKey(0x00, 0x00, PWD, tcti_context, 44 | &secret, &secret_size, &keyBlob, &keyBlob_size); 45 | chkrc(rc, goto err); 46 | 47 | rc = tpm2totp_calculate(keyBlob, keyBlob_size, tcti_context, &now, &totp); 48 | chkrc(rc, goto err); 49 | snprintf(&totp_string[0], 7, "%.*" PRIu64, 6, totp); 50 | 51 | rc = oath_totp_generate((char *)secret, secret_size, now, 30, 0, 6, &totp_check[0]); 52 | chkrc(rc, goto err); 53 | 54 | if (!!memcmp(&totp_string[0], &totp_check[0], 7)) { 55 | fprintf(stderr, "TPM's %s != %s\n", totp_string, totp_check); 56 | goto err; 57 | } 58 | 59 | /**********/ 60 | 61 | rc = tpm2totp_reseal(keyBlob, keyBlob_size, PWD, 0, 0, tcti_context, &newBlob, &newBlob_size); 62 | chkrc(rc, goto err); 63 | 64 | rc = tpm2totp_calculate(newBlob, newBlob_size, tcti_context, &now, &totp); 65 | chkrc(rc, goto err); 66 | snprintf(&totp_string[0], 7, "%.*" PRIu64, 6, totp); 67 | 68 | rc = oath_totp_generate((char *)secret, secret_size, now, 30, 0, 6, &totp_check[0]); 69 | chkrc(rc, goto err); 70 | 71 | if (!!memcmp(&totp_string[0], &totp_check[0], 7)) { 72 | fprintf(stderr, "TPM's %s != %s\n", totp_string, totp_check); 73 | goto err; 74 | } 75 | free(newBlob); 76 | 77 | /**********/ 78 | 79 | rc = tpm2totp_getSecret(keyBlob, keyBlob_size, PWD, tcti_context, 80 | &secret, &secret_size); 81 | chkrc(rc, goto err); 82 | 83 | /**********/ 84 | 85 | rc = tpm2totp_storeKey_nv(keyBlob, keyBlob_size, 0, tcti_context); 86 | chkrc(rc, goto err); 87 | 88 | free(keyBlob); 89 | rc = tpm2totp_loadKey_nv(0, tcti_context, &keyBlob, &keyBlob_size); 90 | chkrc(rc, goto err); 91 | 92 | rc = tpm2totp_deleteKey_nv(0, tcti_context); 93 | chkrc(rc, goto err); 94 | 95 | rc = tpm2totp_storeKey_nv(keyBlob, keyBlob_size, 0, tcti_context); 96 | chkrc(rc, goto err); 97 | 98 | rc = tpm2totp_deleteKey_nv(0, tcti_context); 99 | chkrc(rc, goto err); 100 | 101 | /***********/ 102 | 103 | free(keyBlob); 104 | free(secret); 105 | Tss2_TctiLdr_Finalize(&tcti_context); 106 | return 0; 107 | 108 | err: 109 | free(keyBlob); 110 | free(secret); 111 | Tss2_TctiLdr_Finalize(&tcti_context); 112 | return 1; 113 | } 114 | -------------------------------------------------------------------------------- /test/libtpm2-totp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2018 Fraunhofer SIT 4 | # All rights reserved. 5 | 6 | set -eufx 7 | 8 | libtpm2-totp 9 | -------------------------------------------------------------------------------- /test/plymouth-tpm2-totp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2019 Jonas Witschel 4 | # All rights reserved. 5 | 6 | set -eufx 7 | 8 | success_or_timeout() { 9 | # 124 is the exit status GNU timeout returns when the timeout is reached 10 | [ "$1" -eq 0 ] || [ "$1" -eq 124 ] 11 | return $? 12 | } 13 | 14 | cleanup() { 15 | kill "$plymouth_tpm2_totp_pid" || true 16 | kill "$plymouthd_pid" || true 17 | } 18 | 19 | plymouthd_pid='' 20 | plymouth_tpm2_totp_pid='' 21 | trap "cleanup" EXIT 22 | 23 | if pgrep plymouthd; then 24 | echo "ERROR: plymouthd is already running." 25 | exit 99 26 | fi 27 | 28 | plymouth-tpm2-totp --help 29 | 30 | exit_status=0 31 | timeout 10s plymouth-tpm2-totp || exit_status=$? 32 | if success_or_timeout "$exit_status"; then 33 | echo "plymouth-tpm2-totp should fail when plymouthd is not running." 34 | exit 1 35 | fi 36 | 37 | if [ "$EUID" -eq 0 ]; then 38 | plymouthd --no-daemon & 39 | else 40 | # plymouthd usually needs root access in order to display the splash screen. 41 | # Since we are only interested in the messaging infrastructure, attempt to 42 | # start plymouthd with fakeroot. 43 | fakeroot plymouthd --no-daemon & 44 | fi 45 | sleep 1 46 | 47 | # We need the PID of plymouthd, not the fakeroot PID, so we cannot use $! 48 | plymouthd_pid="$(pgrep plymouthd)" 49 | if [ -z "$plymouthd_pid" ]; then 50 | echo "ERROR: Failed to start plymouthd." 51 | exit 99 52 | fi 53 | 54 | tpm2-totp --banks SHA256 --pcrs 0 --nvindex 0x018094AF --password abc init 55 | 56 | tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 57 | exit_status=0 58 | timeout 10s plymouth-tpm2-totp --nvindex 0x018094AF || exit_status=$? 59 | if success_or_timeout "$exit_status"; then 60 | echo "plymouth-tpm2-totp should fail when the PCR state is changed." 61 | exit 1 62 | fi 63 | 64 | tpm2-totp --nvindex 0x018094AF --password abc reseal 65 | 66 | plymouth-tpm2-totp --nvindex 0x018094AF --time & 67 | plymouth_tpm2_totp_pid=$! 68 | 69 | # Wait for the TOTP to refresh after 30 seconds 70 | sleep 40 71 | 72 | kill "$plymouthd_pid" 73 | 74 | # Give plymouth-tpm2-totp some time to quit 75 | timeout 10s tail --pid "$plymouth_tpm2_totp_pid" --follow /dev/null 76 | 77 | # plymouthd-tpm2-totp should exit successfully after plymouthd has quit 78 | wait "$plymouth_tpm2_totp_pid" 79 | -------------------------------------------------------------------------------- /test/sh_log_compiler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2019 Jonas Witschel 4 | # All rights reserved. 5 | 6 | export LANG=C 7 | export PATH="$PWD:$PATH" 8 | 9 | test_script="$(realpath "$1")" 10 | 11 | tmp_dir="$(mktemp --directory)" 12 | echo "Switching to temporary directory $tmp_dir" 13 | cd "$tmp_dir" 14 | 15 | for simulator in 'swtpm' 'tpm_server'; do 16 | simulator_binary="$(command -v "$simulator")" && break 17 | done 18 | if [ -z "$simulator_binary" ]; then 19 | echo 'ERROR: No TPM simulator was found on PATH' 20 | exit 99 21 | fi 22 | 23 | for attempt in $(seq 9 -1 0); do 24 | simulator_port="$(shuf --input-range 1024-65534 --head-count 1)" 25 | echo "Starting simulator on port $simulator_port" 26 | case "$simulator_binary" in 27 | *swtpm) "$simulator_binary" socket --tpm2 --server port="$simulator_port" \ 28 | --ctrl type=tcp,port="$(( simulator_port + 1 ))" \ 29 | --flags not-need-init --tpmstate dir="$tmp_dir" &;; 30 | *tpm_server) "$simulator_binary" -port "$simulator_port" &;; 31 | esac 32 | simulator_pid="$!" 33 | sleep 1 34 | 35 | if ( ss --listening --tcp --ipv4 --processes | grep "$simulator_pid" | grep --quiet "$simulator_port" && 36 | ss --listening --tcp --ipv4 --processes | grep "$simulator_pid" | grep --quiet "$(( simulator_port + 1 ))" ) 37 | then 38 | echo "Simulator with PID $simulator_pid started successfully" 39 | break 40 | else 41 | echo "Failed to start simulator, the port might be in use" 42 | kill "$simulator_pid" 43 | 44 | if [ "$attempt" -eq 0 ]; then 45 | echo 'ERROR: Reached maximum number of tries to start simulator, giving up' 46 | exit 99 47 | fi 48 | fi 49 | done 50 | 51 | case "$simulator_binary" in 52 | *swtpm) export TPM2TOTP_TCTI="swtpm:port=$simulator_port";; 53 | *tpm_server) export TPM2TOTP_TCTI="mssim:port=$simulator_port";; 54 | esac 55 | export TPM2TOOLS_TCTI="$TPM2TOTP_TCTI" 56 | 57 | tpm2_startup --clear 58 | 59 | echo "Starting $test_script" 60 | "$test_script" 61 | test_status="$?" 62 | 63 | kill "$simulator_pid" 64 | rm -rf "$tmp_dir" 65 | 66 | exit "$test_status" 67 | -------------------------------------------------------------------------------- /test/tpm2-totp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright (c) 2018 Fraunhofer SIT 4 | # All rights reserved. 5 | 6 | set -eufx 7 | 8 | exit_status=0 9 | tpm2-totp invalid-argument || exit_status=$? 10 | if [ "$exit_status" -ne 1 ]; then 11 | echo "tpm2-totp should have exit status 1 on invalid arguments!" 12 | exit 1 13 | fi 14 | 15 | tpm2-totp -P abc -p 0,1,2,3,4,5,6 -b SHA1,SHA256 init 16 | 17 | # Changing an unselected PCR bank should not affect the TOTP calculation 18 | tpm2_pcrextend 0:sha384=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 19 | 20 | tpm2-totp -t show 21 | 22 | tpm2_pcrextend 1:sha1=0000000000000000000000000000000000000000 23 | 24 | if tpm2-totp -t show; then 25 | echo "The TOTP was calculated despite a changed PCR state!" 26 | exit 1 27 | fi 28 | 29 | tpm2-totp -P abc recover 30 | 31 | # Test reading password from stdin 32 | echo -n 'abc' | tpm2-totp -P - recover 33 | 34 | if tpm2-totp -P wrongpassword recover; then 35 | echo "The secret was recovered despite an incorrect password!" 36 | exit 1 37 | fi 38 | 39 | tpm2-totp -P abc -p 0,1,2,3,4,5,6 -b SHA1,SHA256 reseal 40 | 41 | # Changing an unselected PCR bank should not affect the TOTP calculation 42 | tpm2_pcrextend 0:sha384=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 43 | 44 | tpm2-totp show 45 | 46 | tpm2_pcrextend 1:sha1=0000000000000000000000000000000000000000 47 | 48 | if tpm2-totp show; then 49 | echo "The TOTP was calculated despite a changed PCR state!" 50 | exit 1 51 | fi 52 | 53 | tpm2-totp clean 54 | --------------------------------------------------------------------------------