pax_global_header00006660000000000000000000000064152011072170014506gustar00rootroot0000000000000052 comment=6df9b6969da3fa438604cc711cb231ba2fa52a4d latchset-clevis-6df9b69/000077500000000000000000000000001520110721700152335ustar00rootroot00000000000000latchset-clevis-6df9b69/.fmf/000077500000000000000000000000001520110721700160615ustar00rootroot00000000000000latchset-clevis-6df9b69/.fmf/version000066400000000000000000000000011520110721700174600ustar00rootroot000000000000001latchset-clevis-6df9b69/.github/000077500000000000000000000000001520110721700165735ustar00rootroot00000000000000latchset-clevis-6df9b69/.github/workflows/000077500000000000000000000000001520110721700206305ustar00rootroot00000000000000latchset-clevis-6df9b69/.github/workflows/build.yml000066400000000000000000000031211520110721700224470ustar00rootroot00000000000000--- name: build on: push: ignore-paths: - '**.md' pull_request: ignore-paths: - '**.md' jobs: build: runs-on: ubuntu-latest continue-on-error: ${{ ! matrix.stable }} strategy: matrix: os: - fedora:latest - quay.io/centos/centos:stream10 - quay.io/centos/centos:stream9 - debian:testing - debian:latest - ubuntu:rolling - ubuntu:latest stable: [true] include: - os: quay.io/fedora/fedora:rawhide stable: false - os: ubuntu:devel stable: false steps: - uses: actions/checkout@v4 - name: Show OS information run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available - name: Install build dependencies run: bash .github/workflows/install-dependencies - name: Build clevis run: | mkdir -p build && cd build export ninja=$(command -v ninja) [ -z "${ninja}" ] && export ninja=$(command -v ninja-build) export CFLAGS="-g -coverage" meson .. || cat meson-logs/meson-log.txt >&2 ${ninja} - name: Run tests run: | cd build if ! meson test ; then cat meson-logs/testlog.txt >&2 exit -1 fi - name: Show full test logs run: cat build/meson-logs/testlog.txt >&2 container: image: ${{matrix.os}} env: DISTRO: ${{matrix.os}} options: --privileged --device /dev/loop-control # vim:set ts=2 sw=2 et: latchset-clevis-6df9b69/.github/workflows/differential-shellcheck.yml000066400000000000000000000012151520110721700261110ustar00rootroot00000000000000# Doc: https://github.com/redhat-plumbers-in-action/differential-shellcheck#usage --- name: Differential ShellCheck on: pull_request: branches: [master] ignore-paths: - '**.md' permissions: contents: read jobs: lint: runs-on: ubuntu-latest permissions: security-events: write pull-requests: write steps: - name: Repository checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Differential ShellCheck uses: redhat-plumbers-in-action/differential-shellcheck@v3 with: severity: warning token: ${{ secrets.GITHUB_TOKEN }} latchset-clevis-6df9b69/.github/workflows/install-dependencies000077500000000000000000000041321520110721700246500ustar00rootroot00000000000000#!/bin/bash -ex COMMON="meson curl git make file bzip2 jose tang cryptsetup keyutils jq socat lsof procps ${CC}" case "${DISTRO}" in debian:*|ubuntu:*) apt-get clean while ! apt-get update; do sleep 5 done while ! apt-get -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confnew" \ dist-upgrade; do sleep 5 done export DEBIAN_FRONTEND=noninteractive apt-get install -y keyboard-configuration console-setup while ! apt-get -y install ${COMMON} \ build-essential pkg-config libssl-dev libjansson-dev libjose-dev \ luksmeta libluksmeta-dev libpwquality-tools libglib2.0-dev \ libudisks2-dev libaudit-dev systemd opensc pcscd libsofthsm2-dev \ swtpm-tools tpm-tools tpm2-tools; do sleep 5 done ;; *fedora:*) printf 'max_parallel_downloads=10\nfastestmirror=1\n' >> /etc/dnf/dnf.conf dnf -y clean all # Fix Fedora 43 (rawhide) build - install systemd and awk dnf -y install --allowerasing systemd awk dnf -y --setopt=deltarpm=0 update dnf -y install dnf-utils jq socat cryptsetup keyutils cracklib-dicts lsof \ opensc pcsc-lite softhsm swtpm-tools tpm-tools trousers command -v dnf5 && dnf5 -y install dnf5-command\(builddep\) \ || dnf -y install dnf-command\(builddep\) dnf -y builddep clevis ;; *centos:*) yum -y clean all yum -y --setopt=deltarpm=0 update yum install -y yum-utils yum config-manager -y --set-enabled crb || yum config-manager \ -y --set-enabled powertools || : yum -y install epel-release yum -y install epel-next-release || : yum -y --allowerasing install ${COMMON} yum -y install pkgconfig openssl-devel openssl zlib-devel \ jansson-devel findutils gcc libjose-devel luksmeta libluksmeta-devel \ audit-libs-devel tpm2-tools desktop-file-utils cracklib-dicts opensc \ pcsc-lite softhsm swtpm-tools tpm-tools trousers sed -i 's|>=1\.0\.2|>=1\.0\.1|' meson.build ;; esac # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: latchset-clevis-6df9b69/.github/workflows/spellcheck.yml000066400000000000000000000030631520110721700234720ustar00rootroot00000000000000name: Spellcheck on: push: # branches: [main] pull_request: jobs: spell-check: name: Language tool & Misspell check runs-on: ubuntu-latest steps: - name: check out code uses: actions/checkout@v4 - name: running language tool uses: reviewdog/action-languagetool@v1 with: github_token: ${{ secrets.github_token }} # Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review]. reporter: github-check # Change reporter level if you need. level: warning language: en-US disabled_categories: 'TYPOS,TYPOGRAPHY,CASING' disabled_rules: 'WHITESPACE_RULE,EN_QUOTES,DASH_RULE,WORD_CONTAINS_UNDERSCORE,UPPERCASE_SENTENCE_START,ARROWS,COMMA_PARENTHESIS_WHITESPACE,UNLIKELY_OPENING_PUNCTUATION,SENTENCE_WHITESPACE,CURRENCY,EN_UNPAIRED_BRACKETS,PHRASE_REPETITION,PUNCTUATION_PARAGRAPH_END,METRIC_UNITS_EN_US,ENGLISH_WORD_REPEAT_BEGINNING_RULE,DOUBLE_PUNCTUATION,' enabled_only: 'false' enabled_rules: '' enabled_categories: '' patterns: "**.md" - name: running misspell # To perform misspell check even after the language tool test fails if: success() || failure() uses: reviewdog/action-misspell@v1 with: github_token: ${{ secrets.github_token }} locale: "US" reporter: github-check level: info pattern: "**.md" exclude: | ./.git/* ./.cache/* latchset-clevis-6df9b69/.gitignore000066400000000000000000000007031520110721700172230ustar00rootroot00000000000000*~ *.a *.o *.la *.lo *.log *.m4 *.so *.swp *.swo *.trs .autotools .cproject .deps .dirstamp .libs/ .project .settings aclocal.m4 ar-lib autom4te.cache build compile config.guess config.log config.status config.sub configure configure-stamp depcomp install-sh libtool ltmain.sh Makecache Makefile.in Makefile missing tags test-driver src/clevis-encrypt-sss src/clevis-decrypt-sss src/udisks2/clevis-luks-udisks2 src/udisks2/clevis-luks-udisks2.desktop latchset-clevis-6df9b69/.packit.yaml000066400000000000000000000011631520110721700174510ustar00rootroot00000000000000jobs: - &tests-ci-base job: tests trigger: pull_request branch: master skip_build: true fmf_url: "https://github.com/RedHat-SP-Security/clevis-tests" fmf_ref: "master" targets: - fedora-stable - centos-stream-10-x86_64 - centos-stream-9-x86_64 identifier: "sanity" tmt_plan: "/Plans/upstream/sanity" tf_extra_params: environment: tmt: context: target_PR_branch: master - <<: *tests-ci-base targets: - centos-stream-10-x86_64 identifier: "pkcs11" tmt_plan: "/Plans/upstream/pkcs11-.*" use_internal_tf: True latchset-clevis-6df9b69/COPYING000066400000000000000000001045131520110721700162720ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . latchset-clevis-6df9b69/COPYING.openssl000066400000000000000000000012771520110721700177570ustar00rootroot00000000000000In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library under certain conditions as described in each individual source file, and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify file(s) with this exception, you may extend this exception to your version of the file(s), but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. latchset-clevis-6df9b69/INSTALL.md000066400000000000000000000036731520110721700166740ustar00rootroot00000000000000This file contains instructions to build and install Clevis from source # Dependencies To build and install the Clevis software the following software packages are required. In many cases dependencies are platform specific and so the following sections describe them for the supported platforms. ## Linux: * Meson * Ninja * C compiler * C Library Development Libraries and Header Files * [jose](https://github.com/latchset/jose) * [luksmeta](https://github.com/latchset/luksmeta) * [audit-libs](https://github.com/linux-audit/audit-userspace) * [udisks2](https://github.com/storaged-project/udisks) * [OpenSSL](https://github.com/openssl/openssl) * [desktop-file-utils](https://cgit.freedesktop.org/xdg/desktop-file-utils) * [pkg-config](https://cgit.freedesktop.org/pkg-config) * [systemd](https://github.com/systemd) * [dracut](https://github.com/dracutdevs/dracut) * [tang](https://github.com/latchset/tang) * [curl](https://github.com/curl/curl) * [tpm2-tools](https://github.com/tpm2-software/tpm2-tools) ### Fedora There is a package already, so the package build dependencies information can be used to make sure that the needed packages to compile from source are installed: ``` $ sudo dnf builddep clevis ``` # Building From Source ## Configuring the Build To configure Clevis, run `meson` which generates the build files: ``` $ meson build ``` ## Compiling Then compile the code using `ninja`: ``` $ ninja -C build -j$(nproc) ``` ## Installing Once you've built the Clevis software it can be installed with: ``` $ sudo ninja -C build install ``` This will install Clevis to a location determined at configure time. See the output of `meson --help` for the available options. Typically, much won't be needed besides providing an alternative --prefix option at configure time, and maybe DESTDIR at install time if you're packaging for a distro. After is installed, the dracut and systemd hooks can be added to the initramfs with: ``` $ sudo dracut -f ``` latchset-clevis-6df9b69/README.md000066400000000000000000000733071520110721700165240ustar00rootroot00000000000000[![build](https://github.com/latchset/clevis/workflows/build/badge.svg)](https://github.com/latchset/clevis/actions) # Clevis ## Welcome to Clevis! Clevis is a pluggable framework for automated decryption. It can be used to provide automated decryption of data or even automated unlocking of LUKS volumes. ### Encrypting Data What does this look like? Well, the first step is encrypting some data. We do this with a simple command: ```bash $ clevis encrypt PIN CONFIG < PLAINTEXT > CIPHERTEXT.jwe ``` This command takes plaintext on standard input and produces an encrypted JWE object on standard output. Besides the plaintext, we need to specify two additional input parameters. First, is the pin. In clevis terminology, a pin is a plugin which implements automated decryption. We simply pass the name of a pin here. Second, is the config. The config is a JSON object which will be passed directly to the pin. It contains all the necessary configuration to perform encryption and setup automated decryption. To decrypt our JWE, we simply perform the following: ```bash $ clevis decrypt < CIPHERTEXT.jwe > PLAINTEXT ``` Notice that no additional input or interaction is required for the decrypt command. Let's look at some more concrete examples. #### PIN: Tang [Tang](http://github.com/latchset/tang) is a server implementation which provides cryptographic binding services without the need for an escrow. Clevis has full support for Tang. Here is an example of how to use Clevis with Tang: ```bash $ echo hi | clevis encrypt tang '{"url": "http://tang.local"}' > hi.jwe The advertisement is signed with the following keys: kWwirxc5PhkFIH0yE28nc-EvjDY Do you wish to trust the advertisement? [yN] y ``` In this example, we encrypt the message "hi" using the Tang pin. The only parameter needed in this case is the URL of the Tang server. During the encryption process, the Tang pin requests the key advertisement from the server and asks you to trust the keys. This works similarly to SSH. Alternatively, you can manually load the advertisement using the `adv` parameter. This parameter takes either a string referencing the file where the advertisement is stored, or the JSON contents of the advertisement itself. When the advertisement is specified manually like this, Clevis presumes that the advertisement is trusted. #### PIN: TPM1 and TPM2 Clevis provides support to encrypt a key in a Trusted Platform Module 1.2 (TPM1) and 2.0 (TPM2) chips. The cryptographically-strong, random key used for encryption is encrypted using the TPM chip, and is decrypted using TPM at the time of decryption to allow clevis to decrypt the secret stored in the JWE. For example for TPM1 pin: ```bash $ echo hi | clevis encrypt tpm1 '{}' > hi.jwe ``` or TPM2 pin: ```bash $ echo hi | clevis encrypt tpm2 '{}' > hi.jwe ``` Clevis store the public and private keys of the encrypted key in the JWE object, so those can be fetched on decryption to unseal the key encrypted using the TPM chip. Check manual pages for `clevis-encrypt-tpm1` and `clevis-encrypt-tpm2` tools for more options, like binding to a particular PCR registry states and/or values. ##### TPM1 PIN Limitations To avoid prompting for a password during unlocking, the encryption and decryption processes require that the well-known Storage Root Key (SRK) be configured when taking ownership of the TPM 1.2 chip. This means you must have either run the `tpm_takeownership` command ```bash $ tpm_takeownership --srk-well-known ``` during setup or executed `tpm_changeownerauth` command ```bash $ tpm_changeownerauth --srk --set-well-known ``` to configure it. Note that a _well-known_ key is not the same as an empty key. > [!IMPORTANT] > If you have changed the SRK to a _well-known_ key, remember to run > `update-initramfs` command (on Debian-like systems) > > ```bash > $ update-initramfs -u > ``` > > or `dracut` command (on Fedora-like systems) > > ```bash > $ dracut -f > ``` > > afterward to recreate initramfs image, because `/var/lib/tpm` is > included in the image. This applies to `initramfs-tools` and Dracut in > _host-only_ mode. In Dracut's _default_ mode, `/var/lib/tpm` is already > configured to allow access to the TPM 1.2 chip using a _well-known_ SRK. ##### Unlocking with a Separately-Encrypted `/var` Volume with TPM1 PIN Because TPM1 PIN relies on the `tcsd` daemon from the Trousers project to access the TPM 1.2 chip, the daemon must start early in the boot process to unlock the root filesystem automatically. The `/var/lib/tpm` directory contains runtime data for `tcsd` and must be available before the daemon starts. A minimal copy of the required `/var` files is included in the initramfs image prepared by Clevis, so the daemon _should_ be able to start during the _initrd bootup_ phase if everything is configured correctly. After switching to the real root (`/`) filesystem, the _System Manager bootup_ phase starts and `/var` is mounted from the actual target. At this point, Clevis cannot unlock it (`tcsd` would need `/var` to unlock `/var`), so it must already be unlocked. Refer to the instructions below for `initramfs-tools` and Dracut. If the `/var` volume is part of the main LVM volume group (the same as the root `/` filesystem) and is protected by the same LUKS volume, no special configuration is needed. However, if the `/var` volume is encrypted separately (i.e., it uses a different LUKS volume, regardless of whether it has the same password), follow the instructions below to enable automatic unlocking with Clevis. ###### `initramfs-tools` Initrd Bootup `initramfs-tools` unlocks the root and swap filesystems by copying the corresponding option lines from `/etc/crypttab` into the initramfs. To ensure that `/var` volume options are also included, add the `initramfs` option on Debian-like system to the relevant line in `/etc/crypttab` as shown in the following example: > `/etc/crypttab` > ```bash > … > luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,initramfs > … > ``` This line corresponds to the `crypto_LUKS` volume used by the `/var` volume, as shown by the `lsblk -fp` command: > LVM on LUKS > ```bash > … > └─/dev/vda3 crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 > └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 > └─/dev/mapper/separate-var xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 753,3M 22% /var > ``` The above example uses an LVM-on-LUKS encryption scheme, but the same applies to LUKS-on-LVM — just check the `crypto_LUKS` volume UUID. > LUKS on LVM > ```bash > … > └─/dev/vda3 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 > └─/dev/mapper/separate-var crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 > └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 781,5M 19% /var > ```` > [!IMPORTANT] > After modifying `/etc/crypttab`, you must run `update-initramfs -u` (on > Debian-like systems). ###### Dracut Initrd Bootup Dracut automatically unlocks the root and swap filesystems. The operating system installer ensures that the kernel command line (in `/etc/default/grub`) contains the necessary parameters for Dracut and Systemd. Dracut considers both the kernel command line and the lines copied from `/etc/crypttab` for unlocking. By default, the root and swap lines from `/etc/crypttab` are copied into the initramfs. To ensure the `/var` volume is also unlocked, you must ensure that its options are included and referenced by the kernel command line (as described below). > [!CAUTION] > Changing the following options can render the system unbootable, potentially > requiring a rescue DVD and expert knowledge to recover. Make a full backup > before proceeding! > > For recovery, you may find these commands helpful: > > * `cryptsetup open /dev/ ` > * `mount /dev/mapper/ /` > * `lvm vgscan` > * `lvm lvdisplay -o lv_full_name,lv_dm_path` To ensure that the `/var` options are included, add either the `x-initrd.attach` option to the corresponding line in /etc/crypttab (to unlock the `/var` volume) or the `x-initrd.mount` option to the corresponding line in `/etc/fstab` (to unlock _and_ mount the `/var` volume). Using both is equivalent to `x-initrd.mount`. > `/etc/crypttab` > ```bash > … > luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,x-initrd.attach > … > ``` > `/etc/fstab` > ```bash > … > UUID=767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 /var xfs defaults,x-systemd.device-timeout=0,x-initrd.mount 0 0 > … > ``` Refer to the `initramfs-tools` section for instructions on finding the correct `/etc/crypttab` line with `lsblk -fp`. The `/etc/fstab` entry is matched by the UUID of the filesystem (see the line with `/var` in the `lsblk -fp` output). > [!IMPORTANT] > After changing `/etc/crypttab` and/or `/etc/fstab`, run `dracut -f`. > [!NOTE] > If you use `x-initrd.mount`, the volume is mounted during the _initrd bootup_ > phase. However, this is not strictly necessary. Systemd's startup order > ensures that `/var` is mounted before `tcsd` starts in the _System Manager > bootup_ phase, so using `x-initrd.attach` alone is sufficient. Next, ensure that the volumes are found and unlocked. Two kernel command line parameters in `/etc/default/grub` affect this: * `rd.luks.uuid` – Either remove all values or add the UUID of the `crypto_LUKS` volume (optionally prefixed by `luks-`). If this option is present (it can appear multiple times), only the specified volumes are initialized from `/etc/crypttab`. If it is missing, all lines from `/etc/crypttab` are considered. * `rd.lvm.lv` – Either remove all values or add the full LVM volume name for `/var`. If this option is present (it can appear multiple times), only the listed logical volumes are initialized. If it is missing, Dracut automatically detects LVM volumes during boot. > [!NOTE] > The `rd.lvm.lv` option matters only in the LUKS-on-LVM case, because the > `crypto_LUKS` volume is accessible only after the LVM logical volume is > activated. If `rd.lvm.lv` is missing, Dracut will detect LVM volumes > automatically. If it is present, make sure to include the `/var` full volume > name. For more information, see `man dracut.cmdline` and `man systemd-cryptsetup-generator`. > [!NOTE] > Dracut internally uses the same Systemd options, so the same logic applies > even if Systemd is not present in the Dracut initrd environment. To find the correct `rd.lvm.lv` value, run: ```bash lvs -o lv_full_name,lv_dm_path ``` This shows the logical volume's full name and Device Mapper path, which also appears in the `lsblk -fp` output. For example, if it shows `separate/var` (see example below), the `rd.lvm.lv` value would be `rd.lvm.lv=separate/var`: > ```bash > LV DMPath > … > separate/var /dev/mapper/separate-var > … > ``` Example of a kernel command line in `/etc/default/grub` with all options present: > `/etc/default/grub` > ```bash > GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/root rd.luks.uuid=luks-21a9c1b8-c202-4985-809a-aba2d6fdab01 rd.lvm.lv=separate/var rd.luks.uuid=luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 quiet" > ``` Example of a kernel command line in `/etc/default/grub` when relying on the configuration copied from `/etc/crypttab` and Dracut’s automatic LVM detection: > `/etc/default/grub` > ```bash > GRUB_CMDLINE_LINUX="quiet" > ``` > [!IMPORTANT] > After changing the kernel command line, update the Grub configuration with > `update-grub2` (on Debian-like systems) or > `grub2-mkconfig -o /etc/grub2.cfg` (on Fedora-like systems). #### PIN: PKCS#11 Clevis can perform the role of a PKCS#11 application, as described in the [RFC 7512: The PKCS#11 URI Scheme](https://www.rfc-editor.org/rfc/rfc7512.html). PKCS#11 protocol determines that a PIN (Personal Identity Number) must be configured into the hardware device so that the unlocking process is successful. Clevis will allow users to unlock a particular encrypted disk, and will provide a way to get the PIN. There will be two possibilities: 1 - Provide the PIN at boot time: In this first case, Clevis will detect PKCS#11 device and will prompt for its PIN. In case PIN is wrong, Clevis will prompt for the PIN again. It is the user's responsibility to be aware of the possible lock / brick of the device in case PIN is unknown. 2 - Provide the PIN at Clevis configuration time: In this second case, Clevis will be configured with the PIN value. Initially, RFC7512 defines a mechanism to specify a special kind of URI (the `pkcs11` URI), that allows identifying both a device and also the information required for it to be unlocked. Special attention deserves the parameters `pin-value`, which allow specifying the value of the PIN or the location of the PIN respectively. Clevis will understand, initially, the 'pin-value' parameter. Below you can find and example of PKCS#11 URIs using previous parameter: * PKCS#11 URI with `pin-value` defined: ``` pkcs11:token=Software%20PKCS%2311%20softtoken;manufacturer=Snake%20Oil,%20Inc.?pin-value=the-pin ``` In the next section, Clevis configuration examples are provided, so that it is clarified what are the different options for a PKCS#11 device to be bound to an encrypted disk. ##### Clevis configuration Clevis will provide a mechanism for the user to bind a particular PKCS#11 device to an encrypted device. The name of the new pin for Clevis will be `pkcs11`, and the way to configure it will be the same that is currently used: ``` $ clevis luks bind -h ``` ``` Usage: clevis luks bind [-y] [-f] [-s SLT] [-k KEY] [-t TOKEN_ID] [-e EXISTING_TOKEN_ID] -d DEV PIN CFG ``` ##### Configuration to provide a PKCS#11 URI to Clevis As first example, a user can provide the information of the device by specifying its URI to Clevis: ``` $ clevis luks bind -d /dev/sda1 pkcs11 '{"uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II; serial=0a35ba26b062b9c5;token=clevis;id=%02;object=Encryption%20Key"}' ``` ##### Configuration to bind Clevis to the first PKCS#11 device found An additional option is to provide Clevis a configuration so that the first PKCS#11 device found by Clevis is bound. To do so, an empty URI can be provided as shown below: ``` $ clevis luks bind -d /dev/sda1 pkcs11 '{"uri": "pkcs11:"}' ``` An even shorter configuration command, equivalent to the previous one, is shown below: ``` $ clevis luks bind -d /dev/sda1 pkcs11 '{}' ``` In this case, Clevis will be responsible for the detection of the device and, if no device is found, responsible for dumping the corresponding error. It must be clarified that providing an empty URI will make Clevis to prompt also to select one of the available keys matched on the token to avoid accidentally encryption with unwanted keys. ##### Configuration to provide a module path to Clevis PKCS#11 pin: A module path can be provided to Clevis, so that it uses that module to access a device. This is only required in case the card is not supported by underlying Clevis software (OpenSC). For this reason, the module path field is completely optional. To provide the module location the user can provide the "module-path" to the "uri" Clevis configuration: ``` $ clevis-luks-bind -d /dev/sda1 pkcs11 '{"uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II; serial=0a35ba26b062b9c5;token=clevis;id=%02;object=Encryption%20Key? module-path=/usr/local/lib64/libmypkcs11.so"}' ``` As it happens with the rest of devices, encrypted disks that have been bound to a PKCS#11 device can be checked with `clevis luks list` command: ``` $ clevis luks list -d /dev/sda1 ``` ``` 1: pkcs11 '{"uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II; serial=0a35ba26b062b9c5;token=clevis;id=%02;object=Encryption%20Key? module-path=/usr/local/lib64/libmypkcs11.so"}' ``` ##### Configuration to provide PKCS#11 tool a different mechanism In the first phase of development, Clevis will be used in top of OpenSC to provide PKCS#11 functionality. OpenSC, and, in particular, `pkcs11-tool`, provides an option to indicate the mechanism to use for decryption. For testing purposes, some libraries, such as [SoftHSM](https://www.opendnssec.org/softhsm)), don't work with default `pkcs11-tool` mechanism, so it is required to provide a particular mechanism to use. For this reason, Clevis can be provided with the mechanism to use, in case the default one, `RSA-PKCS-OAEP`, is not valid: ``` $ clevis luks bind -d /dev/sda1 pkcs11 '{"uri": "pkcs11:", "mechanism":"RSA-PKCS"}' ``` In order to check available mechanisms for a specific token, command `pkcs11-tool -M` can be used: ``` $ pkcs11-tool -M Using slot 0 with a present token (0x0) Supported mechanisms: SHA-1, digest ... SHA512, digest MD5, digest ... RSA-PKCS-KEY-PAIR-GEN, keySize={2048,4096}, generate_key_pair ``` At this time, only RSA mechanisms are supported by Clevis. Due to a limitation of the rest of the algorithms, no other asymmetric cryptographic algorithm can do encryption easily. The ECC supports only signatures and key derivation, but not encryption. The encryption operation can be somehow constructed from the key derivation, but it is not a straightforward operation. It must be highlighted that the RSA-PKCS mechanism (PKCS#1.5 padding for encryption) is [considered to be not secure](https://people.redhat.com/~hkario/marvin/) and it is mostly provided for compatibility, but it is not recommended using it in production. ##### Multi-device configuration Clevis will allow specifying the slot where a PKCS#11 device is located through the parameters provided to the URI: ``` $ clevis luks bind -d /dev/sda1 pkcs11 '{"uri": "pkcs11:slot-id=0"}' ``` It must be clarified that providing just the slot information will make Clevis to guess one of the available keys matched on the token in the selected slot, which could cause accidentally encryption with unwanted keys. **It is not recommended to use slot as device selector, as slot id is a number that is not guaranteed to be stable across PKCS#11 module initializations**. However, there are certain libraries and modules that provide stable slot identifiers, so it can be used for these particular cases. There are two better options to distinguish between different PKCS#11 devices: 1 - Multi-device configuration with public key object (**recommended**): With recent versions of `OpenSC` (from OpenSC 0.26.0 release) onwards, `pkcs11-tool`, which is used by Clevis to handle most of the PKCS#11 commands, the PKCS#11 URI is dumped for both the tokens and the objects of a particular token: ``` $ pkcs11-tool -L | grep uri uri : pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=42facd1f749ece7f;token=clevis uri : pkcs11:model=PKCS%2315%20emulated;manufacturer=OpenPGP%20project;serial=000f06080f4f;token=OpenPGP%20card%20%28User%20PIN%29 $ pkcs11-tool -O --slot-index 1 --type pubkey | grep uri ising slot 0 with a present token (0x0) uri: pkcs11:model=PKCS%2315%20emulated;manufacturer=OpenPGP%20project;serial=000f06080f4f;token=OpenPGP%20card%20%28User%20PIN%29;id=%03;object=Authentication%20key;type=public ``` In this particular cases, when multiple PKCS#11 devices exist, select the public key of the particular device and bind it to Clevis: ``` $ clevis luks bind -d /dev/sda pkcs11 '{"uri":"pkcs11:model=PKCS%2315%20emulated;manufacturer=OpenPGP%20project;serial=000f06080f4f;token=OpenPGP%20card%20%28User%20PIN%29;id=%03;object=Authentication%20key;type=public"}' ``` **In case you are using module-path, you will have to use the one returned when providing --module option:** ``` $ pkcs11-tool --module /usr/lib64/libykcs11.so -O --type pubkey | grep uri /usr/local/bin/pkcs11-tool.manual --module /usr/lib64/libykcs11.so -O --type pubkey | grep uri Using slot 0 with a present token (0x0) uri: pkcs11:model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=28083311;token=YubiKey%20PIV%20%2328083311;id=%03;object=Public%20key%20for%20Key%20Management;type=public uri: pkcs11:model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=28083311;token=YubiKey%20PIV%20%2328083311;id=%19;object=Public%20key%20for%20PIV%20Attestation;type=public $ clevis luks bind -d /dev/sda pkcs11 '{"uri":"pkcs11:model=YubiKey%20YK5;manufacturer=Yubico%20%28www.yubico.com%29;serial=28083311;token=YubiKey%20PIV%20%2328083311;id=%03;object=Public%20key%20for%20Key%20Management;type=public;module-path=/usr/lib64/libykcs11.so"}' ``` 2 - Multi-device configuration with serial + token specification: **For versions where `pkcs11-tool` does not dump the URI for the tokens/objects**, specific identification will be "tried" by Clevis by using the device `serial` + `token label` pair. In this type of scenarios, identification can be performed with these two parameters, although `model` should be provided also to ease Clevis informing about the device when asking for the PIN: ``` # pkcs11-tool -L | grep "token label\|serial" token label : OpenPGP card (User PIN) serial num : 42facd1f749ece7f $ clevis luks bind -d /dev/sda pkcs11 '{"uri":"pkcs11:model=PKCS%2315%20emulated;serial=000f06080f4f;token=OpenPGP%20card%20%28User%20PIN%29"}' ``` Remember that special characters must be defined in percent mode, as defined in [RFC 7512: The PKCS#11 URI Scheme](https://www.rfc-editor.org/rfc/rfc7512.html). ##### Clevis PKCS#11 installation and configuration For installation and configuration of the clevis PKCS#11 feature, next steps must be followed: 1 - Install Clevis required dependencies, including PKCS#11 dependencies: ``` $ sudo dnf install clevis-pin-pkcs11 ``` 2 - The PKCS11 device must be accessible by “pkcs11-tool”: ``` $ pkcs11-tool -L pkcs11-tool -L Available slots: Slot 0 (0x0): Yubico YubiKey OTP+CCID 00 00 token label : clevis ... uri : pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=42facd1f749ece7f;token=clevis ``` 3 - Configure device to bind with clevis: ``` $ sudo clevis luks bind -d /dev/sda5 pkcs11 '{"uri":"pkcs11:"}' ``` In case it is required to provide the module to use, it can be done through `module-path` URI parameter: ``` $ sudo clevis luks bind -d /dev/sda5 pkcs11 '{"uri":"pkcs11:module-path=/usr/lib64/libykcs11.so.2"}' ``` 4 - Enable clevis-luks-pkcs11-askpass.socket unit: ``` $ sudo systemctl enable --now clevis-luks-pkcs11-askpass.socket ``` 5 - /etc/crypttab configuration: For PKCS#11 feature to work appropriately, `/etc/crypttab` file must be configured so that systemd uses an AF\_UNIX socket to wait for the keyphrase that will unlock the disk and not to prompt it through the console. Clevis PKCS#11 unit file will configure a socket in path `/run/systemd/clevis-pkcs11.sock` to send and receive information about disk unlocking. For disks that will be unlocked through PKCS#11 Clevis pin, that socket file must be configured as key file. So, next change must be introduced in `/etc/crypttab` for unlocking to take place: ``` $ sudo diff -Nuar /etc/crypttab.ori /etc/crypttab --- /etc/crypttab.ori 2024-07-04 10:46:16.295073739 +0200 +++ /etc/crypttab 2024-07-03 17:14:27.764743860 +0200 @@ -1 +1,2 @@ -luks-6e38d5e1-7f83-43cc-819a-7416bcbf9f84 UUID=6e38d5e1-7f83-43cc-819a-7416bcbf9f84 - - +luks-6e38d5e1-7f83-43cc-819a-7416bcbf9f84 UUID=6e38d5e1-7f83-43cc-819a-7416bcbf9f84 /run/systemd/clevis-pkcs11.sock keyfile-timeout=30s ``` It is highly recommended setting a `keyfile-timeout` option to configure a fall-through mechanism in case some unlocking error occurs and passphrase is required to be entered manually through console. 6 - Reboot and test: System should boot and ask for the PKCS#11 device PIN, and decrypt the corresponding configured encrypted disk only in case PIN is correct. 7 - In case no boot process needs to be tested, encrypt and decrypt with next command (note it is necessary to provide the PIN value for it to work appropriately) and check encryption/decryption of a string can be performed with this one-liner, and no error takes place: ``` $ echo "top secret" | clevis encrypt pkcs11 '{"uri":"pkcs11:module-path=/usr/lib64/libykcs11.so.2?pin-value=123456"}' | clevis decrypt ``` The `top secret` string should be returned #### PIN: Shamir Secret Sharing Clevis provides a way to mix pins together to provide sophisticated unlocking policies. This is accomplished by using an algorithm called Shamir Secret Sharing (SSS). SSS is a thresholding scheme. It creates a key and divides it into a number of pieces. Each piece is encrypted using another pin (possibly even SSS recursively). Additionally, you define the threshold `t`. If at least `t` pieces can be decrypted, then the encryption key can be recovered and decryption can succeed. Here is an example where we use the SSS pin with both the Tang and TPM2 pins: ```bash $ echo hi | clevis encrypt sss \ '{"t": 2, "pins": {"tpm2": {"pcr_ids": "0"}, "tang": {"url": "http://tang.local"}}}' \ > hi.jwe ``` In the above example, we define two child pins and have a threshold of 2. This means that during decryption **both** child pins must succeed in order for SSS itself to succeed. Here is another example where we use just the Tang pin: ```bash $ echo hi | clevis encrypt sss \ '{"t": 1, "pins": {"tang": [{"url": "http://server1.local/key"}, {"url": "http://server2.local/key"}]}}' \ > hi.jwe ``` In this example, we define two child instances of the Tang pin - each with its own configuration. Since we have a threshold of 1, if **either** of the Tang pin instances succeed during decryption, SSS will succeed. ### Binding LUKS Volumes Clevis can be used to bind a LUKS volume using a pin so that it can be automatically unlocked. How this works is rather simple. We generate a new, cryptographically strong key. This key is added to LUKS as an additional passphrase. We then encrypt this key using Clevis, and store the output JWE inside the LUKS header using [LUKSMeta](http://github.com/latchset/luksmeta). Here is an example where we bind `/dev/sda1` using the Tang pin: ```bash $ sudo clevis luks bind -d /dev/sda1 tang '{"url": "http://tang.local"}' The advertisement is signed with the following keys: kWwirxc5PhkFIH0yE28nc-EvjDY Do you wish to trust the advertisement? [yN] y Enter existing LUKS password: ``` Upon successful completion of this binding process, the disk can be unlocked using one of the provided unlockers. #### Network based unlocking If you want to use network based unlocking you will need to specify `rd.neednet=1` as kernel argument or use `--hostonly-cmdline` when creating with dracut. If you're using **Tang** with TLS (Example: `'{"url": "https://tang.remote"}'`), the folder `/etc/ssl` should be included in the initramfs image, `--include /etc/ssl /etc/ssl --force` when creating with dracut. #### Unlocker: Dracut The Dracut unlocker attempts to automatically unlock volumes during early boot. This permits automated root volume encryption. Enabling the Dracut unlocker is easy. Just rebuild your initramfs after installing Clevis: ```bash $ sudo dracut -f ``` Upon reboot, you will be prompted to unlock the volume using a password. In the background, Clevis will attempt to unlock the volume automatically. If it succeeds, the password prompt will be cancelled and boot will continue. #### Unlocker: Initramfs-tools When using Clevis with initramfs-tools, in order to rebuild your initramfs you will need to run: ```bash sudo update-initramfs -u -k 'all' ``` Upon reboot, it will behave exactly as if using Dracut. #### Unlocker: UDisks2 Our UDisks2 unlocker runs in your desktop session. You should not need to manually enable it; just install the Clevis UDisks2 unlocker and restart your desktop session. The unlocker should be started automatically. This unlocker works almost exactly the same as the Dracut unlocker. If you insert a removable storage device that has been bound with Clevis, we will attempt to unlock it automatically in parallel with a desktop password prompt. If automatic unlocking succeeds, the password prompt will be dismissed without user intervention. #### Unlocker: Clevis command A LUKS device bound to a Clevis policy can also be unlocked by using the clevis luks unlock command. ```bash $ sudo clevis luks unlock -d /dev/sda1 ``` #### Unbinding LUKS volumes LUKS volumes can be unbound using the clevis luks unbind command. For example: ```bash $ sudo clevis luks unbind -d /dev/sda1 -s 1 ``` #### Listing pins bound to LUKS volumes The pins that are bound to a given LUKS volume can be listed using the clevis luks list command. For example: ```bash $ sudo clevis luks list -d /dev/sda1 ``` ## Installing Clevis Please don't install Clevis directly. Instead, use your preferred distribution's packages. ### Fedora 24+ This command installs the core Clevis commands, the Dracut, Systemd and LUKs unlockers, and the UDisks2 unlocker, respectively. ```bash $ sudo dnf install clevis clevis-dracut clevis-udisks2 clevis-systemd clevis-luks ``` ## Manual compilation As remarked in the previous section, **it is suggested not to install Clevis directly**. However, in case no Clevis packages exist for your Linux distribution, the steps to manually compile and install Clevis are next ones: * Download latest version of the binaries (note that the latest version could change): ```bash $ wget https://github.com/latchset/clevis/releases/download/v21/clevis-21.tar.xz ``` * Untar the binaries file: ```bash $ tar Jxvf clevis-21.tar.xz ``` * Create build directory and change path to it: ```bash $ cd clevis-21 $ mkdir build $ cd build ``` * Execute `meson` to setup compilation: ```bash $ meson setup .. ``` * Compile with `ninja` command: ```bash $ ninja ``` * Install with `ninja install` command (you will need root permissions for it): ```bash $ sudo ninja install ``` latchset-clevis-6df9b69/meson.build000066400000000000000000000034441520110721700174020ustar00rootroot00000000000000project('clevis', 'c', license: 'GPL3+', version: '23', default_options: 'c_std=c99' ) libexecdir = join_paths(get_option('prefix'), get_option('libexecdir')) sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir')) bindir = join_paths(get_option('prefix'), get_option('bindir')) libdir = join_paths(get_option('prefix'), get_option('libdir')) data = configuration_data() data.set('libexecdir', libexecdir) data.set('sysconfdir', sysconfdir) data.set('bindir', bindir) data.set('libdir', libdir) add_project_arguments( '-Wall', '-Wextra', '-Werror', '-Wstrict-aliasing', '-Wchar-subscripts', '-Wformat-security', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wnested-externs', '-Wpointer-arith', '-Wshadow', '-Wsign-compare', '-Wstrict-prototypes', '-Wtype-limits', '-Wunused-function', '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-Wno-unknown-pragmas', '-D_DEFAULT_SOURCE', '-D_POSIX_C_SOURCE=200112L', '-DBINDIR="' + bindir + '"', '-DCLEVIS_USER="' + get_option('user') + '"', '-DCLEVIS_GROUP="' + get_option('group') + '"', language: 'c' ) jansson = dependency('jansson', version: '>=2.10', required: false) jose = dependency('jose', version: '>=8') a2x = find_program('a2x', required: false) bins = [] mans = [] subdir('src') install_data(bins, install_dir: bindir) if a2x.found() foreach m : mans custom_target(m.split('/')[-1], input: m + '.adoc', output: m.split('/')[-1], command: [a2x, '-f', 'manpage', '-D', meson.current_build_dir(), '@INPUT@'], install_dir: join_paths(get_option('mandir'), 'man' + m.split('.')[-1]), install: true ) endforeach else warning('Will not build man pages due to missing dependencies!') endif summary('TPM 1.2 pin', get_option('tpm1'), section: 'Features') latchset-clevis-6df9b69/meson_options.txt000066400000000000000000000004721520110721700206730ustar00rootroot00000000000000option('user', type: 'string', value: 'clevis', description: 'Unprivileged user for secure clevis operations') option('group', type: 'string', value: 'clevis', description: 'Unprivileged group for secure clevis operations') option('tpm1', type: 'feature', value: 'auto', description: 'Enable TPM 1.2 pin support') latchset-clevis-6df9b69/src/000077500000000000000000000000001520110721700160225ustar00rootroot00000000000000latchset-clevis-6df9b69/src/bash/000077500000000000000000000000001520110721700167375ustar00rootroot00000000000000latchset-clevis-6df9b69/src/bash/clevis000066400000000000000000000022231520110721700201460ustar00rootroot00000000000000# bash completion support for clevis. _clevis() { local dir prev cur field dir=$(dirname "$(command -v clevis)") prev=${COMP_WORDS[COMP_CWORD-1]} cur=${COMP_WORDS[COMP_CWORD]} field=$((COMP_CWORD + 1)) case "${prev}" in -d) cur=${cur:=/dev/} _filedir return ;; -k) _filedir return ;; esac local name suggestions if [[ "${COMP_WORDS[COMP_CWORD-1]}" == "clevis" ]]; then name="clevis-*" fi if [[ "${COMP_WORDS[COMP_CWORD-2]}" == "clevis" ]]; then name="clevis-${COMP_WORDS[COMP_CWORD-1]}-*" fi suggestions= if [[ -n "${name}" ]]; then suggestions=$(find "${dir}" -name "${name}" -executable \ | cut -d '-' -f"${field}" | sort -u) local word if [[ -n "${cur}" ]]; then suggestions=$(for word in "${suggestions[@]}"; do \ echo "${word}" | grep -- "${cur}"; done) fi fi COMPREPLY=($(compgen -W "${suggestions}" -- "${cur}")) } complete -F _clevis clevis # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: latchset-clevis-6df9b69/src/bash/meson.build000066400000000000000000000004321520110721700211000ustar00rootroot00000000000000bashcomp = dependency('bash-completion', required: false) if bashcomp.found() bashcompdir = bashcomp.get_pkgconfig_variable('completionsdir') install_data('clevis', install_dir: bashcompdir) else warning('Will not install bash completion due to missing dependencies!') endif latchset-clevis-6df9b69/src/clevis000077500000000000000000000032201520110721700172320ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # function findexe() { [ $# -eq 1 ] || return 1 while read -r -d: path; do [ -f "$path/$1" ] && [ -x "$path/$1" ] && echo "$path/$1" && return 0 done <<< "$PATH:" return 1 } cmd=clevis input_commands="$cmd $@" while [ $# -gt 0 ]; do [[ "$1" =~ ^- ]] && break cmd="$cmd-$1" shift exe="$(findexe "$cmd")" && exec "$exe" "$@" done exec >&2 if [ "$cmd" != "clevis" ]; then echo echo "Command '$input_commands' is invalid" fi echo echo "Usage: clevis COMMAND [OPTIONS]" echo max=0 for f in "$0"-*; do [ -f "$f" ] && [ -x "$f" ] || continue f="${f##*/}" [ ${#f} -gt $max ] && max=${#f} done for f in "$0"-*; do [ -f "$f" ] && [ -x "$f" ] || continue summ="$("$f" --summary 2>/dev/null)" || continue f="${f##*/}" printf " %-*s %s\n" "$max" "${f//-/ }" "$summ" done echo exit 2 latchset-clevis-6df9b69/src/clevis-decrypt000077500000000000000000000031741520110721700207120ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # SUMMARY="Decrypts using the policy defined at encryption time" function findexe() { [ $# -eq 1 ] || return 1 while read -r -d: path; do [ -f "$path/$1" ] && [ -x "$path/$1" ] && echo "$path/$1" && return 0 done <<< "$PATH:" return 1 } if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi if ! [ -t 0 ]; then read -r -d . hdr if ! pin="$(jose fmt -q "$hdr" -SyOg clevis -Og pin -Su-)"; then echo "JWE is missing the required 'clevis.pin' header property!" >&2 exit 1 fi if ! cmd="$(findexe clevis-decrypt-"$pin")"; then echo "Unable to locate pin '$pin'!" >&2 exit 1 fi (echo -n "$hdr."; /bin/cat) | "$cmd" exit $? fi exec >&2 echo echo "Usage: clevis decrypt < JWE > PLAINTEXT" echo echo "$SUMMARY" echo exit 2 latchset-clevis-6df9b69/src/clevis-decrypt.1.adoc000066400000000000000000000006571520110721700217560ustar00rootroot00000000000000CLEVIS-DECRYPT(1) ================= :doctype: manpage == NAME clevis-decrypt - Decrypts using the policy defined at encryption time == SYNOPSIS *clevis decrypt* CONFIG < JWE > PT == OVERVIEW The *clevis decrypt* command decrypts data using the policy defined at encryption time. The specific decryption pin is inferred during decryption. There are no parameters. == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/clevis.1.adoc000066400000000000000000000110771520110721700203040ustar00rootroot00000000000000CLEVIS(1) ========= :doctype: manpage == NAME clevis - Automated decryption policy framework == SYNOPSIS *clevis* COMMAND [OPTIONS] == OVERVIEW Clevis is a framework for automated decryption policy. It allows you to define a policy at encryption time that must be satisfied for the data to decrypt. Once this policy is met, the data is decrypted. Clevis is pluggable. Our plugins are called pins. The job of a pin is to take a policy as its first argument and plaintext on standard input and to encrypt the data so that it can be automatically decrypted if the policy is met. Lets walk through an example. == TANG BINDING Clevis provides support for the Tang network binding server. Tang provides a stateless, lightweight alternative to escrows. Encrypting data using the Tang pin works much like our HTTP pin above: $ clevis encrypt tang '{"url":"http://tang.srv"}' < PT > JWE The advertisement contains the following signing keys: _OsIk0T-E2l6qjfdDiwVmidoZjA Do you wish to trust these keys? [ynYN] y As you can see above, Tang utilizes a trust-on-first-use workflow. Alternatively, Tang can perform entirely offline encryption if you pre-share the server advertisement. Decryption, too works like our first example: $ clevis decrypt < JWE > PT For more information, see link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)]. == TPM2 BINDING Clevis provides support to encrypt a key in a Trusted Platform Module 2.0 (TPM2) chip. The cryptographically-strong, random key used for encryption is encrypted using the TPM2 chip, and then at decryption time is decrypted using the TPM2 to allow clevis to decrypt the secret stored in the JWE. Encrypting data using the tpm2 pin works the same than the pins mentioned above: $ clevis encrypt tpm2 '{}' < PT > JWE The pin has reasonable defaults for its configuration, but a different hierarchy, hash, and key algorithms can be chosen if the defaults used are not suitable. Decryption also works similar to other pins, only the JWE needs to be provided: $ clevis decrypt < JWE > PT Note that like other pins no configuration is used for decryption, this is due clevis storing the public and private keys to unseal the TPM2 encrypted object in the JWE so clevis can fetch that information from there. For more information see link:clevis-encrypt-tpm2.1.adoc[*clevis-encrypt-tpm2*(1)]. == SHAMIR'S SECRET SHARING Clevis provides a way to mix pins together to create sophisticated unlocking and high availability policies. This is accomplished by using an algorithm called Shamir's Secret Sharing (SSS). SSS is a thresholding scheme. It creates a key and divides it into a number of pieces. Each piece is encrypted using another pin (possibly even SSS recursively). Additionally, you define the threshold *t*. If at least *t* pieces can be decrypted, then the encryption key can be recovered and decryption can succeed. For example, let's create a high-availability setup using Tang: $ cfg='{"t":1,"pins":{"tang":[{"url":...},{"url":...}]}}' $ clevis encrypt sss "$cfg" < PT > JWE In this policy, we are declaring that we have a threshold of 1, but that there are multiple key fragments encrypted using different Tang servers. Since our threshold is 1, so long as any of the Tang servers are available, decryption will succeed. As always, decryption is simply: $ clevis decrypt < JWE > PT For more information, see link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)]. == LUKS BINDING Clevis can be used to bind an existing LUKS volume to its automation policy. This is accomplished with a simple command: $ clevis luks bind -d /dev/sda tang '{"url":...}' This command performs four steps: 1. Creates a new key with the same entropy as the LUKS master key -- maximum entropy bits is 256. 2. Encrypts the new key with Clevis. 3. Stores the Clevis JWE in the LUKS header. 4. Enables the new key for use with LUKS. This disk can now be unlocked with your existing password as well as with the Clevis policy. Clevis provides two unlockers for LUKS volumes. First, we provide integration with Dracut to automatically unlock your root volume during early boot. Second, we provide integration with UDisks2 to automatically unlock your removable media in your desktop session. For more information, see link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)]. == SEE ALSO link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)], link:clevis-encrypt-tpm2.1.adoc[*clevis-encrypt-tpm2*(1)], link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)], link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)], link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/initramfs-tools/000077500000000000000000000000001520110721700211545ustar00rootroot00000000000000latchset-clevis-6df9b69/src/initramfs-tools/hooks/000077500000000000000000000000001520110721700222775ustar00rootroot00000000000000latchset-clevis-6df9b69/src/initramfs-tools/hooks/clevis.in000077500000000000000000000160501520110721700241210ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2017 Shawn Rose # Author: Shawn Rose # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # PREREQ="cryptroot" prereqs() { echo "$PREREQ" } case $1 in prereqs) prereqs exit 0 ;; esac . @initramfstoolsdir@/hook-functions die() { code="$1" msg="$2" echo " (ERROR): $msg" >&2 exit $1 } find_binary() { bin_name="$1" resolved=$(command -v ${bin_name}) [ -z "$resolved" ] && die 1 "Unable to find ${bin_name}" echo "$resolved" } find_library() { lib_name="$1" for lib_path in \ {/usr,}/libexec/${lib_name} \ {/usr,}/lib64/${lib_name} \ {/usr,}/lib/${lib_name} \ /usr/lib/`uname -m`-linux-gnu*/${lib_name} \ ; do if [ -e "$lib_path" ]; then echo "$lib_path" return fi done die 1 "Unable to find library ${lib_name}" } if [ -n "${FORCE_CLEVIS}" ] && [ "${FORCE_CLEVIS}" != "n" ]; then for f in /sbin/cryptsetup /sbin/dmsetup /lib/cryptsetup/askpass; do if [ ! -e "${DESTDIR}${f}" ]; then die 2 "cryptsetup utility '$f' wasn't found in the generated ramdisk image. " fi done fi copy_exec @bindir@/clevis-decrypt-tang || die 1 "@bindir@/clevis-decrypt-tang not found" copy_exec @bindir@/clevis-decrypt-sss || die 1 "@bindir@/clevis-decrypt-sss not found" copy_exec @bindir@/clevis-decrypt-null || die 1 "@bindir@/clevis-decrypt-null not found" copy_exec @bindir@/clevis-decrypt || die 1 "@bindir@/clevis-decrypt not found" copy_exec @bindir@/clevis-luks-common-functions || die 1 "@bindir@/clevis-luks-common-functions not found" copy_exec @bindir@/clevis-luks-list || die 1 "@bindir@/clevis-luks-list not found" if [ -x @bindir@/clevis-decrypt-tpm2 ]; then copy_exec @bindir@/clevis-decrypt-tpm2 || die 1 "@bindir@/clevis-decrypt-tpm2 not found" tpm2_creatprimary_bin=$(find_binary "tpm2_createprimary") tpm2_unseal_bin=$(find_binary "tpm2_unseal") tpm2_load_bin=$(find_binary "tpm2_load") tpm2_flushcontext=$(find_binary "tpm2_flushcontext") copy_exec "${tpm2_creatprimary_bin}" || die 1 "Unable to copy ${tpm2_creatprimary_bin}" copy_exec "${tpm2_unseal_bin}" || die 1 "Unable to copy ${tpm2_unseal_bin}" copy_exec "${tpm2_load_bin}" || die 1 "Unable to copy ${tpm2_load_bin}" copy_exec "${tpm2_flushcontext}" || die 1 "Unable to copy ${tpm2_flushcontext}" for _LIBRARY in @libdir@/libtss2-tcti-device.so*; do if [ -e "${_LIBRARY}" ]; then copy_exec "${_LIBRARY}" || die 2 "Unable to copy ${_LIBRARY}" fi done manual_add_modules tpm_crb manual_add_modules tpm_tis fi if [ -x @bindir@/clevis-decrypt-tpm1 ]; then copy_exec @bindir@/clevis-decrypt-tpm1 || die 1 "@bindir@/clevis-decrypt-tpm1 not found" copy_exec @libexecdir@/clevis-luks-tpm1-functions || die 1 "@libexecdir@/clevis-luks-tpm1-functions not found" copy_exec @libdir@/libclevis-tpm1-tcsd-preload.so || die 1 "@libdir@/libclevis-tpm1-tcsd-preload.so not found" tcsd_bin=$(find_binary "tcsd") # libgcc_s.so.* is no longer installed for gcc 2.34+ (no link to libpthread) libgcc_s=$(find_library "libgcc_s.so.[1-9]") tpm_version_bin=$(find_binary "tpm_version") tpm_unsealdata_bin=$(find_binary "tpm_unsealdata") copy_exec "${tpm_version_bin}" || die 1 "Unable to copy ${tpm_version_bin}" copy_exec "${tpm_unsealdata_bin}" || die 1 "Unable to copy ${tpm_unsealdata_bin}" copy_exec "${tcsd_bin}" || die 1 "Unable to copy ${tcsd_bin}" copy_exec "${libgcc_s}" || die 1 "Unable to copy ${libgcc_s}" copy_file config /etc/tcsd.conf || die 2 "Unable to copy /etc/tcsd.conf" mkdir -p "${DESTDIR}/var/lib/tpm" || die 2 "Unable to create /var/lib/tpm" cp /var/lib/tpm/* "${DESTDIR}/var/lib/tpm/" || die 2 "Unable to copy /var/lib/tpm" chown -R tss:tss "${DESTDIR}/var/lib/tpm" || die 2 "Unable to change owner of /var/lib/tpm" chmod -R u=rwX,go= "${DESTDIR}/var/lib/tpm" || die 2 "Unable to change permissions of /var/lib/tpm" if (( $(umask) & 0004 )); then # Root-only readable initrd filesystem, we need to run as root # shellcheck disable=SC2154 # $verbose is a dracut variable [ "${verbose}" = "y" ] && echo "Forcing tcsd to run as root" sed -i 's/^\([ ]*remote_ops\)/#\1/' "${DESTDIR}/etc/tcsd.conf" echo "TCSD_NO_PRIVILEGE_DROP=1" >> "${DESTDIR}/conf/conf.d/clevis" fi mkdir -p "${DESTDIR}/lib/udev/rules.d" || die 2 "Unable to create /lib/udev/rules.d" # shellcheck disable=SC2043 for rule in 60-tpm-udev.rules; do if [ -e /etc/udev/rules.d/$rule ]; then copy_file udev_rule /etc/udev/rules.d/$rule "/lib/udev/rules.d" || die 2 "Unable to copy $rule" elif [ -e /lib/udev/rules.d/$rule ]; then copy_file udev_rule /lib/udev/rules.d/$rule "/lib/udev/rules.d" || die 2 "Unable to copy $rule" fi done echo "root:x:0:0:root:/root:/bin/bash" >> "${DESTDIR}/etc/passwd" echo "root:x:0:" >> "${DESTDIR}/etc/group" group_id=`id -g tss` || die 2 "Unable to get tss group ID" user_id=`id -u tss` || die 2 "Unable to get tss user ID" echo "tss:x:$user_id:$group_id::/var/lib/tpm:/bin/false" >> "${DESTDIR}/etc/passwd" echo "tss:x:$group_id:" >> "${DESTDIR}/etc/group" echo "127.0.0.1 localhost" >> "${DESTDIR}/etc/hosts" echo "::1 localhost ip6-localhost ip6-loopback" >> "${DESTDIR}/etc/hosts" echo "ff02::1 ip6-allnodes" >> "${DESTDIR}/etc/hosts" echo "ff02::2 ip6-allrouters" >> "${DESTDIR}/etc/hosts" manual_add_modules tpm_tis fi luksmeta_bin=$(find_binary "luksmeta") jose_bin=$(find_binary "jose") copy_exec "${luksmeta_bin}" || die 2 "Unable to copy ${luksmeta_bin}" copy_exec "${jose_bin}" || die 2 "Unable to copy ${jose_bin}" copy_exec @bindir@/clevis || die 1 "@bindir@/clevis not found" curl_bin=$(find_binary "curl") awk_bin=$(find_binary "awk") bash_bin=$(find_binary "bash") copy_exec "${curl_bin}" || die 2 "Unable to copy ${curl_bin} to initrd image" copy_exec "${awk_bin}" || die 2 "Unable to copy ${awk_bin} to initrd image" copy_exec "${bash_bin}" || die 2 "Unable to copy ${bash_bin} to initrd image" # Copy latest versions of shared objects needed for DNS resolution for so in $(ldconfig -p | sed -nr 's/^\s*libnss_files\.so\.[0-9]+\s.*=>\s*//p'); do copy_exec "${so}" done for so in $(ldconfig -p | sed -nr 's/^\s*libnss_dns\.so\.[0-9]+\s.*=>\s*//p'); do copy_exec "${so}" done copy_file data @sysconfdir@/ssl/certs/ca-certificates.crt || die 2 "Unable to copy certificate bundle to initrd image" latchset-clevis-6df9b69/src/initramfs-tools/hooks/meson.build000066400000000000000000000002011520110721700244320ustar00rootroot00000000000000configure_file( input: 'clevis.in', output: 'clevis', install_dir: initramfs_hooks_dir, configuration: initramfs_data, ) latchset-clevis-6df9b69/src/initramfs-tools/meson.build000066400000000000000000000012031520110721700233120ustar00rootroot00000000000000initramfs_tools = find_program('update-initramfs', required: false) if initramfs_tools.found() initramfstools_dir = '/usr/share/initramfs-tools' initramfs_hooks_dir = '/usr/share/initramfs-tools/hooks' initramfs_scripts_dir = '/usr/share/initramfs-tools/scripts' initramfs_data = configuration_data() initramfs_data.merge_from(data) initramfs_data.set('initramfstoolsdir', initramfstools_dir) libdir = join_paths(get_option('prefix'), get_option('libdir')) initramfs_data.set('libdir', libdir) subdir('hooks') subdir('scripts') else warning('Will not install initramfs-tools module due to missing dependencies!') endif latchset-clevis-6df9b69/src/initramfs-tools/scripts/000077500000000000000000000000001520110721700226435ustar00rootroot00000000000000latchset-clevis-6df9b69/src/initramfs-tools/scripts/local-bottom/000077500000000000000000000000001520110721700252375ustar00rootroot00000000000000latchset-clevis-6df9b69/src/initramfs-tools/scripts/local-bottom/clevis.in000077500000000000000000000027361520110721700270670ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2017 Shawn Rose # # Author: Shawn Rose # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # PREREQ="" prereqs() { echo "$PREREQ" } case "$1" in prereqs) prereqs exit 0 ;; esac [ -s /run/clevis.pid ] || exit 0 . @bindir@/clevis-luks-common-functions if [ -f @libexecdir@/clevis-luks-tpm1-functions ]; then . @libexecdir@/clevis-luks-tpm1-functions stop_tcsd fi pid=$(cat /run/clevis.pid) clevis_kill_pid $pid rm -f /run/clevis.pid # Not really worried about downing extra interfaces: they will come up # during the actual boot. Might make this configurable later if needed. for iface in /sys/class/net/*; do if [ -e "$iface" ]; then iface=$(basename "$iface") ip link set dev "$iface" down ip addr flush dev "$iface" ip route flush dev "$iface" > /dev/null 2>&1 fi done latchset-clevis-6df9b69/src/initramfs-tools/scripts/local-bottom/meson.build000066400000000000000000000002371520110721700274030ustar00rootroot00000000000000configure_file( input: 'clevis.in', output: 'clevis', install_dir: join_paths(initramfs_scripts_dir, 'local-bottom'), configuration: initramfs_data, ) latchset-clevis-6df9b69/src/initramfs-tools/scripts/local-top/000077500000000000000000000000001520110721700245355ustar00rootroot00000000000000latchset-clevis-6df9b69/src/initramfs-tools/scripts/local-top/clevis.in000077500000000000000000000225231520110721700263610ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2017 Red Hat, Inc. # Copyright (c) 2017 Shawn Rose # Copyright (c) 2017 Guilhem Moulin # # Author: Harald Hoyer # Author: Nathaniel McCallum # Author: Shawn Rose # Author: Guilhem Moulin # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # case $1 in prereqs) exit 0 ;; esac # Return fifo path or nothing if not found get_pid_fifo_path() { local pid="$1" for fd in /proc/$pid/fd/*; do if [ -e "$fd" ]; then if [[ $(readlink -f "${fd}") == *"/cryptsetup/passfifo" ]]; then readlink -f "${fd}" return 0 fi fi done return 1 } # Gets the luks device to be unlocked and used pins get_pid_device_pins() { local pid="$1" local CRYPTTAB_SOURCE CRYPTTAB_SOURCE=$(tr '\0' '\n' 2>/dev/null "${PASSFIFO}" return 0 else return 1 fi } # Wait for askpass, and then try and decrypt immediately. Just in case # there are multiple devices that need decrypting, this will loop # infinitely (The local-bottom script will kill this after decryption) clevisloop() { # Set the path how we want it (Probably not all needed) PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin" local cryptkeyscript local askpass_info local sleep_time local CRYPTTAB_SOURCE local OLD_CRYPTTAB_SOURCE="" local netcfg_attempted=0 local tpm1cfg_attempted=0 local pins local PASSFIFO if [ -x /bin/plymouth ] && plymouth --ping; then cryptkeyscript='plymouth ask-for-password' else # This has to be escaped for awk cryptkeyscript='\/lib\/cryptsetup\/askpass' fi while true; do # Re-get the askpass PID in case there are multiple encrypted devices CRYPTTAB_SOURCE="" sleep_time=.1 until [ -n "$CRYPTTAB_SOURCE" ] && [ -p "$PASSFIFO" ]; do sleep $sleep_time if askpass_info=$(get_askpass_info "$cryptkeyscript"); then # Workaround for initramfs-tools checking the script as sh-compatible IFS=':' read -r CRYPTTAB_SOURCE pins PASSFIFO <> /etc/resolv.conf fi if [ ! -z "${IPV4DNS1}" ] && [ "${IPV4DNS1}" != "0.0.0.0" ]; then echo nameserver "${IPV4DNS1}" >> /etc/resolv.conf fi if [ ! -z "${IPV6DNS0}" ]; then echo nameserver "${IPV6DNS0}" >> /etc/resolv.conf fi done fi fi } do_configure_tpm1() { local tcsd_output= [ -x @bindir@/clevis-decrypt-tpm1 ] && [ -f @libexecdir@/clevis-luks-tpm1-functions ] || return . @libexecdir@/clevis-luks-tpm1-functions log_begin_msg "clevis: Starting TCSD daemon" wait_for_udev 10 # shellcheck disable=SC2034 # setting default value TCSD_NO_PRIVILEGE_DROP=0 [ -f /conf/conf.d/clevis ] && . /conf/conf.d/clevis if ! tcsd_output=$(start_tcsd 2>&1); then if [ -n "$tcsd_output" ]; then log_failure_msg "failed to start TCSD: $tcsd_output" else log_failure_msg "failed to start TCSD" fi fi log_end_msg } mkdir -p /var/cache/clevis-disks chmod 0700 /var/cache/clevis-disks clevisloop & echo $! >/run/clevis.pid latchset-clevis-6df9b69/src/initramfs-tools/scripts/local-top/meson.build000066400000000000000000000002341520110721700266760ustar00rootroot00000000000000configure_file( input: 'clevis.in', output: 'clevis', install_dir: join_paths(initramfs_scripts_dir, 'local-top'), configuration: initramfs_data, ) latchset-clevis-6df9b69/src/initramfs-tools/scripts/meson.build000066400000000000000000000000531520110721700250030ustar00rootroot00000000000000subdir('local-top') subdir('local-bottom') latchset-clevis-6df9b69/src/luks/000077500000000000000000000000001520110721700170005ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/clevis-luks-bind000077500000000000000000000107431520110721700221060ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Harald Hoyer # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Binds a LUKS device using the specified policy" usage() { exec >&2 echo echo "Usage: clevis luks bind [-y] [-f] [-s SLT] [-k KEY] [-t TOKEN_ID] [-e EXISTING_TOKEN_ID] -d DEV PIN CFG" echo echo "$SUMMARY": echo echo " -f Do not prompt for LUKSMeta initialization" echo echo " -d DEV The LUKS device on which to perform binding" echo echo " -y Automatically answer yes for all questions" echo echo " -s SLT The LUKS slot to use" echo echo " -t TKN_ID The LUKS token ID to use; only available for LUKS2" echo echo " -k KEY Non-interactively read LUKS password from KEY file" echo " -k - Non-interactively read LUKS password from standard input" echo echo " -e E_TKN_ID Existing LUKS token ID for existing passphrase; only available for LUKS2" echo exit 2 } if [ $# -eq 1 ] && [ "$1" = "--summary" ]; then echo "$SUMMARY" exit 0 fi FRC= YES= while getopts ":hfyd:s:k:t:e:" o; do case "$o" in f) FRC='-f';; d) DEV="$OPTARG";; s) SLT="$OPTARG";; k) KEY="$OPTARG";; t) TOKEN_ID="$OPTARG";; e) EXISTING_TOKEN_ID="$OPTARG";; y) FRC='-f' YES='-y';; *) usage;; esac done if [ -z "$DEV" ]; then echo "Did not specify a device!" >&2 usage fi if ! luks_type="$(clevis_luks_type "${DEV}")"; then echo "${DEV} is not a supported LUKS device" >&2 exit 1 fi if ! PIN="${@:$((OPTIND++)):1}" || [ -z "$PIN" ]; then echo "Did not specify a pin!" >&2 usage elif ! EXE=$(command -v clevis-encrypt-"${PIN}") || [ -z "${EXE}" ]; then echo "'${PIN}' is not a valid pin!" >&2 usage fi if ! CFG="${@:$((OPTIND++)):1}" || [ -z "$CFG" ]; then echo "Did not specify a pin config!" >&2 usage fi # Check whether the config is valid JSON. if ! jose fmt --json="${CFG}" --object 2>/dev/null; then echo "Configuration is malformed; it should be valid JSON" >&2 exit 1 fi if [ "${luks_type}" = "luks1" ] && [ -n "${TOKEN_ID}" ]; then echo "${DEV} is a LUKS1 device; -t is only supported in LUKS2" >&2 exit 1 fi if [ -n "${EXISTING_TOKEN_ID}" ] && ! clevis_luks_luks2_existing_token_id_supported; then echo "Existing token ID not supported in this cryptsetup version" >&2 exit 1 fi # Get the existing passphrase/keyfile. existing_key= keyfile= case "${KEY}" in "") if [ -z "${EXISTING_TOKEN_ID}" ] ; then IFS= read -r -s -p "Enter existing LUKS password: " existing_key; echo >&2 fi ;; -) IFS= read -r -s -p "" existing_key ||: if [ "${luks_type}" = "luks1" ] && ! luksmeta test -d "${DEV}" \ && [ -z "${FRC}" ]; then echo "Cannot use '-k-' without '-f' or '-y' unless already initialized!" >&2 usage fi ;; *) keyfile="${KEY}" if [ ! -r "${keyfile}" ]; then echo "Cannot read key file '${keyfile}'" >&2 exit 1 fi ;; esac # Check if existing token id for keyring read is provided # If so, keyfile is not allowed if [ -n "${EXISTING_TOKEN_ID}" ] && [ -n "${keyfile}" ] ; then echo "Cannot specify kernel keyring description together with key file" >&2 exit 1 fi # If necessary, initialize the LUKS volume. if [ "${luks_type}" = "luks1" ] && ! luksmeta test -d "${DEV}"; then luksmeta init -d "${DEV}" ${FRC} fi if ! clevis_luks_do_bind "${DEV}" "${SLT}" "${TOKEN_ID}" \ "${PIN}" "${CFG}" \ "${YES}" "" \ "${existing_key}" "${keyfile}" "${EXISTING_TOKEN_ID}"; then echo "Error adding new binding to ${DEV}" >&2 exit 1 fi latchset-clevis-6df9b69/src/luks/clevis-luks-bind.1.adoc000066400000000000000000000050631520110721700231460ustar00rootroot00000000000000CLEVIS-LUKS-BIND(1) =================== :doctype: manpage == NAME clevis-luks-bind - Bind a LUKS device using the specified policy == SYNOPSIS *clevis luks bind* [-f] [-y] -d DEV [-t TKN_ID] [-s SLT] [-k KEY] [-e EXISTING_TOKEN_ID] PIN CFG == OVERVIEW The *clevis luks bind* command binds a LUKS device using the specified policy. This is accomplished with a simple command: $ clevis luks bind -d /dev/sda tang '{"url":...}' This command performs four steps: 1. Creates a new key with the same entropy as the LUKS master key -- maximum entropy bits is 256. 2. Encrypts the new key with Clevis. 3. Stores the Clevis JWE in the LUKS header. 4. Enables the new key for use with LUKS. This disk can now be unlocked with your existing password as well as with the Clevis policy. You will additionally need to enable one or more of the Clevis LUKS unlockers. See link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlockers*(7)]. == OPTIONS * *-f* : Do not prompt for LUKSMeta initialization * *-y* : Automatically answer yes for all questions. When using _tang_, it causes the advertisement trust check to be skipped, which can be useful in automated deployments * *-d* _DEV_ : The LUKS device on which to perform binding * *-s* _SLT_ : The LUKSMeta slot to use for metadata storage * *-t* _TKN_ID_ : The LUKS token ID to use; only available for LUKS2 * *-k* _KEY_ : Non-interactively read LUKS password from KEY file * *-k* - : Non-interactively read LUKS password from standard input * *-e* _E_TKN_ID_ : LUKS token ID for existing passphrase; only available for LUKS2. This parameter allows providing a configured token ID in LUKS2 containing the existing passphrase for this device, so that existing passphrase is not prompted by clevis == CAVEATS This command does not change the LUKS master key. This implies that if you create a LUKS-encrypted image for use in a Virtual Machine or Cloud environment, all the instances that run this image will share a master key. This is extremely dangerous and should be avoided at all cost. This is not a limitation of Clevis but a design principle of LUKS. If you wish to have encrypted root volumes in the cloud, you will need to make sure that you perform the OS install method for each instance in the cloud as well. The images cannot be shared without also sharing a master key. == SEE ALSO link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlockers*(7)], link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)], link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)], link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/luks/clevis-luks-common-functions.in000066400000000000000000001174531520110721700251000ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # CLEVIS_UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" # Length, in bytes, used for password generated for LUKS key # This value corresponds to an entropy of 256 bits if the password # was generated by pwmake or similar tool JOSE_PASSWORD_LENGTH=40 enable_debugging() { # Automatically enable debugging if in initramfs phase and rd.debug if [ -e /usr/lib/dracut-lib.sh ]; then local bashopts=$- # Because dracut is loosely written, disable hardening options temporarily [[ $bashopts != *u* ]] || set +u [[ $bashopts != *e* ]] || set +e . /usr/lib/dracut-lib.sh [[ $bashopts != *u* ]] || set -u [[ $bashopts != *e* ]] || set -e fi } enable_debugging # valid_slot() will check whether a given slot is possibly valid, i.e., if it # is a numeric value within the specified range. valid_slot() { local SLT="${1}" local MAX_SLOTS="${2}" case "${SLT}" in ''|*[!0-9]*) return 1 ;; *) # We got an integer, now let's make sure it is within the # supported range. if [ "${SLT}" -ge "${MAX_SLOTS}" ]; then return 1 fi ;; esac } # clevis_luks_read_slot() will read a particular slot of a given device, which # should be either LUKS1 or LUKS2. Returns 1 in case of failure; 0 in case of # success. clevis_luks_read_slot() { local DEV="${1}" local SLT="${2}" if [ -z "${DEV}" ] || [ -z "${SLT}" ]; then echo "Need both a device and a slot as arguments." >&2 return 1 fi local DATA_CODED='' local MAX_LUKS1_SLOTS=8 local MAX_LUKS2_SLOTS=32 if cryptsetup isLuks --type luks1 "${DEV}"; then if ! valid_slot "${SLT}" "${MAX_LUKS1_SLOTS}"; then echo "Please, provide a valid key slot number; 0-7 for LUKS1" >&2 return 1 fi if ! luksmeta test -d "${DEV}"; then echo "The ${DEV} device is not valid!" >&2 return 1 fi local uuid # Pattern from luksmeta: active slot uuid. read -r _ _ uuid <<< "$(luksmeta show -d "${DEV}" | grep "^${SLT} *")" if [ "${uuid}" != ${CLEVIS_UUID}"" ]; then echo "Not a clevis slot!" >&2 return 1 fi if ! DATA_CODED="$(luksmeta load -d "${DEV}" -s "${SLT}")"; then echo "Cannot load data from ${DEV} slot:${SLT}!" >&2 return 1 fi elif cryptsetup isLuks --type luks2 "${DEV}"; then if ! valid_slot "${SLT}" "${MAX_LUKS2_SLOTS}"; then echo "Please, provide a valid key slot number; 0-31 for LUKS2" >&2 return 1 fi local token_id token_id=$(cryptsetup luksDump "${DEV}" \ | grep -E -B1 "^\s+Keyslot:\s+${SLT}$" \ | sed -n 1p | sed -rn 's|^\s+([0-9]+): clevis|\1|p') if [ -z "${token_id}" ]; then echo "Cannot load data from ${DEV} slot:${SLT}. No token found!" >&2 return 1 fi local token token=$(cryptsetup token export --token-id "${token_id}" "${DEV}") DATA_CODED=$(jose fmt -j- -Og jwe -o- <<< "${token}" \ | jose jwe fmt -i- -c) if [ -z "${DATA_CODED}" ]; then echo "Cannot load data from ${DEV} slot:${SLT}!" >&2 return 1 fi else echo "${DEV} is not a supported LUKS device!" >&2 return 1 fi echo "${DATA_CODED}" } # clevis_luks_used_slots() will return the list of used slots for a given LUKS # device. clevis_luks_used_slots() { local DEV="${1:-}" [ -z "${DEV}" ] && return 1 local used_slots if cryptsetup isLuks --type luks1 "${DEV}"; then if ! used_slots=$(cryptsetup luksDump "${DEV}" 2>/dev/null \ | sed -rn 's|^Key Slot ([0-7]): ENABLED$|\1|p'); then return 1 fi elif cryptsetup isLuks --type luks2 "${DEV}"; then if ! used_slots=$(cryptsetup luksDump "${DEV}" 2>/dev/null \ | sed -rn 's|^\s+([0-9]+): luks2$|\1|p'); then return 1 fi else echo "${DEV} is not a supported LUKS device!" >&2 return 1 fi echo "${used_slots}" } # clevis_luks_decode_jwe() will decode a given JWE. clevis_luks_decode_jwe() { local jwe="${1}" local coded read -r -d . coded <<< "${jwe}" jose b64 dec -i- <<< "${coded}" } # clevis_luks_print_pin_config() will print the config of a given pin; i.e. # for tang it will display the associated url address, and for tpm2, the # properties in place, like the hash, for instance. clevis_luks_print_pin_config() { local P="${1}" local decoded="${2}" local content if ! content="$(jose fmt -j- -g clevis -g "${P}" -o- <<< "${decoded}")" \ || [ -z "${content}" ]; then return 1 fi local pin= case "${P}" in null) printf "null '{}'" ;; pkcs11) local uri uri="$(jose fmt -j- -g uri -u- <<< "${content}")" mechanism="$(jose fmt -j- -g mechanism -u- <<< "${content}")" if [ -z "${mechanism}" ]; then pin=$(printf '{"uri":"%s"}' "${uri}") else pin=$(printf '{"uri":"%s", "mechanism":"%s"}' "${uri}" "${mechanism}") fi printf "pkcs11 '%s'" "${pin}" ;; sss) local threshold threshold=$(jose fmt -j- -Og t -o- <<< "${content}") clevis_luks_process_sss_pin "${content}" "${threshold}" ;; tang) local url url="$(jose fmt -j- -g url -u- <<< "${content}")" pin=$(printf '{"url":"%s"}' "${url}") printf "tang '%s'" "${pin}" ;; tpm1) pcr_ids="$(jose fmt -j- -g pcr_ids -u- <<< "${content}")" pin=$(printf '"pcr_ids":"%s"' "${pcr_ids}") printf "tpm1 '{%s}'" "${pin}" ;; tpm2) # Valid properties for tpm2 pin are the following: # hash, key, pcr_bank, pcr_ids, pcr_digest. local key local value for key in 'hash' 'key' 'pcr_bank' 'pcr_ids' 'pcr_digest'; do if value=$(jose fmt -j- -g "${key}" -u- <<< "${content}"); then pin=$(printf '%s,"%s":"%s"' "${pin}" "${key}" "${value}") fi done # Remove possible leading comma. pin=${pin/#,/} printf "tpm2 '{%s}'" "${pin}" ;; *) printf "unknown pin '%s'" "${P}" ;; esac } # clevis_luks_decode_pin_config() will receive a JWE and extract a pin config # from it. clevis_luks_decode_pin_config() { local jwe="${1}" local decoded if ! decoded=$(clevis_luks_decode_jwe "${jwe}"); then return 1 fi local P if ! P=$(jose fmt -j- -Og clevis -g pin -u- <<< "${decoded}"); then return 1 fi clevis_luks_print_pin_config "${P}" "${decoded}" } # clevis_luks_join_sss_cfg() will receive a list of configurations for a given # pin and returns it as list, in the format PIN [cfg1, cfg2, ..., cfgN]. clevis_luks_join_sss_cfg() { local pin="${1}" local cfg="${2}" cfg=$(echo "${cfg}" | tr -d "'" | sed -e 's/^,//') printf '"%s":[%s]' "${pin}" "${cfg}" } # clevis_luks_process_sss_pin() will receive a JWE with information on the sss # pin config, and also its associated threshold, and will extract the info. clevis_luks_process_sss_pin() { local jwe="${1}" local threshold="${2}" local sss_null local sss_pkcs11 local sss_tang local sss_tpm1 local sss_tpm2 local sss local pin_cfg local pin local cfg local coded for coded in $(jose fmt -j- -Og jwe -Af- <<< "${jwe}"| tr -d '"'); do if ! pin_cfg="$(clevis_luks_decode_pin_config "${coded}")"; then continue fi read -r pin cfg <<< "${pin_cfg}" case "${pin}" in null) sss_null="${sss_null},${cfg}" ;; pkcs11) sss_pkcs11="${sss_pkcs11},${cfg}" ;; tang) sss_tang="${sss_tang},${cfg}" ;; tpm1) sss_tpm1="${sss_tpm1},${cfg}" ;; tpm2) sss_tpm2="${sss_tpm2},${cfg}" ;; sss) sss=$(echo "${cfg}" | tr -d "'") ;; esac done cfg= if [ -n "${sss_null}" ]; then cfg=$(clevis_luks_join_sss_cfg "null" "${sss_null}") fi if [ -n "${sss_tang}" ]; then cfg=$(clevis_luks_join_sss_cfg "tang" "${sss_tang}") fi if [ -n "${sss_tpm1}" ]; then cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm1" "${sss_tpm1}") fi if [ -n "${sss_tpm2}" ]; then cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm2" "${sss_tpm2}") fi if [ -n "${sss_pkcs11}" ]; then cfg="${cfg},"$(clevis_luks_join_sss_cfg "pkcs11" "${sss_pkcs11}") fi if [ -n "${sss}" ]; then cfg=$(printf '%s,"sss":%s' "${cfg}" "${sss}") fi # Remove possible leading comma. cfg=${cfg/#,/} pin=$(printf '{"t":%d,"pins":{%s}}' "${threshold}" "${cfg}") printf "sss '%s'" "${pin}" } # clevis_luks_read_pins_from_slot() will receive a given device and slot and # will then output its associated policy configuration. clevis_luks_read_pins_from_slot() { local DEV="${1}" local SLOT="${2}" local jwe if ! jwe=$(clevis_luks_read_slot "${DEV}" "${SLOT}" 2>/dev/null); then return 1 fi local cfg if ! cfg="$(clevis_luks_decode_pin_config "${jwe}")"; then return 1 fi printf "%s: %s\n" "${SLOT}" "${cfg}" } # clevis_luks_decode_used_pins() will receive a JWE and extract used pins # (line-separated, unsorted, not deduped) from it. clevis_luks_decode_used_pins() { local jwe="${1}" local pins= local decoded if ! decoded=$(clevis_luks_decode_jwe "${jwe}"); then return 1 fi local P if ! P=$(jose fmt -j- -Og clevis -g pin -u- <<< "${decoded}"); then return 1 fi pins=$(printf "%s\n%s" "${pins}" "${P}") if [ "${P}" = "sss" ]; then local sss_jwe if ! sss_jwe="$(jose fmt -j- -g clevis -g "${P}" -o- <<< "${decoded}")" \ || [ -z "${sss_jwe}" ]; then return 1 fi local coded sss_pins for coded in $(jose fmt -j- -Og jwe -Af- <<< "${sss_jwe}"| tr -d '"'); do if ! sss_pins="$(clevis_luks_decode_used_pins "${coded}")"; then continue fi pins=$(printf "%s\n%s" "${pins}" "${sss_pins}") done fi echo "${pins}" } # clevis_luks_read_used_pins_from_slot() will receive a given device and slot # and will then output space-separated sorted slot-prefixed list of used pins. clevis_luks_read_used_pins_from_slot() { local DEV="${1}" local SLOT="${2}" local jwe if ! jwe=$(clevis_luks_read_slot "${DEV}" "${SLOT}" 2>/dev/null); then return 1 fi local pins if ! pins=$(clevis_luks_decode_used_pins "${jwe}"); then return 1 fi pins=$(echo -n "${pins}" | sed -e '/^$/d' | sort -u | tr '\n' ' ' | sed -e 's/ $//') printf "%s: %s\n" "${SLOT}" "${pins}" } # clevis_luks_read_used_pins() will receive a given device and will then output # space-separated sorted list of all used pins in all slots. clevis_luks_read_used_pins() { local DEV="${1}" [ -z "${DEV}" ] && return 1 local used_slots if ! used_slots=$(clevis_luks_used_slots "${DEV}") \ || [ -z "${used_slots}" ]; then return 1 fi local pins= local slot slot_pins used_pins for slot in ${used_slots}; do if ! slot_pins=$(clevis_luks_read_used_pins_from_slot "${DEV}" "${slot}"); then continue fi read -r _ used_pins <<< "${slot_pins}" pins=$(printf "%s\n%s" "${pins}" "${used_pins}") done pins=$(echo -n "${pins}" | tr ' ' '\n' | sed -e '/^$/d' | sort -u | tr '\n' ' ' | sed -e 's/ $//') [ -z "${pins}" ] && return 1 echo "${pins}" } # clevis_luks_check_valid_key_or_keyfile() receives a devices and either a # passphrase or keyfile and then checks whether it is able to unlock the # device wih the received passphrase/keyfile. clevis_luks_check_valid_key_or_keyfile() { local DEV="${1}" local KEY="${2:-}" local KEYFILE="${3:-}" local SLT="${4:-}" local EXISTING_TOKEN_ID="${5:-}" [ -z "${DEV}" ] && return 1 local extra_args extra_args="$([ -n "${SLT}" ] && printf -- '--key-slot %s' "${SLT}")" # We have an empty key here. if [ -z "${EXISTING_TOKEN_ID}" ] && [ -z "${KEYFILE}" ] \ && [ -z "${KEY}" ]; then echo | cryptsetup open --force-password --test-passphrase "${DEV}" \ ${extra_args} return fi if [ -n "${KEYFILE}" ]; then cryptsetup open --test-passphrase "${DEV}" --key-file "${KEYFILE}" \ ${extra_args} return fi if [ -n "${EXISTING_TOKEN_ID}" ]; then cryptsetup open --test-passphrase "${DEV}" --token-id "${EXISTING_TOKEN_ID}" \ ${extra_args} return fi printf '%s' "${KEY}" | cryptsetup open --test-passphrase "${DEV}" \ ${extra_args} } # clevis_luks_unlock_device_by_slot() does the unlock of the device and slot # passed as parameters and returns the decoded passphrase. clevis_luks_unlock_device_by_slot() { local DEV="${1}" local SLT="${2}" local SKIP_CHECK="${3}" [ -z "${DEV}" ] && return 1 [ -z "${SLT}" ] && return 1 local jwe passphrase if ! jwe="$(clevis_luks_read_slot "${DEV}" "${SLT}" 2>/dev/null)" \ || [ -z "${jwe}" ]; then return 1 fi if ! passphrase="$(printf '%s' "${jwe}" | clevis decrypt)" \ || [ -z "${passphrase}" ]; then return 1 fi if [ -z "${SKIP_CHECK}" ]; then clevis_luks_check_valid_key_or_keyfile "${DEV}" "${passphrase}" || return 1 fi printf '%s' "${passphrase}" } # clevis_luks_unlock_device() does the unlock of the device passed as # parameter and returns the decoded passphrase. clevis_luks_unlock_device() { local DEV="${1}" local SKIP_CHECK="YES" [ -z "${DEV}" ] && return 1 local used_slots if ! used_slots=$(clevis_luks_used_slots "${DEV}") \ || [ -z "${used_slots}" ]; then return 1 fi local slt pt for slt in ${used_slots}; do if ! pt=$(clevis_luks_unlock_device_by_slot "${DEV}" "${slt}" "${SKIP_CHECK}") \ || [ -z "${pt}" ]; then continue fi printf '%s' "${pt}" return 0 done return 1 } # clevis_map_device() tries to map the device received as a parameter to a # block device. As per crypttab(5), we support /path/to/encrypted/blockdev # or UUID=. clevis_map_device() { local CDEV="${1}" if [[ "${CDEV}" == UUID=* ]]; then CDEV=/dev/disk/by-uuid/${CDEV#UUID=} fi if [[ "${CDEV}" == /* ]] && [ -b "${CDEV}" ]; then echo "${CDEV}" else # Invalid crypttab entry. return 1 fi } # clevis_is_luks_device_by_uuid_open() checks whether the LUKS device whose # UUID was passed as a parameter is already open. clevis_is_luks_device_by_uuid_open() { local dev_luks_uuid="${1}" [ -z "${dev_luks_uuid}" ] && return 1 dev_luks_uuid="$(echo "${dev_luks_uuid}" | sed -e 's/-//g')" test -b /dev/disk/by-id/dm-uuid-*"${dev_luks_uuid}"* } # clevis_devices_to_unlock() returns a list of devices to be unlocked, as per # the info from crypttab. clevis_devices_to_unlock() { local list_open_devices="${1:-}" [ ! -r /etc/crypttab ] && return 1 local dev clevis_devices crypt_device dev_uuid bindings clevis_devices= # Build list of devices to unlock. while read -r _volname_ crypt_device _; do # skip empty lines and lines which begin with the '#' char, per # crypttab(5) case $_volname_ in ''|\#*) continue ;; esac if ! dev=$(clevis_map_device "${crypt_device}") \ || [ -z "${dev}" ]; then # Unable to get the device - maybe it's not available, e.g. a # device on a volume group that has not been activated yet. # Add it to the list anyway, since it's a pending device. clevis_devices="${clevis_devices} ${crypt_device}" continue fi # Check if this device has clevis bindings. if ! bindings="$(clevis luks list -d "${dev}" 2>/dev/null)" \ || [ -z "${bindings}" ]; then continue fi if [ -z "${list_open_devices}" ]; then # Check if this device is already open. dev_uuid="$(cryptsetup luksUUID "${dev}")" if clevis_is_luks_device_by_uuid_open "${dev_uuid}"; then continue fi fi clevis_devices="${clevis_devices} ${dev}" done < /etc/crypttab echo "${clevis_devices}" | sed -e 's/^ //' } # clevis_luks1_save_slot() works with LUKS1 devices and it saves a given JWE # to a specific device and slot. The last parameter indicates whether we # should overwrite existing metadata. clevis_luks1_save_slot() { local DEV="${1}" local SLOT="${2}" local JWE="${3}" local SHOULD_OVERWRITE="${4:-}" luksmeta test -d "${DEV}" || return 1 if luksmeta load -d "${DEV}" -s "${SLOT}" -u "${CLEVIS_UUID}" \ >/dev/null 2>/dev/null; then [ -z "${SHOULD_OVERWRITE}" ] && return 1 if ! luksmeta wipe -f -d "${DEV}" -s "${SLOT}" \ -u "${CLEVIS_UUID}"; then echo "Error wiping slot ${SLOT} from ${DEV}" >&2 return 1 fi fi if ! echo -n "${JWE}" | luksmeta save -d "${DEV}" -s "${SLOT}" \ -u "${CLEVIS_UUID}"; then echo "Error saving metadata to LUKSMeta slot ${SLOT} from ${DEV}" >&2 return 1 fi if ! luksmeta test -d "${DEV}" 2>/dev/null >/dev/null ; then echo "Error detected after saving metadata to LUKSMeta slot ${SLOT}, device ${DEV}" >&2 return 1 fi return 0 } # clevis_luks2_save_slot() works with LUKS2 devices and it saves a given JWE # to a specific device and slot. The last parameter indicates whether we # should overwrite existing metadata. clevis_luks2_save_slot() { local DEV="${1}" local SLOT="${2}" local TKN_ID="${3}" local JWE="${4}" local SHOULD_OVERWRITE="${5:-}" # Sanitize clevis LUKS2 tokens. Remove "orphan" clevis tokens, i.e., # tokens that are not linked to any key slots. local token array_len for token in $(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^\s+([0-9]+): clevis|\1|p'); do # Let's check the length of the "keyslots" array. If zero, it means # no key slots are linked, which is a problem. if ! array_len=$(cryptsetup token export --token-id \ "${token}" "${DEV}" \ | jose fmt --json=- --get keyslots --array --length \ --output=-) || [ "${array_len}" -eq 0 ]; then # Remove bad token. cryptsetup token remove --token-id "${token}" "${DEV}" fi done if ! token="$(cryptsetup luksDump "${DEV}" \ | grep -E -B1 "^\s+Keyslot:\s+${SLOT}$" \ | sed -rn 's|^\s+([0-9]+): clevis|\1|p')"; then echo "Error trying to read token from LUKS2 device ${DEV}, slot ${SLOT}" >&2 return 1 fi if [ -n "${token}" ]; then [ -z "${SHOULD_OVERWRITE}" ] && return 1 if ! cryptsetup token remove --token-id "${token}" "${DEV}"; then echo "Error while removing token ${token} from LUKS2 device ${DEV}" >&2 return 1 fi fi if [ -n "${SHOULD_OVERWRITE}" ] && [ -n "${TKN_ID}" ]; then cryptsetup token remove --token-id "${TKN_ID}" "${DEV}" 2>/dev/null || : fi local metadata metadata=$(printf '{"type":"clevis","keyslots":["%s"],"jwe":%s}' \ "${SLOT}" "$(jose jwe fmt --input="${JWE}")") if ! printf '%s' "${metadata}" | cryptsetup token import \ $([ -n "${TKN_ID}" ] && printf -- '--token-id %s' "${TKN_ID}") \ "${DEV}"; then echo "Error saving metadata to LUKS2 header in device ${DEV}" >&2 return 1 fi return 0 } # clevis_luks_save_slot() saves a given JWE to a LUKS device+slot. It can also # overwrite existing metadata. clevis_luks_save_slot() { local DEV="${1}" local SLOT="${2}" local TKN_ID="${3}" local JWE="${4}" local SHOULD_OVERWRITE="${5:-}" if cryptsetup isLuks --type luks1 "${DEV}"; then clevis_luks1_save_slot "${DEV}" "${SLOT}" "${JWE}" \ "${SHOULD_OVERWRITE}" || return 1 elif cryptsetup isLuks --type luks2 "${DEV}"; then clevis_luks2_save_slot "${DEV}" "${SLOT}" "${TKN_ID}" "${JWE}" \ "${SHOULD_OVERWRITE}" || return 1 else return 1 fi return 0 } # clevis_luks1_backup_dev() backups the LUKSMeta slots from a LUKS device, # which can be restored with clevis_luks1_restore_dev(). clevis_luks1_backup_dev() { local DEV="${1}" local TMP="${2}" [ -z "${DEV}" ] && return 1 [ -z "${TMP}" ] && return 1 luksmeta test -d "${DEV}" || return 0 touch "${TMP}/initialized" local used_slots slt uuid jwe fname if ! used_slots=$(clevis_luks_used_slots "${DEV}") \ || [ -z "${used_slots}" ]; then return 1 fi for slt in ${used_slots}; do if ! uuid=$(luksmeta show -d "${DEV}" -s "${slt}") \ || [ -z "${uuid}" ]; then continue fi if ! jwe=$(luksmeta load -d "${DEV}" -s "${slt}") \ || [ -z "${jwe}" ]; then continue fi fname=$(printf "slot_%s_%s" "${slt}" "${uuid}") printf "%s" "${jwe}" > "${TMP}/${fname}" done return 0 } # clevis_luks1_restore_dev() takes care of restoring the LUKSMeta slots from # a LUKS device that was backup'ed by clevis_luks1_backup_dev(). clevis_luks1_restore_dev() { local DEV="${1}" local TMP="${2}" [ -z "${DEV}" ] && return 1 [ -z "${TMP}" ] && return 1 [ -e "${TMP}/initialized" ] || return 0 luksmeta test -d "${DEV}" || luksmeta init -f -d "${DEV}" local slt uuid jwe fname for fname in "${TMP}"/slot_*; do [ -f "${fname}" ] || break if ! slt=$(echo "${fname}" | cut -d '_' -f 2) \ || [ -z "${slt}" ]; then continue fi if ! uuid=$(echo "${fname}" | cut -d '_' -f 3) \ || [ -z "${uuid}" ]; then continue fi if ! jwe=$(cat "${fname}") || [ -z "${jwe}" ]; then continue fi if ! clevis_luks1_save_slot "${DEV}" "${slt}" \ "${jwe}" "overwrite"; then echo "Error restoring LUKSmeta slot ${slt} from ${DEV}" >&2 return 1 fi done return 0 } # clevis_luks_backup_dev() backups a particular LUKS device, which can then # be restored with clevis_luks_restore_dev(). clevis_luks_backup_dev() { local DEV="${1}" local TMP="${2}" [ -z "${DEV}" ] && return 1 [ -z "${TMP}" ] && return 1 printf '%s' "${DEV}" > "${TMP}/device" printf '%s' "${DEV}" > "${TMP}/device" local HDR HDR="${TMP}/$(basename "${DEV}").header" if ! cryptsetup luksHeaderBackup "${DEV}" --batch-mode \ --header-backup-file "${HDR}"; then echo "Error backing up LUKS header from ${DEV}" >&2 return 1 fi # If LUKS1, we need to manually back up (and later restore) the # LUKSmeta slots. For LUKS2, simply saving the header also saves # the associated tokens. if cryptsetup isLuks --type luks1 "${DEV}"; then if ! clevis_luks1_backup_dev "${DEV}" "${TMP}"; then return 1 fi fi return 0 } # clevis_luks_restore_dev() restores a given device that was backup'ed by # clevis_luks_backup_dev(). clevis_luks_restore_dev() { local TMP="${1}" [ -z "${TMP}" ] && return 1 [ -r "${TMP}"/device ] || return 1 local DEV DEV="$(cat "${TMP}"/device)" local HDR HDR="${TMP}/$(basename "${DEV}").header" if [ ! -e "${HDR}" ]; then echo "LUKS header backup does not exist" >&2 return 1 fi if ! cryptsetup luksHeaderRestore "${DEV}" --batch-mode \ --header-backup-file "${HDR}"; then echo "Error restoring LUKS header from ${DEV}" >&2 return 1 fi # If LUKS1, we need to manually back up (and later restore) the # LUKSmeta slots. For LUKS2, simply saving the header also saves # the associated tokens. if cryptsetup isLuks --type luks1 "${DEV}"; then if ! clevis_luks1_restore_dev "${DEV}" "${TMP}"; then return 1 fi fi return 0 } # clevis_luks_get_existing_key() may try to recover a valid password from # existing bindings and additionally prompt the user for the passphrase. clevis_luks_get_existing_key() { local DEV="${1}" local PROMPT="${2}" local RECOVER="${3:-}" [ -z "${DEV}" ] && return 1 local pt if [ -n "${RECOVER}" ] && pt="$(clevis_luks_unlock_device "${DEV}")" \ && [ -n "${pt}" ]; then printf '%s' "${pt}" return 0 fi # Let's prompt the user for the password. IFS= read -r -s -p "${PROMPT}" pt; echo >&2 # Check if key is valid. clevis_luks_check_valid_key_or_keyfile "${DEV}" "${pt}" || return 1 printf '%s' "${pt}" } # clevis_luks_luksmeta_sync_fix() makes sure LUKSmeta slots are sync'ed with # cryptsetup, in order to prevent issues when saving clevis metadata. clevis_luks_luksmeta_sync_fix() { local DEV="${DEV}" [ -z "${DEV}" ] && return 1 # This applies only to LUKS1 devices. cryptsetup isLuks --type luks1 "${DEV}" || return 0 # No issues if the LUKSmeta metadata is not initialized. luksmeta test -d "${DEV}" || return 0 local first_free_slot if ! first_free_slot=$(clevis_luks_first_free_slot "${DEV}") \ || [ -z "${first_free_slot}" ]; then echo "There are possibly no free slots in ${DEV}" >&2 return 1 fi # In certain circumstances, we may have LUKSMeta slots "not in sync" with # cryptsetup, which means we will try to save LUKSMeta metadata over an # already used or partially used slot -- github issue #70. # If that is the case, let's wipe the LUKSMeta slot here prior to using # the LUKSMeta slot. local lmeta_slot lmeta_status lmeta_uuid lmeta_slot="$(luksmeta show -d "${DEV}" | grep "^${first_free_slot}")" # 1 active cb6e8904-81ff-40da-a84a-07ab9ab5715e # 2 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e lmeta_status="$(echo "${lmeta_slot}" | awk '{print $2}')" [ "${lmeta_status}" != 'inactive' ] && return 0 lmeta_uuid="$(echo "${lmeta_slot}" | awk '{print $3}')" [ "${lmeta_uuid}" != "${CLEVIS_UUID}" ] && return 0 luksmeta wipe -f -d "${DEV}" -s "${first_free_slot}" } # clevis_luks_get_hash() returns the hash algorithm used by a LUKS device. clevis_luks_get_hash() { local DEV="${1}" [ -z "${DEV}" ] && return 1 local luks_type hash luks_type=$(clevis_luks_type "${DEV}") || return 1 case "${luks_type}" in luks1) hash=$(cryptsetup luksDump "${DEV}" 2>/dev/null \ | awk '/^Hash spec:[ \t]+/{print $3; exit}');; luks2) hash=$(cryptsetup luksDump "${DEV}" 2>/dev/null \ | awk '/^[[:space:]]+[0-9]+: luks/{in_slot=1} in_slot && /Hash:/{print $2; exit}');; *) return 1;; esac # Validate: only allow alphanumeric characters, hyphens and underscores. case "${hash}" in *[!a-zA-Z0-9_-]*|"") return 1;; esac printf '%s\n' "${hash}" } # clevis_luks_add_key() adds a new key to a key slot. clevis_luks_add_key() { local DEV="${1}" local SLT="${2}" local NEWKEY="${3}" local KEY="${4}" local KEYFILE="${5:-}" local EXISTING_TOKEN_ID="${6:-}" [ -z "${DEV}" ] && return 1 [ -z "${NEWKEY}" ] && return 1 local extra_args='' input input="$(printf '%s\n%s' "${KEY}" "${NEWKEY}")" if [ -n "${KEYFILE}" ]; then extra_args="$(printf -- '--key-file %s' "${KEYFILE}")" input="$(printf '%s' "${NEWKEY}")" fi if [ -n "${EXISTING_TOKEN_ID}" ]; then extra_args="$(printf -- '--token-id %s' "${EXISTING_TOKEN_ID}")" input="$(printf '%s' "${NEWKEY}")" fi local pbkdf_args="--pbkdf pbkdf2 --pbkdf-force-iterations 1000" local hash hash=$(clevis_luks_get_hash "${DEV}") if [ -n "${hash}" ]; then pbkdf_args="${pbkdf_args} --hash ${hash}" fi printf '%s' "${input}" | cryptsetup luksAddKey --force-password --batch-mode \ --key-slot "${SLT}" \ "${DEV}" \ ${pbkdf_args} \ ${extra_args} } # clevis_luks_update_key() will update a key slot with a new key. clevis_luks_update_key() { local DEV="${1}" local SLT="${2}" local NEWKEY="${3}" local KEY="${4}" local KEYFILE="${5:-}" local EXISTING_TOKEN_ID="${6:-}" [ -z "${DEV}" ] && return 1 [ -z "${NEWKEY}" ] && return 1 # Update the key slot with the new key. If we have the key for this slot, # the change happens in-place. Otherwise, we kill the slot and re-add it. local in_place clevis_luks_check_valid_key_or_keyfile "${DEV}" \ "${KEY}" "${KEYFILE}" \ "${SLT}" "${EXISTING_TOKEN_ID}" 2>/dev/null \ && in_place=true local input extra_args= input="$(printf '%s\n%s' "${KEY}" "${NEWKEY}")" if [ -n "${KEYFILE}" ]; then extra_args="$(printf -- '--key-file %s --force-password' "${KEYFILE}")" input="$(printf '%s' "${NEWKEY}")" fi if [ -n "${EXISTING_TOKEN_ID}" ]; then extra_args="$(printf -- '--token-id %s --force-password' "${EXISTING_TOKEN_ID}")" input="$(printf '%s' "${NEWKEY}")" fi local pbkdf_args="--pbkdf pbkdf2 --pbkdf-force-iterations 1000" local hash hash=$(clevis_luks_get_hash "${DEV}") if [ -n "${hash}" ]; then pbkdf_args="${pbkdf_args} --hash ${hash}" fi if [ -n "${in_place}" ]; then printf '%s' "${input}" | cryptsetup luksChangeKey "${DEV}" \ --key-slot "${SLT}" \ --batch-mode \ ${pbkdf_args} \ ${extra_args} return fi if ! printf '%s' "${input}" | cryptsetup luksKillSlot "${DEV}" \ "${SLT}" \ ${extra_args}; then echo "Error wiping slot ${SLT} from ${DEV}" >&2 return 1 fi clevis_luks_add_key "${DEV}" "${SLT}" "${NEWKEY}" "${KEY}" "${KEYFILE}" } # clevis_luks_save_key_to_slot() will save a new key to a slot. It can either # add a new key to a slot or updating an already used slot. clevis_luks_save_key_to_slot() { local DEV="${1}" local SLT="${2}" local NEWKEY="${3}" local KEY="${4}" local KEYFILE="${5:-}" local OVERWRITE="${6:-}" local EXISTING_TOKEN_ID="${7:-}" [ -z "${DEV}" ] && return 1 [ -z "${SLT}" ] && return 1 [ -z "${NEWKEY}" ] && return 1 # Make sure LUKSmeta slots are in sync with cryptsetup, to avoid the # problem reported in github issue #70. Applies to LUKS1 only. clevis_luks_luksmeta_sync_fix "${DEV}" # Let's check if we are adding a new key or updating an existing one. local update update="$(clevis_luks_used_slots "${DEV}" | grep "^${SLT}$")" if [ -n "${update}" ]; then # Replace an existing key. [ -n "${OVERWRITE}" ] || return 1 clevis_luks_update_key "${DEV}" "${SLT}" \ "${NEWKEY}" "${KEY}" "${KEYFILE}" "${EXISTING_TOKEN_ID}" return fi # Add a new key. clevis_luks_add_key "${DEV}" "${SLT}" \ "${NEWKEY}" "${KEY}" "${KEYFILE}" "${EXISTING_TOKEN_ID}" } # clevis_luks_generate_key() generates a new key for use with clevis. clevis_luks_generate_key() { local input input=$(printf '{"kty":"oct","bytes":%s}' "${JOSE_PASSWORD_LENGTH}") jose jwk gen --input="${input}" --output=- | \ jose fmt --json=- --object --get k --unquote=- } # clevis_luks_token_id_by_slot() returns the token ID linked to a # particular LUKS2 key slot. clevis_luks_token_id_by_slot() { local DEV="${1}" local SLT="${2}" [ -z "${DEV}" ] && return 1 [ -z "${SLT}" ] && return 1 cryptsetup isLuks --type luks1 "${DEV}" && echo && return local tkn_id tkn_id="$(cryptsetup luksDump "${DEV}" \ | grep -E -B1 "^\s+Keyslot:\s+${SLT}$" \ | sed -rn 's|^\s+([0-9]+): clevis|\1|p')" printf '%s' "${tkn_id}" } # clevis_luks_cleanup() removes the temporary directory used to store the data # relevant to device backup and restore. clevis_luks_cleanup() { [ -z "${CLEVIS_TMP_DIR}" ] && return 0 [ -d "${CLEVIS_TMP_DIR}" ] || return 0 if ! rm -rf "${CLEVIS_TMP_DIR}"; then echo "Deleting temporary files failed!" >&2 echo "You may need to clean up '${CLEVIS_TMP_DIR}'" >&2 exit 1 fi unset CLEVIS_TMP_DIR } # clevis_luks_first_free_slot() returns the first key slot that is available # in a LUKS device. clevis_luks_first_free_slot() { local DEV="${1}" [ -z "${DEV}" ] && return 1 local first_free_slot if cryptsetup isLuks --type luks1 "${DEV}"; then first_free_slot=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Key Slot ([0-7]): DISABLED$|\1|p' \ | sed -n 1p) elif cryptsetup isLuks --type luks2 "${DEV}"; then local used_slots slt used_slots="$(clevis_luks_used_slots "${DEV}")" for slt in {0..31}; do if ! echo "${used_slots}" | grep -q "^${slt}$"; then first_free_slot="${slt}" break fi done else echo "Unsupported device ${DEV}" >&2 return 1 fi echo "${first_free_slot}" } # clevis_luks_do_bind() creates or updates a particular binding. clevis_luks_do_bind() { local DEV="${1}" local SLT="${2}" local TKN_ID="${3}" local PIN="${4}" local CFG="${5}" local YES="${6:-}" local OVERWRITE="${7:-}" local KEY="${8:-}" local KEYFILE="${9:-}" local EXISTING_TOKEN_ID="${10:-}" [ -z "${DEV}" ] && return 1 [ -z "${PIN}" ] && return 1 [ -z "${CFG}" ] && return 1 if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" \ "${KEY}" \ "${KEYFILE}" \ "" \ "${EXISTING_TOKEN_ID}" \ && ! KEY="$(clevis_luks_get_existing_key "${DEV}" \ "Enter existing LUKS password: " \ "recover")"; then return 1 fi local newkey jwe if ! newkey="$(clevis_luks_generate_key)" || [ -z "${newkey}" ]; then echo "Unable to generate a new key" >&2 return 1 fi # Encrypt the new key. if ! jwe="$(printf '%s' "${newkey}" | clevis encrypt "${PIN}" "${CFG}" \ ${YES})" || [ -z "${jwe}" ]; then echo "Unable to perform encryption with PIN '${PIN}' and config '${CFG}'" >&2 return 1 fi # We can proceed to binding, after backing up the LUKS header and # metadata. local CLEVIS_TMP_DIR mkdir -p "${TMPDIR:-/tmp}" if ! CLEVIS_TMP_DIR="$(mktemp -d)" || [ -z "${CLEVIS_TMP_DIR}" ]; then echo "Unable to create a a temporary dir for device backup/restore" >&2 return 1 fi export CLEVIS_TMP_DIR trap 'clevis_luks_cleanup' EXIT # Backup LUKS header. if ! clevis_luks_backup_dev "${DEV}" "${CLEVIS_TMP_DIR}"; then echo "Unable to back up LUKS header from ${DEV}" >&2 return 1 fi if [ -z "${SLT}" ] && ! SLT=$(clevis_luks_first_free_slot "${DEV}") \ || [ -z "${SLT}" ]; then echo "Unable to find a free slot in ${DEV}" >&2 return 1 fi [ -z "${TKN_ID}" ] && ! TKN_ID="$(clevis_luks_token_id_by_slot "${DEV}" \ "${SLT}")" && return 1 if ! clevis_luks_save_key_to_slot "${DEV}" "${SLT}" \ "${newkey}" "${KEY}" "${KEYFILE}" \ "${OVERWRITE}" "${EXISTING_TOKEN_ID}"; then echo "Unable to save/update key slot; operation cancelled" >&2 clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || : rm -rf "${CLEVIS_TMP_DIR}" return 1 fi if ! clevis_luks_save_slot "${DEV}" "${SLT}" "${TKN_ID}" \ "${jwe}" "${OVERWRITE}"; then echo "Unable to update metadata; operation cancelled" >&2 clevis_luks_restore_dev "${CLEVIS_TMP_DIR}" || : rm -rf "${CLEVIS_TMP_DIR}" return 1 fi clevis_luks_cleanup trap - EXIT return 0 } # clevis_luks_luks2_supported() indicates whether we support LUKS2 devices. # Support is determined at build time. function clevis_luks_luks2_supported() { # We require cryptsetup >= 2.0.4 to fully support LUKSv2. return @OLD_CRYPTSETUP@ } # clevis_luks_luks2_existing_token_id_supported() indicates whether # cryptsetup allows token id for passphrase providing function clevis_luks_luks2_existing_token_id_supported() { # We require cryptsetup >= 2.6.0 to fully support LUKSv2 addkey/open by token ID return @OLD_CRYPTSETUP_EXISTING_TOKEN_ID@ } # clevis_luks_type() returns the LUKS type of a device, e.g. "luks1". clevis_luks_type() { local DEV="${1}" [ -z "${DEV}" ] && return 1 local luks_type if cryptsetup isLuks --type luks1 "${DEV}"; then luks_type="luks1" elif cryptsetup isLuks --type luks2 "${DEV}"; then clevis_luks_luks2_supported "${DEV}" || return 1 luks_type="luks2" else return 1 fi echo "${luks_type}" } # clevis_kill_pid() kills process and its children in a portable way # Works with both procps ps and Busybox ps. clevis_kill_pid() { local pid="$1" local child_pids [ -z "${pid}" ] && return 1 child_pids="$({ ps -Ao pid,ppid 2>/dev/null || ps -o pid,ppid 2>/dev/null || ps -Al 2>/dev/null || ps -l || { echo 'clevis: unable to get list of processes' >&2; exit 1; }; } | awk -v pid="$pid" ' NR==1 { for (i=1; i<=NF; i++) if ($i == "PID") pid_col = i; else if ($i == "PPID") ppid_col = i if (!pid_col || !ppid_col) { print "clevis: unable to find PID and/or PPID columns in ps output" | "cat >&2"; exit 1 } next } { if ($ppid_col == pid) print $pid_col }')" for kill_pid in $pid $child_pids; do kill "$kill_pid" 2>/dev/null done } latchset-clevis-6df9b69/src/luks/clevis-luks-edit000077500000000000000000000121111520110721700221060ustar00rootroot00000000000000#!/bin/bash -e # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Edit a binding from a clevis-bound slot in a LUKS device" usage() { exec >&2 echo "Usage: clevis luks edit [-f] -d DEV -s SLT [-c CONFIG]" echo echo "${SUMMARY}" echo echo "'clevis luks edit' uses the text editor defined in the EDITOR environment variable." echo " If EDITOR is not defined, it will attempt to use 'vi' as default editor." echo echo " -d DEV The LUKS device to edit clevis-bound pins" echo echo " -s SLOT The slot to use when editing the clevis binding" echo echo " -f Proceed with the edit operation even if the configuration is unchanged" echo echo " -c CONFIG The updated config to use" echo exit 1 } on_exit() { [ -d "${CLEVIS_EDIT_TMP}" ] && rm -rf "${CLEVIS_EDIT_TMP}" } validate_cfg() { local json="${1}" [ -z "${json}" ] && return 1 jose fmt --json="${json}" --object 2>/dev/null } edit_cfg() { local cfg_file="${1}" local editor="${EDITOR:-vi}" if ! command -v "${editor}" >/dev/null; then echo "Editor '${editor}' not found. " >&2 echo "Please define a valid text editor with the EDITOR environment variable." >&2 exit 1 fi "${editor}" "${cfg_file}" || true if ! validate_cfg "${cfg_file}"; then local ans= while true; do read -r -p \ "Malformed configuration. Would you like to edit again? [ynYN] " \ ans [ "${ans}" != "y" ] && [ "${ans}" != "Y" ] && return 1 break done edit_cfg "${cfg_file}" fi return 0 } if [ "${#}" -eq 1 ] && [ "${1}" = "--summary" ]; then echo "${SUMMARY}" exit 0 fi CFG= FRC= while getopts ":fd:s:c:" o; do case "$o" in d) DEV=${OPTARG};; s) SLT=${OPTARG};; c) CFG=${OPTARG};; f) FRC=-f;; *) usage;; esac done if [ -z "${DEV}" ]; then echo "Did not specify a device!" >&2 usage fi if [ -z "${SLT}" ]; then echo "Did not specify a slot!" >&2 usage fi if ! binding="$(clevis luks list -d "${DEV}" -s "${SLT}" 2>/dev/null)" \ || [ -z "${binding}" ]; then echo "Error retrieving current configuration from ${DEV}:${SLT}" >&2 exit 1 fi pin="$(echo "${binding}" | cut -d' ' -f2)" cfg="$(echo "${binding}" | cut -d' ' -f3 | sed -e "s/'//g")" if ! pretty_cfg="$(printf '%s' "${cfg}" | jq --monochrome-output .)" \ || [ -z "${pretty_cfg}" ]; then echo "Error reading the configuration from ${DEV}:${SLT}" >&2 exit 1 fi mkdir -p "${TMPDIR:-/tmp}" if ! CLEVIS_EDIT_TMP="$(mktemp -d)" || [ -z "${CLEVIS_EDIT_TMP}" ]; then echo "Creating a temporary dir for editing binding failed" >&2 exit 1 fi trap 'on_exit' EXIT if [ -z "${CFG}" ]; then CFG_FILE="${CLEVIS_EDIT_TMP}/cfg" echo "${pretty_cfg}" > "${CFG_FILE}" edit_cfg "${CFG_FILE}" || exit 1 if ! new_cfg="$(jq . -S < "${CFG_FILE}")" || [ -z "${new_cfg}" ]; then echo "Error reading the updated config for ${DEV}:${SLT}" >&2 exit 1 fi else if ! validate_cfg "${CFG}"; then echo "Invalid configuration given as parameter with -c" >&2 exit 1 fi new_cfg="$(printf '%s' "${CFG}" | jq --sort-keys --monochrome-output .)" fi if [ "${new_cfg}" = "$(printf '%s' "${pretty_cfg}" \ | jq --sort-keys --monochrome-output .)" ] \ && [ -z "${FRC}" ]; then echo "No changes detected; exiting" >&2 exit 1 fi if ! jcfg="$(jose fmt --json="${new_cfg}" --object --output=- 2>/dev/null)" \ || [ -z "${jcfg}" ]; then echo "Error preparing the configuration for the binding update" >&2 exit 1 fi if [ -z "${CFG}" ]; then printf "Pin: %s\nNew config:\n%s\n" "${pin}" "${new_cfg}" while true; do read -r -p \ "Would you like to proceed with the updated configuration? [ynYN] " \ ans [ "${ans}" != "y" ] && [ "${ans}" != "Y" ] && exit 0 break done fi # Remove temporary directory. rm -rf "${CLEVIS_EDIT_TMP}" echo "Updating binding..." if ! clevis_luks_do_bind "${DEV}" "${SLT}" "" "${pin}" "${new_cfg}" \ "-y" "overwrite"; then echo "Unable to update binding in ${DEV}:${SLT}. Operation cancelled." >&2 exit 1 fi echo "Binding edited successfully" >&2 latchset-clevis-6df9b69/src/luks/clevis-luks-edit.1.adoc000066400000000000000000000037021520110721700231550ustar00rootroot00000000000000CLEVIS-LUKS-EDIT(1) =================== :doctype: manpage == NAME clevis-luks-edit - Edit a binding from a clevis-bound slot in a LUKS device == SYNOPSIS *clevis luks edit* [-f] -d DEV -s SLT [-c CONFIG] == OVERVIEW The *clevis luks edit* command edits clevis bindings from a LUKS device. For example: clevis luks edit -d /dev/sda1 -s 1 == OPTIONS * *-d* _DEV_ : The LUKS device to edit clevis-bound pins * *-s* _SLT_ : The slot to use when editing the clevis binding * *-f* : Proceed with the edit operation even if the config is unchanged * *-c* _CONFIG_ : The updated config to use == EXAMPLES clevis luks list -d /dev/sda1 1: tang '{"url":"addr"}' As we can see in the example above, */dev/sda1* has one slots bound, in this case, to a _tang_ pin. We can edit this binding by issuing the following command: clevis luks edit -d /dev/sda1 -s 1 This will open a text editor -- the one set in the $EDITOR environment variable, or _vi_, as a fallback -- with the current configuration of this binding to be edited. In this case, we should have the following: { "url": "addr" } Once at the editor, we can edit the pin configuration. For _tang_, we could edit the _url_, for instance. After completing the change, save the file and exit. The updated configuration will be validated for JSON, and if there are no errors, you will be shown the updated configuration and prompted whether to proceed. By proceeding, the binding will be updated. There may be required to provide a valid LUKS passphrase for the device. In the second example, we will update the same device and slot, but we will be providing the updated configuration as well: clevis luks edit -d /dev/sda1 -s 1 -c '{"url":"new-addr-here"}' In this case, the binding update will be done in non-interactive mode. Note that it may also be required to provide a LUKS passphrase for the device. == SEE ALSO link:clevis-luks-list.1.adoc[*clevis-luks-list*(1)], latchset-clevis-6df9b69/src/luks/clevis-luks-list000077500000000000000000000044131520110721700221420ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017-2019 Red Hat, Inc. # Author: Javier Martinez Canillas # Author: Sergio Correia - LUKS2 support. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Lists pins bound to a LUKSv1 or LUKSv2 device" function usage() { echo >&2 echo "Usage: clevis luks list -d DEV [-s SLT] [-p]" >&2 echo >&2 echo "$SUMMARY": >&2 echo >&2 echo " -d DEV The LUKS device to list bound pins" >&2 echo >&2 echo " -s SLOT The slot number to list" >&2 echo >&2 echo " -p Print only a sorted space-separated list of used pins bound to the specified device" >&2 echo >&2 exit 1 } if [ ${#} -eq 1 ] && [ "${1}" = "--summary" ]; then echo "${SUMMARY}" exit 0 fi luks_function=clevis_luks_read_pins_from_slot while getopts ":d:s:p" o; do case "$o" in d) DEV=${OPTARG};; s) SLT=${OPTARG};; p) luks_function=clevis_luks_read_used_pins_from_slot;; *) usage;; esac done if [ -z "${DEV}" ]; then echo "Did not specify a device!" >&2 usage fi if cryptsetup isLuks --type luks1 "${DEV}"; then if ! luksmeta test -d "${DEV}" 2>/dev/null; then echo "The ${DEV} device is not valid!" >&2 exit 1 fi fi if [ -n "${SLT}" ]; then $luks_function "${DEV}" "${SLT}" else if ! used_slots=$(clevis_luks_used_slots "${DEV}"); then echo "No used slots detected for device ${DEV}!" >&2 exit 1 fi for s in ${used_slots}; do if ! $luks_function "${DEV}" "${s}"; then continue fi done fi latchset-clevis-6df9b69/src/luks/clevis-luks-list.1.adoc000066400000000000000000000036211520110721700232030ustar00rootroot00000000000000CLEVIS-LUKS-LIST(1) =================== :doctype: manpage == NAME clevis-luks-list - Lists pins bound to a LUKS device == SYNOPSIS *clevis luks list* -d DEV [-s SLT] == OVERVIEW The *clevis luks list* command list the pins bound to LUKS device. For example: clevis luks list -d /dev/sda1 == OPTIONS * *-d* _DEV_ : The LUKS device on which to list bound pins * *-s* _SLT_ : The slot to use for listing the pin from * *-p* : Print only a sorted space-separated list of bound pins == EXAMPLES clevis luks list -d /dev/sda1 1: sss '{"t":1,"pins":{"tang":[{"url":"addr1"},{"url":"addr2"}],"tpm2":[{"hash":"sha256","key":"ecc"}],"sss":{"t":1,"pins":{"tang":[{"url":"addr3"}]}}}}' 2: tang '{"url":"addr"}' 3: tpm2 '{"hash":"sha256","key":"ecc","pcr_bank":"sha1","pcr_ids":"7"}' clevis luks list -d /dev/sda1 -p 1: sss tang tpm2 2: tang 3: tpm2 As we can see in the example above, */dev/sda1* has three slots bound each with a different pin. - Slot #1 is bound with the _sss_ pin, and uses also tang and tpm2 pins in its policy. - Slot #2 is bound using the _tang_ pin - Slot #3 is bound with the _tpm2_ pin Note that the output of *clevis luks list* can be used with the *clevis luks bind* command, such as: clevis luks bind -d /dev/sda1 tpm2 '{"hash":"sha256","key":"ecc","pcr_bank":"sha1","pcr_ids":"7"}' And we will bind another slot with a policy similar to the one we have in slot #3. Also note that if you are interested in a particular slot, you can pass the _-s SLT_ argument to *clevis luks list*: clevis luks list -d /dev/sda1 -s 2 2: tang '{"url":"addr"}' In the above example, we listed only the pin bound to slot #2. == SEE ALSO link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)], link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)], link:clevis-encrypt-tpm2.1.adoc[*clevis-encrypt-tpm2*(1)], link:clevis-encrypt-sss.1.adoc[*clevis-encrypt-sss*(1)], latchset-clevis-6df9b69/src/luks/clevis-luks-pass000077500000000000000000000033411520110721700221340ustar00rootroot00000000000000#!/bin/bash -e # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Returns the LUKS passphrase used for binding a particular slot." usage() { exec >&2 echo "Usage: clevis luks pass -d DEV -s SLT" echo echo "$SUMMARY" echo echo " -d DEV The LUKS device to extract the LUKS passphrase used for binding" echo echo " -s SLOT The slot number to extract the LUKS passphrase" echo exit 1 } if [ ${#} -eq 1 ] && [ "${1}" = "--summary" ]; then echo "${SUMMARY}" exit 0 fi while getopts ":d:s:" o; do case "$o" in d) DEV=${OPTARG};; s) SLT=${OPTARG};; *) usage;; esac done if [ -z "${DEV}" ]; then echo "Did not specify a device!" >&2 usage fi if [ -z "${SLT}" ]; then echo "Did not specify a slot!" >&2 usage fi if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}"; then echo "It was not possible to decrypt the passphrase associated to slot ${SLT} in ${DEV}!" >&2 exit 1 fi latchset-clevis-6df9b69/src/luks/clevis-luks-pass.1.adoc000066400000000000000000000020121520110721700231670ustar00rootroot00000000000000CLEVIS-LUKS-PASS(1) =================== :doctype: manpage == NAME clevis-luks-pass - Extracts the passphrase used for binding a particular slot in a LUKS device == SYNOPSIS *clevis luks pass* -d DEV -s SLT == OVERVIEW The *clevis luks pass* command extracts the passphrase used for binding a particular slot in a LUKS device. For example: clevis luks pass -d /dev/sda1 -s 1 == OPTIONS * *-d* _DEV_ : The LUKS device on which to extract a passphrase from * *-s* _SLT_ : The slot to use for extracting the passphrase == EXAMPLE clevis luks pass -d /dev/sda1 -s 1 Note that the output of *clevis luks pass* might be non-printable, in which case it would be better to redirect its output to a file and use it as a key file together with cryptsetup. For instance: clevis luks pass -d /dev/sda1 -s 1 > slot1-passphrase And the file slot1-passphrase will contain the passphrase associated with slot #1 in /dev/sda1. == SEE ALSO link:clevis-luks-unlock.1.adoc[*clevis-luks-unlock*(1)], latchset-clevis-6df9b69/src/luks/clevis-luks-regen000077500000000000000000000047211520110721700222710ustar00rootroot00000000000000#!/bin/bash -e # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Radovan Sroka # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Regenerate clevis binding" if [ "${1}" = "--summary" ]; then echo "${SUMMARY}" exit 0 fi usage_and_exit () { exec >&2 echo "Usage: clevis luks regen [-q] -d DEV -s SLOT" echo echo "${SUMMARY}" echo echo " -d DEV The LUKS device on which to perform rebinding" echo echo " -s SLT The LUKS slot to use" echo echo " -q Do not prompt for confirmation" echo exit "${1}" } QOPT= while getopts ":hqd:s:" o; do case "${o}" in d) DEV="${OPTARG}";; h) usage_and_exit 0;; s) SLT="${OPTARG}";; q) QOPT="-q";; *) usage_and_exit 1;; esac done if [ -z "${DEV}" ]; then echo "Did not specify a device!" >&2 exit 1 fi if [ -z "${SLT}" ]; then echo "Did not specify a slot!" >&2 exit 1 fi # Get pin and configuration. if ! pin_cfg="$(clevis luks list -d "${DEV}" -s "${SLT}")" \ || [ -z "${pin_cfg}" ]; then exit 1 fi pin="$(echo "${pin_cfg}" | cut -d' ' -f2)" cfg="$(echo "${pin_cfg}" | cut -d' ' -f3 | sed -e "s/'//g")" if [ -z "${pin}" ] || [ -z "${cfg}" ]; then echo "Invalid pin or configuration" >&2 exit 1 fi echo "Regenerating binding (device ${DEV}, slot ${SLT}):" echo "Pin: ${pin}, Config: '${cfg}'" if [ -z "${QOPT}" ]; then read -r -p "Do you want to proceed? [ynYN] " ans [ "${ans}" != "y" ] && [ "${ans}" != "Y" ] && exit 0 fi if ! clevis_luks_do_bind "${DEV}" "${SLT}" "" "${pin}" "${cfg}" \ "-y" "overwrite"; then echo "Unable to regenerate binding in ${DEV}:${SLT}" >&2 exit 1 fi echo "Binding regenerated successfully" >&2 latchset-clevis-6df9b69/src/luks/clevis-luks-regen.1.adoc000066400000000000000000000026711520110721700233340ustar00rootroot00000000000000CLEVIS-LUKS-REGEN(1) ===================== :doctype: manpage == NAME clevis-luks-regen - Regenerates a clevis binding == SYNOPSIS *clevis luks regen* [-q] -d DEV -s SLT == OVERVIEW The *clevis luks regen* command regenerates the clevis binding for a given slot in a LUKS device, using the same configuration of the existing binding. Its operation can be compared to performing *clevis luks unbind* and *clevis luks bind* for rebinding said slot and device. This is useful when rotating tang keys. == OPTIONS * *-d* _DEV_ : The bound LUKS device * *-s* _SLT_ : The slot or key slot number for rebinding. Note that it requires that such slot is currently bound by clevis. * *-q*: Do not prompt for confirmation. == EXAMPLE Let's start by using clevis luks list to see the current binding configuration in /dev/sda1: # clevis luks list -d /dev/sda1 1: tang '{"url":"http://tang.server"}' 2: tpm2 '{"hash":"sha256","key":"ecc"}' We see that slot 1 in /dev/sda1 has a tang binding with the following configuration: '{"url":"http://tang.server"}' Now let's do the rebinding of slot 1: # clevis luks regen -d /dev/sda1 -s 1 After a successful operation, we will have the new binding using the same configuration that was already in place. == SEE ALSO link:clevis-luks-list.1.adoc[*clevis-luks-list*(1)] link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)] link:clevis-luks-unbind.1.adoc[*clevis-luks-unbind*(1)] latchset-clevis-6df9b69/src/luks/clevis-luks-report000077500000000000000000000136001520110721700225000ustar00rootroot00000000000000#!/bin/bash -e # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2018, 2020 Red Hat, Inc. # Author: Radovan Sroka # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Report tang keys' rotations" if [ "${1}" = "--summary" ]; then echo "${SUMMARY}" exit 0 fi report_compare() { local adv_keys="${1}" local mdata_keys="${2}" [ -z "${adv_keys}" ] && return 1 [ -z "${mdata_keys}" ] && return 1 local thp keys for thp in $(printf '%s' "${mdata_keys}" | jose jwk thp --input=-); do if ! printf '%s' "${adv_keys}" | jose jwk thp --input=- \ --find "${thp}" >/dev/null; then keys="$(printf '%s %s' "${keys}" "${thp}")" fi done printf '%s' "${keys}" } report_tang() { local content="${1}" [ -z "${content}" ] && return 1 local url if ! url="$(jose fmt --json="${content}" --get url --unquote=-)" \ || [ -z "${url}" ]; then echo "Invalid tang metadata; URL not found" >&2 return 1 fi local jws if ! jws="$(curl -sfg "${url}/adv")"; then echo "Unable to fetch advertisement (${url}/adv)" >&2 return 1 fi local adv_keys if ! adv_keys="$(jose fmt --json="${jws}" --object --get payload \ --string --b64load --object --get keys \ --array --unwind --output=-)"; then echo "Advertisement is malformed" >&2 return 1 fi # Check advertisement validity. local ver if ! ver="$(printf '%s' "${adv_keys}" | jose jwk use --input=- \ --required \ --use=verify \ --output=-)"; then echo "Unable to validate advertisement" >&2 return 1 fi if ! printf '%s' "${ver}" | jose jws ver --input="${jws}" --key=- \ --all; then echo "Advertisement is missing signatures" >&2 return 1 fi local mdata_keys if ! mdata_keys="$(jose fmt --json="${content}" --get adv --output=-)" \ || [ -z "${mdata_keys}" ]; then echo "Keys from clevis metadata not found" >&2 return 1 fi report_compare "${adv_keys}" "${mdata_keys}" } report_sss() { local content="${1}" [ -z "${content}" ] && return 1 local jwe for jwe in $(jose fmt --json="${content}" --get jwe --foreach=-); do jwe="$(printf '%s' "${jwe}" | sed -e 's/"//g')" report_decode "${jwe}" done } report_decode() { local data64="${1}" [ -z "${data64}" ] && return 1 local data if ! data="$(clevis_luks_decode_jwe "${data64}")" || [ -z "${data}" ]; then echo "Unable to decode metadata" >&2 exit 1 fi local pin if ! pin="$(jose fmt --json="${data}" --get clevis --get pin --unquote=-)" \ || [ -z "${pin}" ]; then echo "Pin not found in clevis metadata" >&2 exit 1 fi local content if ! content="$(jose fmt --json="${data}" --get clevis --get "${pin}" \ --output=-)" || [ -z "${content}" ]; then echo "Invalid pin metadata; no content found" >&2 return 1 fi case "${pin}" in tang) report_tang "${content}" ;; sss) report_sss "${content}" ;; esac } usage_and_exit () { exec >&2 echo "Usage: clevis luks report [-q] [-r] -d DEV -s SLOT" echo echo "${SUMMARY}" echo echo " -d DEV The LUKS device to check for key rotations" echo echo " -s SLT The LUKS slot to use" echo echo " -q Quiet mode; do not prompt for using 'clevis luks regen'" echo echo " -r Regenerate binding with 'clevis luks regen -q -d DEV -s SLOT'" echo exit "${1}" } while getopts "hd:s:rq" o; do case "${o}" in d) DEV="${OPTARG}";; h) usage_and_exit 0;; r) ROPT="regen";; s) SLT="${OPTARG}";; q) QOPT="quiet";; *) usage_and_exit 1;; esac done if [ -z "${DEV}" ]; then echo "Did not specify a device!" >&2 exit 1 fi if [ -z "${SLT}" ]; then echo "Did not specify a slot!" >&2 exit 1 fi if ! data64="$(clevis_luks_read_slot "${DEV}" "${SLT}")" \ || [ -z "${data64}" ]; then # Error message was already displayed by clevis_luks_read_slot(), # at this point. exit 1 fi if ! keys="$(report_decode "${data64}")"; then echo "Unable to verify whether there are rotated keys" >&2 exit 1 fi # No rotated keys. [ -z "${keys}" ] && exit 0 echo "The following keys are not in the current advertisement and were probably rotated:" for k in ${keys}; do printf ' %s\n' "${k}" done if [ -z "${QOPT}" ] && [ -z "${ROPT}" ]; then read -r -p "Do you want to regenerate the binding with \"clevis luks regen -q -d ${DEV} -s ${SLT}\"? [ynYN] " ans if [ "${ans}" = "y" ] || [ "${ans}" = "Y" ]; then ROPT="regen" fi fi if [ "${ROPT}" = "regen" ]; then if ! EXE="$(command -v clevis-luks-regen)" || [ -z "${EXE}" ]; then echo "Unable to find clevis luks regen" >&2 exit 1 fi exec "${EXE}" -q -d "${DEV}" -s "${SLT}" fi exit 1 latchset-clevis-6df9b69/src/luks/clevis-luks-report.1.adoc000066400000000000000000000015641520110721700235470ustar00rootroot00000000000000CLEVIS-LUKS-REPORT(1) ===================== :doctype: manpage == NAME clevis-luks-report - Reports whether a pin bound to a LUKS1 or LUKS2 volume has been rotated == SYNOPSIS *clevis luks report* -d DEV -s SLT == OVERVIEW The *clevis luks report* command checks a given slot of a LUKS device and reports whether the pin bound to it -- if any -- has been rotated. == OPTIONS * *-d* _DEV_ : The bound LUKS device * *-s* _SLT_ : The slot or key slot number for the pin to be verified * *-q* : Quiet mode. If used, we will not prompt whether to regenerate the binding with *clevis luks regen* * *-r* : Regenerates LUKS metadata with *clevis luks regen -q -d DEV -s SLT* == EXAMPLE Check whether the pin bound to slot 1 in /dev/sda1 has been rotated: # clevis luks report -d /dev/sda1 -s 1 == SEE ALSO link:clevis-luks-regen.1.adoc[*clevis-luks-regen*(1)] latchset-clevis-6df9b69/src/luks/clevis-luks-tpm1-functions.in000077500000000000000000000056541520110721700244730ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # start_tcsd() { [ -s /run/tcsd.pid ] && return 0 if ! ip link show up dev lo | grep -qw UP; then ip link set dev lo up && echo "lo" > /tmp/tcsd.if || : if ! ip link show up dev lo | grep -qw UP; then echo "Unable to set-up loopback network device" return 1 fi fi if ! temp_dir="$(mktemp -d)"; then echo "Unable to create temporary directory" return 1 fi fifo_file="$temp_dir/fifo" output_file="$temp_dir/output" # If we have udev, let the initialization on udev if ! [ -f /lib/udev/rules.d/60-tpm-udev.rules ]; then chown tss: /dev/tpm0 chmod 660 /dev/tpm0 fi mkfifo "$fifo_file" # Start timeout to finish TCSD startup sleep 10 & sleep_pid=$! # The following loop ends when output side of FIFO closes (i.e. TCSD ends) { while IFS= read -r LINE; do echo "$LINE" case "$LINE" in *"TCSD up and running"*) kill $sleep_pid 2>/dev/null ;; esac done < $fifo_file && kill $sleep_pid; } >> "$output_file" 2>&1 & # TCSD in background mode logs into syslogd, so we would not have any logs # available for debugging, so start TCSD in foreground mode, but as a # background job. Unfortunatelly the redirected output to pipe is # block-buffered (see `man 3 setbuf`), so in order to see any output we # need to set it to line-buffered with LD_PRELOAD library TCSD_NO_PRIVILEGE_DROP=${TCSD_NO_PRIVILEGE_DROP:-0} LD_PRELOAD="@libdir@/libclevis-tpm1-tcsd-preload.so" tcsd -f >$fifo_file 2>&1 & tcsd_pid=$! wait $sleep_pid 2>/dev/null if { ps -A 2>/dev/null || ps; } | awk -v pid="$tcsd_pid" '$1==pid {found=1} END {exit !found}'; then ret=0 echo $tcsd_pid > /run/tcsd.pid else ret=1 [ -s "$output_file" ] && cat "$output_file" fi rm -rf "$temp_dir" return $ret } stop_tcsd() { [ -s /run/tcsd.pid ] && { pid=$(cat /run/tcsd.pid) kill $pid >/dev/null 2>&1 || : rm -f /run/tcsd.pid } [ -s /tmp/tcsd.if ] && { ip link set dev lo down || : ip addr flush dev lo || : rm -f /tmp/tcsd.if } } latchset-clevis-6df9b69/src/luks/clevis-luks-unbind.1.adoc000066400000000000000000000011471520110721700235100ustar00rootroot00000000000000CLEVIS-LUKS-UNBIND(1) ===================== :doctype: manpage == NAME clevis-luks-unbind - Unbinds a pin bound to a LUKS volume == SYNOPSIS *clevis luks unbind* -d DEV -s SLT == OVERVIEW The *clevis luks unbind* command unbinds a pin bound to a LUKS volume. For example: $ clevis luks unbind -d /dev/sda -s 1 == OPTIONS * *-d* _DEV_ : The bound LUKS device * *-s* _SLT_ : The slot number for the pin to unbind. When using LUKSv1, this is the the LUKSmeta slot * *-f* : Do not ask for confirmation and wipe slot in batch-mode == SEE ALSO link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)] latchset-clevis-6df9b69/src/luks/clevis-luks-unbind.in000077500000000000000000000074021520110721700230540ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Javier Martinez Canillas # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # SUMMARY="Unbinds a pin bound to a LUKS volume" UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e # We require cryptsetup >= 2.0.4 to fully support LUKSv2. # Support is determined at build time. function luks2_supported() { return @OLD_CRYPTSETUP@ } function usage() { exec >&2 echo echo "Usage: clevis luks unbind -d DEV -s SLT" echo echo "$SUMMARY": echo echo " -d DEV The bound LUKS device" echo echo " -s SLOT The LUKS slot number for the pin unbind" echo echo " -f Do not ask for confirmation and wipe slot in batch-mode" echo exit 2 } if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi FRC=() while getopts ":d:s:f" o; do case "$o" in f) FRC+=(-q);; d) DEV="$OPTARG";; s) SLT="$OPTARG";; *) usage;; esac done if [ -z "$DEV" ]; then echo "Did not specify a device!" >&2 usage fi if [ -z "$SLT" ]; then echo "Did not specify a slot!" >&2 usage fi if ! cryptsetup isLuks "$DEV"; then echo "$DEV is not a LUKS device!" >&2 exit 1 fi if luks2_supported; then if cryptsetup isLuks --type luks1 "$DEV"; then luks_type="luks1" elif cryptsetup isLuks --type luks2 "$DEV";then luks_type="luks2" else echo "$DEV is not a supported LUKS device!" >&2 exit 1 fi else luks_type="luks1" fi if [ "$luks_type" == "luks1" ]; then if ! luksmeta test -d "$DEV" 2>/dev/null; then echo "The $DEV device does not contain a LUKSMeta header!" >&2 exit 1 fi read -r slot state uuid < <(luksmeta show -d "$DEV" | grep "^$SLT *") if [ "$uuid" == "empty" ]; then echo "The LUKSMeta slot $SLT on device $DEV is already empty." >&2 exit 1 fi [ "$state" == "active" ] && KILL=true elif [ "$luks_type" == "luks2" ]; then dump="$(cryptsetup luksDump "$DEV")" grep -q "^\s*$SLT: luks2" <<< "$dump" && KILL=true TOK="$(grep -E -B1 "^\s+Keyslot:\s+$SLT$" <<< "$dump" \ | sed -rn 's|^\s+([0-9]+): clevis|\1|p')" if [ -z "${TOK}" ]; then echo "No clevis slot detected on device ${DEV}:${SLT}!" >&2 exit 1 fi fi if [ -z "${FRC[*]}" ]; then echo "The unbind operation will wipe a slot. This operation is unrecoverable." >&2 read -r -p "Do you wish to erase LUKS slot $SLT on $DEV? [ynYN] " ans < /dev/tty [[ "$ans" =~ ^[yY]$ ]] || exit 0 fi if [ -n "$KILL" ]; then if ! cryptsetup luksKillSlot "$DEV" "$SLT" "${FRC[@]}"; then echo "LUKS slot $SLT for device $DEV couldn't be deleted" exit 1 fi fi if [ "$luks_type" == "luks1" ]; then if ! luksmeta wipe -f -d "$DEV" -u "$UUID" -s "$SLT"; then echo "LUKSMeta slot $SLT for device $DEV couldn't be deleted" exit 1 fi elif [ "$luks_type" == "luks2" ] && [ -n "$TOK" ]; then if ! cryptsetup token remove --token-id "$TOK" "$DEV"; then echo "Error while removing token $TOK from LUKS device $DEV!" >&2 exit 1 fi fi latchset-clevis-6df9b69/src/luks/clevis-luks-unlock000077500000000000000000000045131520110721700224630ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions SUMMARY="Unlocks a LUKS volume" function usage() { exec >&2 echo echo "Usage: clevis luks unlock -d DEV [-n NAME] [-t SLT]" echo echo "$SUMMARY": echo echo " -d DEV The LUKS device on which to perform unlocking" echo echo " -n NAME The name of the unlocked device node" echo echo " -t SLT Test the passphrase for the given slot without unlocking" echo " the device" echo echo " -o OPTS Pass options to underlying 'cryptsetup open'; be sure" echo " to quote the OPTS you pass, if they contain a space," echo " etc." echo exit 2 } if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi while getopts ":d:n:t:o:" o; do case "$o" in d) DEV="$OPTARG";; n) NAME="$OPTARG";; t) SLT="$OPTARG";; o) OPENARGS="$OPTARG";; *) usage;; esac done if [ -z "$DEV" ]; then echo "Did not specify a device!" >&2 usage fi if ! cryptsetup isLuks "$DEV"; then echo "$DEV is not a LUKS device!" >&2 exit 1 fi NAME="${NAME:-luks-"$(cryptsetup luksUUID "$DEV")"}" if [ -n "$SLT" ]; then if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}" >/dev/null; then echo "Test for token slot ${SLT} on device ${DEV} failed." >&2 exit 1 fi else if ! pt=$(clevis_luks_unlock_device "${DEV}"); then echo "${DEV} could not be opened." >&2 exit 1 fi echo -n "${pt}" | cryptsetup ${OPENARGS} open -d- "${DEV}" "${NAME}" fi latchset-clevis-6df9b69/src/luks/clevis-luks-unlock.1.adoc000066400000000000000000000013321520110721700235200ustar00rootroot00000000000000CLEVIS-LUKS-UNLOCK(1) ===================== :doctype: manpage == NAME clevis-luks-unlock - Unlocks a LUKS device bound with a Clevis policy == SYNOPSIS *clevis luks unlock* -d DEV [-n NAME] [-t SLT] == OVERVIEW The *clevis luks unlock* command unlocks a LUKS device using its already provisioned Clevis policy. For example: $ clevis luks unlock -d /dev/sda == OPTIONS * *-d* _DEV_ : The LUKS device to unlock * *-n* _NAME_ : The name to give the unlocked device node * *-t* _SLT_ : Test the passphrase for the given slot without unlocking the device * *-o* _PARAMS_ : Pass arbitrary parameters to cryptsetup; quote parameters as necessary == SEE ALSO link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)] latchset-clevis-6df9b69/src/luks/clevis-luks-unlockers.7.adoc000066400000000000000000000044651520110721700242520ustar00rootroot00000000000000CLEVIS-LUKS-UNLOCKERS(7) ======================== :doctype: manpage == NAME clevis-luks-unlockers - Overview of clevis luks unlockers == OVERVIEW Clevis provides unlockers for LUKS volumes which can use LUKS policy: * clevis-luks-unlock - Unlocks manually using the command line. * dracut - Unlocks automatically during early boot. * systemd - Unlocks automatically during late boot. * udisks2 - Unlocks automatically in a GNOME desktop session. Once a LUKS volume is bound using *clevis luks bind*, it can be unlocked using any of the above unlockers without using a password. == MANUAL UNLOCKING You can unlock a LUKS volume manually using the following command: $ sudo clevis luks unlock -d /dev/sda For more information, see link:clevis-luks-unlock.1.adoc[*clevis-luks-unlock*(1)]. == EARLY BOOT UNLOCKING If Clevis integration does not already ship in your initramfs, you may need to rebuild your initramfs with this command: $ sudo dracut -f Once Clevis is integrated into your initramfs, a simple reboot should unlock your root volume. Note, however, that early boot integration only works for the root volume. Non-root volumes should use the late boot unlocker. Dracut will not bring up your network by default. You can either have it come up via DHCP by using rd.neednet=1 in kernel cmdline or you can specify custom network parameters, such as static IP configuration, please consult the dracut documentation. DHCP can be easily added to early boot by setting it in a configuration file and rebuilding initramfs afterwards $ echo 'kernel_cmdline="rd.neednet=1"' | sudo tee /etc/dracut.conf.d/clevis.conf $ sudo dracut -f == LATE BOOT UNLOCKING You can enable late boot unlocking by executing the following command: $ sudo systemctl enable clevis-luks-askpass.path After a reboot, Clevis will attempt to unlock all devices listed in */etc/crypttab* that have clevis bindings when systemd prompts for their passwords. == DESKTOP UNLOCKING When the udisks2 unlocker is installed, your GNOME desktop session should unlock LUKS removable devices configured with Clevis automatically. You may need to restart your desktop session after installation for the unlocker to be loaded. == SEE ALSO link:clevis-luks-unlock.1.adoc[*clevis-luks-unlock*(1)] link:clevis-luks-bind.1.adoc[*clevis-luks-bind*(1)] latchset-clevis-6df9b69/src/luks/dracut/000077500000000000000000000000001520110721700202625ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-null/000077500000000000000000000000001520110721700233035ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-null/meson.build000066400000000000000000000006371520110721700254530ustar00rootroot00000000000000dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-null' configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) else warning('Will not install dracut module clevis-pin-null due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-null/module-setup.sh.in000077500000000000000000000016171520110721700266770ustar00rootroot00000000000000#!/bin/bash # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # depends() { echo clevis return 0 } install() { inst clevis-decrypt-null } latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-pkcs11/000077500000000000000000000000001520110721700234335ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-pkcs11/clevis-pkcs11-hook.sh.in000077500000000000000000000017041520110721700277240ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . /usr/bin/clevis-pkcs11-common if [ ! -f /run/systemd/clevis-pkcs11.run ] && [ -d /run/systemd ]; then clevis_start_pcscd_server echo "" >/run/systemd/clevis-pkcs11.run @libexecdir@/clevis-luks-pkcs11-askpin -d -r fi latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-pkcs11/clevis-pkcs11-prehook.sh000077500000000000000000000017241520110721700300300ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # if [ ! -f /run/systemd/clevis-pkcs11.pre.run ] && [ -d /run/systemd ]; then clevis-pkcs11-afunix-socket-unlock -l /run/systemd/clevis-pkcs11-dracut.log -f /run/systemd/clevis-pkcs11.sock -s 60 & echo "" > /run/systemd/clevis-pkcs11.pre.run fi latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-pkcs11/meson.build000066400000000000000000000012011520110721700255670ustar00rootroot00000000000000dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-pkcs11' configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) configure_file( input: 'clevis-pkcs11-hook.sh.in', output: 'clevis-pkcs11-hook.sh', install_dir: dracutdir, configuration: data, ) install_data('clevis-pkcs11-prehook.sh', install_dir: dracutdir) else warning('Will not install dracut module clevis-pin-pkcs11 due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-pkcs11/module-setup.sh.in000077500000000000000000000041371520110721700270270ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2024 Red Hat, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # shellcheck disable=SC2154 # check() { require_binaries pcscd pkcs11-tool clevis-decrypt-pkcs11 || return 1 require_binaries awk head sed socat tail tr || return 1 return 0 } depends() { echo clevis return 255 } install() { inst_hook pre-trigger 60 "${moddir}/clevis-pkcs11-prehook.sh" inst_hook initqueue/settled 60 "${moddir}/clevis-pkcs11-hook.sh" inst_hook initqueue/online 60 "${moddir}/clevis-pkcs11-hook.sh" inst_multiple \ awk \ pcscd \ pkcs11-tool \ head \ sed \ socat \ tail \ tr \ /usr/lib64/pcsc/drivers/ifd-ccid.bundle/Contents/Linux/libccid.so \ /usr/lib64/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist \ /usr/lib64/opensc-pkcs11.so \ /usr/lib64/pkcs11/opensc-pkcs11.so \ /usr/lib64/libopensc.so* \ /etc/opensc.conf \ /usr/lib64/ossl-modules/legacy.so \ /lib64/libpcsclite.so.1 \ @libexecdir@/clevis-luks-pkcs11-askpass \ @libexecdir@/clevis-luks-pkcs11-askpin \ clevis-luks-common-functions \ clevis-pkcs11-afunix-socket-unlock \ clevis-pkcs11-common \ clevis-decrypt-pkcs11 # Include libraries from configuration automatically . /usr/bin/clevis-pkcs11-common for file in $(clevis_get_module_path_from_pkcs11_config | sed -e 's@;@\n@g'); do inst_multiple ${file} done dracut_need_initqueue } latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-sss/000077500000000000000000000000001520110721700231415ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-sss/meson.build000066400000000000000000000006351520110721700253070ustar00rootroot00000000000000dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-sss' configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) else warning('Will not install dracut module clevis-pin-sss due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-sss/module-setup.sh.in000077500000000000000000000016161520110721700265340ustar00rootroot00000000000000#!/bin/bash # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # depends() { echo clevis return 0 } install() { inst clevis-decrypt-sss } latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-tang/000077500000000000000000000000001520110721700232625ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-tang/meson.build000066400000000000000000000006371520110721700254320ustar00rootroot00000000000000dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-tang' configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) else warning('Will not install dracut module clevis-pin-tang due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-tang/module-setup.sh.in000077500000000000000000000025141520110721700266530ustar00rootroot00000000000000#!/bin/bash # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # depends() { echo clevis network return 0 } have_tang_bindings() { . clevis-luks-common-functions local dev for dev in $(clevis_devices_to_unlock "list-open-devices"); do if clevis luks list -d "${dev}" -p | grep -q tang; then return 0 fi done return 1 } install() { if [ "${hostonly_cmdline}" = "yes" ] && have_tang_bindings; then echo "rd.neednet=1" > "${initdir}/etc/cmdline.d/99clevis-pin-tang.conf" fi inst_multiple \ clevis-decrypt-tang \ curl } latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-tpm1/000077500000000000000000000000001520110721700232125ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-tpm1/meson.build000066400000000000000000000006371520110721700253620ustar00rootroot00000000000000dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-tpm1' configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) else warning('Will not install dracut module clevis-pin-tpm1 due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in000077500000000000000000000137151520110721700266100ustar00rootroot00000000000000#!/bin/bash # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # require_file() { local path="$1" if ! [ -f "$path" ]; then # shellcheck disable=SC2154 # $moddir is a dracut variable local _module_name="${moddir##*/[0-9][0-9]}" dinfo "dracut module '${_module_name}' will not be installed, because file '$path' could not be found!" return 1 fi return 0 } require_files() { local _ret=0 for path in "$@"; do require_file "$path" || ((_ret++)) done return "$_ret" } require_dir() { local path="$1" if ! [ -d "$path" ]; then local _module_name="${moddir##*/[0-9][0-9]}" dinfo "dracut module '${_module_name}' will not be installed, because directory '$path' could not be found!" return 1 fi return 0 } require_nonempty_dir() { local path="$1" require_dir "$path" || return 1 # See https://superuser.com/a/667095 how to test empty dir files=$(shopt -s nullglob; shopt -u dotglob; echo "$path/"*) if ! [[ "$files" ]]; then local _module_name="${moddir##*/[0-9][0-9]}" dinfo "dracut module '${_module_name}' will not be installed, because directory '$path' is empty!" return 1 fi return 0 } check() { local _module_name="${moddir##*/[0-9][0-9]}" require_binaries clevis-decrypt-tpm1 tpm_version tpm_unsealdata tcsd || return 1 if [[ $hostonly ]]; then require_nonempty_dir /var/lib/tpm || return 1 else [ -f /usr/share/trousers/system.data.auth ] || \ [ -f /var/lib/tpm/system.data.auth ] || \ ddebug " ${_module_name}: no usable system.data.auth in /usr/share/trousers nor /var/lib/tpm found, using built-in one" fi if dracut_module_included "systemd"; then # shellcheck disable=SC2154 # $systemdsystemunitdir is a dracut variable require_files \ "$systemdsystemunitdir"/tcsd.service \ "$systemdsystemunitdir"/tcsd.service.d/clevis-tcsd.conf \ || return 1 fi return 0 } depends() { echo clevis network return 0 } install() { if dracut_module_included "systemd"; then inst_multiple \ "$systemdsystemunitdir/tcsd.service" \ "$systemdsystemunitdir/tcsd.service.d/clevis-tcsd.conf" # shellcheck disable=SC2154 # $initdir is a dracut variable systemctl -q --root "$initdir" add-wants cryptsetup.target tcsd.service else inst_multiple \ awk chmod chown mkfifo mktemp ip ps \ @libdir@/libclevis-tpm1-tcsd-preload.so \ @libexecdir@/clevis-luks-tpm1-functions fi inst_multiple \ clevis-decrypt-tpm1 \ tcsd \ tpm_version \ tpm_unsealdata inst_rules 60-tpm-udev.rules if ! [[ $hostonly ]] || ! dracut_module_included "systemd"; then # /etc/hosts is installed only in host-only mode with systemd, so # we need to create our own in order to get tpm tools working. # The localhost entry is required by tpm tools. if [ ! -f "$initdir/etc/hosts" ]; then echo "127.0.0.1 localhost" >> "$initdir/etc/hosts" echo "::1 localhost ip6-localhost ip6-loopback" >> "$initdir/etc/hosts" echo "ff02::1 ip6-allnodes" >> "$initdir/etc/hosts" echo "ff02::2 ip6-allrouters" >> "$initdir/etc/hosts" fi fi if [[ $hostonly ]]; then inst /etc/tcsd.conf inst_multiple /var/lib/tpm/* else inst_dir /etc touch "$initdir/etc/tcsd.conf" if [ -f "/etc/tcsd.conf" ] && [[ $(stat -c "0%a" "/etc/tcsd.conf") = "0600" ]]; then # Compatibility with tcsd version 0.3.14 chmod 0600 "$initdir/etc/tcsd.conf" chown tss:tss "$initdir/etc/tcsd.conf" else chmod 0640 "$initdir/etc/tcsd.conf" chown root:tss "$initdir/etc/tcsd.conf" fi inst_dir /var/lib/tpm if [ -f /usr/share/trousers/system.data.auth ]; then inst /usr/share/trousers/system.data.auth /var/lib/tpm/system.data elif [ -f /var/lib/tpm/system.data.auth ]; then inst /var/lib/tpm/system.data.auth /var/lib/tpm/system.data else jose b64 dec -i- >"$initdir/var/lib/tpm/system.data" < # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # check() { require_binaries clevis-decrypt-tpm2 || return 1 if command -v clevis-pin-tpm2 >/dev/null; then require_binaries clevis-pin-tpm2 || return 1 else require_binaries tpm2_createprimary tpm2_flushcontext \ tpm2_load tpm2_unseal tpm2_getcap || return 1 require_any_binary tpm2_pcrread tpm2_pcrlist || return 1 fi return 0 } depends() { echo clevis return 0 } install_tpm2_tools() { inst_multiple tpm2_createprimary tpm2_flushcontext \ tpm2_load tpm2_unseal tpm2_getcap inst_multiple -o tpm2_pcrread tpm2_pcrlist } install() { inst_multiple clevis-decrypt-tpm2 inst_libdir_file "libtss2-tcti-device.so*" if command -v clevis-pin-tpm2 >/dev/null; then inst_multiple clevis-pin-tpm2 else install_tpm2_tools fi } installkernel() { hostonly='' instmods =drivers/char/tpm } latchset-clevis-6df9b69/src/luks/dracut/clevis/000077500000000000000000000000001520110721700215475ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/dracut/clevis/clevis-cleanup-hook.sh000077500000000000000000000015111520110721700257540ustar00rootroot00000000000000#!/bin/sh # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . /bin/clevis-cleanup latchset-clevis-6df9b69/src/luks/dracut/clevis/clevis-cleanup.in000077500000000000000000000021611520110721700250140ustar00rootroot00000000000000#!/bin/bash # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . [ -s /run/clevis.pid ] || exit 0 . clevis-luks-common-functions if [ -f @libexecdir@/clevis-luks-tpm1-functions ]; then . @libexecdir@/clevis-luks-tpm1-functions stop_tcsd fi pid=$(cat /run/clevis.pid) clevis_kill_pid $pid rm -f /run/clevis.pid rm -f /run/clevis-online rm -rf /run/cryptroot-ask-pipes latchset-clevis-6df9b69/src/luks/dracut/clevis/clevis-online-hook.sh000077500000000000000000000014021520110721700256100ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # touch /run/clevis-online latchset-clevis-6df9b69/src/luks/dracut/clevis/clevis-password-unlocker-hook.sh000077500000000000000000000014171520110721700300140ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # /bin/clevis-password-unlocker-prepare latchset-clevis-6df9b69/src/luks/dracut/clevis/clevis-password-unlocker-prepare.in000077500000000000000000000041101520110721700304770ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . /lib/dracut-crypt-lib.sh . clevis-luks-common-functions mkdir -p /run/cryptroot-ask-pipes chmod 0700 /run/cryptroot-ask-pipes pipeprefix=run/cryptroot-ask-pipes ensurestarted=0 # shellcheck disable=SC2154 # $hookdir is a dracut variable for askpass in "$hookdir"/initqueue/settled/cryptroot-ask-*.sh; do [ ! -e "${askpass}" ] && continue if device=$(grep '^[^ ]*/cryptroot-ask ' "${askpass}" | awk '{print $2}'); then if [ "${1##/dev/dm-}" != "$1" ]; then device="/dev/mapper/$(dmsetup info -c --noheadings -o name "$1")" fi getkey /tmp/luks.keys "$device" >/dev/null && continue detectedfile="/tmp/clevis-device-detected-${device//\//_}" [ -f "${detectedfile}" ] && continue : >> "${detectedfile}" pins=$(clevis_luks_read_used_pins "${device}") || continue if [[ " $pins " == *" tang "* ]] && getargbool 0 rd.neednet && [ ! -f /run/clevis-online ]; then mv -f "${askpass}" "${askpass/settled/online}" fi pipepath="${pipeprefix}/pipe-${device//\//_}" echo "rd.luks.key=/:${pipepath}:${device}" >> /etc/cmdline.d/60-clevis-keys.conf echo "${device}:/:${pipepath}" >> /tmp/luks.keys mkfifo "/${pipepath}" ensurestarted=1 fi done [ $ensurestarted -eq 1 ] && [ ! -s /run/clevis.pid ] && /bin/clevis-password-unlocker latchset-clevis-6df9b69/src/luks/dracut/clevis/clevis-password-unlocker.in000077500000000000000000000132051520110721700270500ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2017 Red Hat, Inc. # Copyright (c) 2017 Shawn Rose # Copyright (c) 2017 Guilhem Moulin # # Author: Harald Hoyer # Author: Nathaniel McCallum # Author: Shawn Rose # Author: Guilhem Moulin # Based-on: src/initramfs-tools/scripts/local-top/clevis.in # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . /lib/dracut-lib.sh . /lib/dracut-crypt-lib.sh . clevis-luks-common-functions # Return fifo path or nothing if not found get_device_fifo_path() { local device="$1" local tmp [ -z "${device}" ] && return 0 if tmp=$(getkey /tmp/luks.keys "$device"); then keydev="${tmp%%:*}" keypath="${tmp#*:}" [ "${keydev}" != "/" ] && return 1 [ "${keypath#run/cryptroot-ask-pipes/}" = "${keypath}" ] && return 1 echo "/${keypath}" return 0 fi return 1 } # Gets the luks device to be unlocked and used pins get_pid_device_pins() { local pid="$1" local CRYPTTAB_SOURCE CRYPTTAB_SOURCE=$(tr '\0' '\n' 2>/dev/null 3 { exit }') # Wrong process, no CRYPTTAB_SOURCE, return error [ -n "$CRYPTTAB_SOURCE" ] || return 1 [ -b "$CRYPTTAB_SOURCE" ] || return 0 local cache="/var/cache/clevis-disks/${CRYPTTAB_SOURCE//\//_}" if [ ! -f "$cache" ]; then local pins if ! pins=$(clevis_luks_read_used_pins "$CRYPTTAB_SOURCE"); then return 1 fi echo "${CRYPTTAB_SOURCE}:${pins}" > "$cache" fi cat "$cache" return 0 } # Print colon-separated password-asking info like device, pins and fifo # path for unlocking with password get_askpass_info() { local psinfo pf dev_pins psinfo=$(ps -A 2>/dev/null || ps) # Doing this so I don't end up matching myself echo "$psinfo" | awk '/cryptroot-ask/ { print $1 }' | { while read -r pid; do if dev_pins=$(get_pid_device_pins "${pid}") && pf=$(get_device_fifo_path "${dev_pins%%:*}"); then if [[ $pf != "" && $dev_pins != "" ]]; then # Output only in case of clevis device echo "${dev_pins}:${pf}" fi # Return that we found valid process return 0 fi done return 1 } } # Try to decrypt the password to fifo file luks_decrypt() { local CRYPTTAB_SOURCE=$1 local PASSFIFO=$2 local pt if pt=$(clevis_luks_unlock_device "${CRYPTTAB_SOURCE}"); then echo -n "${pt}" >"${PASSFIFO}" return 0 else return 1 fi } # Wait for askpass, and then try and decrypt immediately. Just in case # there are multiple devices that need decrypting, this will loop # infinitely (The local-bottom script will kill this after decryption) clevisloop() { local askpass_info local sleep_time local OLD_CRYPTTAB_SOURCE="" local tpm1cfg_attempted=0 while true; do # Re-get the askpass PID in case there are multiple encrypted devices CRYPTTAB_SOURCE="" sleep_time=.1 until [ -n "$CRYPTTAB_SOURCE" ] && [ -p "$PASSFIFO" ]; do sleep $sleep_time if askpass_info=$(get_askpass_info); then IFS=':' read -r CRYPTTAB_SOURCE pins PASSFIFO < "${PASSFIFO}" sleep 5 fi done } do_configure_tpm1() { local tcsd_output= [ -x @bindir@/clevis-decrypt-tpm1 ] && [ -f @libexecdir@/clevis-luks-tpm1-functions ] || return . @libexecdir@/clevis-luks-tpm1-functions info "Starting TCSD daemon" if ! tcsd_output=$(TCSD_NO_PRIVILEGE_DROP=0 start_tcsd 2>&1); then if [ -n "$tcsd_output" ]; then echo "Unable to start TCSD: $tcsd_output" | vwarn else warn "Unable to start TCSD" fi fi } mkdir -p /var/cache/clevis-disks chmod 0700 /var/cache/clevis-disks clevisloop & echo $! >/run/clevis.pid latchset-clevis-6df9b69/src/luks/dracut/clevis/meson.build000066400000000000000000000023031520110721700237070ustar00rootroot00000000000000dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() dracut_data = configuration_data() dracut_data.merge_from(data) dracut_data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: dracut_data, ) configure_file( input: 'clevis-cleanup.in', output: 'clevis-cleanup', install_dir: dracutdir, configuration: dracut_data, ) configure_file( input: 'clevis-password-unlocker.in', output: 'clevis-password-unlocker', install_dir: dracutdir, configuration: dracut_data, ) configure_file( input: 'clevis-password-unlocker-prepare.in', output: 'clevis-password-unlocker-prepare', install_dir: dracutdir, configuration: dracut_data, ) install_data('clevis-cleanup-hook.sh', install_dir: dracutdir) install_data('clevis-password-unlocker-hook.sh', install_dir: dracutdir) install_data('clevis-online-hook.sh', install_dir: dracutdir) else warning('Will not install dracut module due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/dracut/clevis/module-setup.sh.in000077500000000000000000000051141520110721700251370ustar00rootroot00000000000000#!/bin/bash # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # depends() { local __depends="crypt bash" if dracut_module_included "systemd"; then # Dracut v103 introduced a separate systemd-cryptsetup module systemd_cryptsetup_dir=$(dracut_module_path "systemd-cryptsetup") if [ -d "$systemd_cryptsetup_dir" ]; then __depends=$(printf '%s systemd-cryptsetup' "${__depends}") else __depends=$(printf '%s systemd' "${__depends}") fi fi echo "${__depends}" return 255 } install() { if dracut_module_included "systemd"; then inst_multiple \ "$systemdsystemunitdir"/clevis-luks-askpass.service \ "$systemdsystemunitdir"/clevis-luks-askpass.path \ "$systemdsystemunitdir"/cryptsetup.target \ @SYSTEMD_REPLY_PASS@ \ @libexecdir@/clevis-luks-askpass systemctl -q --root "$initdir" add-wants cryptsetup.target clevis-luks-askpass.path else inst_hook initqueue/settled 60 "$moddir"/clevis-password-unlocker-hook.sh inst_hook initqueue/online 60 "$moddir"/clevis-online-hook.sh inst_hook cleanup 60 "$moddir"/clevis-cleanup-hook.sh inst_script "$moddir"/clevis-cleanup /bin/clevis-cleanup inst_script "$moddir"/clevis-password-unlocker /bin/clevis-password-unlocker inst_script "$moddir"/clevis-password-unlocker-prepare /bin/clevis-password-unlocker-prepare inst_multiple \ clevis-luks-unlock \ blkid fi inst_multiple \ /etc/services \ clevis-luks-common-functions \ awk date ps sort touch tr \ grep sed cut \ clevis-decrypt \ clevis-luks-list \ cryptsetup \ luksmeta \ clevis \ mktemp \ mkdir \ jose dracut_need_initqueue } latchset-clevis-6df9b69/src/luks/dracut/meson.build000066400000000000000000000003331520110721700224230ustar00rootroot00000000000000subdir('clevis') subdir('clevis-pin-tang') if not get_option('tpm1').disabled() subdir('clevis-pin-tpm1') endif subdir('clevis-pin-tpm2') subdir('clevis-pin-sss') subdir('clevis-pin-null') subdir('clevis-pin-pkcs11') latchset-clevis-6df9b69/src/luks/meson.build000066400000000000000000000072251520110721700211500ustar00rootroot00000000000000 luksmeta_data = configuration_data() luksmeta = dependency('luksmeta', version: '>=8', required: false) libcryptsetup = dependency('libcryptsetup', version: '>=2.0.4', required: false) if libcryptsetup.found() luksmeta_data.set('OLD_CRYPTSETUP', '0') else libcryptsetup = dependency('libcryptsetup', version: '>=2.0.2', required: false) if libcryptsetup.found() luksmeta_data.set('OLD_CRYPTSETUP', '1') warning('Old version of cryptsetup found, forcing use of luksmeta') endif endif libcryptsetup_ext_token_id = dependency('libcryptsetup', version: '>=2.6.0', required: false) if libcryptsetup_ext_token_id.found() luksmeta_data.set('OLD_CRYPTSETUP_EXISTING_TOKEN_ID', '0') message('cryptsetup version supports existing token id') else luksmeta_data.set('OLD_CRYPTSETUP_EXISTING_TOKEN_ID', '1') warning('cryptsetup version does not support existing token id') endif clevis_luks_common_functions = configure_file( input: 'clevis-luks-common-functions.in', output: 'clevis-luks-common-functions', configuration: luksmeta_data ) if not get_option('tpm1').disabled() clevis_luks_tpm1_functions = configure_file( input: 'clevis-luks-tpm1-functions.in', output: 'clevis-luks-tpm1-functions', configuration: data ) endif clevis_luks_unbind = configure_file(input: 'clevis-luks-unbind.in', output: 'clevis-luks-unbind', configuration: luksmeta_data) # SystemD dependencies checked here, used both in systemd and dracut subdirs systemd = dependency('systemd', required: false) systemdutildir = systemd.found() ? systemd.get_pkgconfig_variable('systemdutildir', default: '') : '' sd_reply_pass = find_program( (systemdutildir != '') ? join_paths(systemdutildir, 'systemd-reply-password') : '', join_paths(get_option('prefix'), get_option('libdir'), 'systemd', 'systemd-reply-password'), join_paths(get_option('prefix'), 'lib', 'systemd', 'systemd-reply-password'), join_paths('/', 'usr', get_option('libdir'), 'systemd', 'systemd-reply-password'), join_paths('/', 'usr', 'lib', 'systemd', 'systemd-reply-password'), required: false ) if libcryptsetup.found() and luksmeta.found() subdir('systemd') subdir('dracut') subdir('udisks2') bins += clevis_luks_unbind mans += join_paths(meson.current_source_dir(), 'clevis-luks-unbind.1') bins += join_paths(meson.current_source_dir(), 'clevis-luks-bind') mans += join_paths(meson.current_source_dir(), 'clevis-luks-bind.1') mans += join_paths(meson.current_source_dir(), 'clevis-luks-unlockers.7') bins += clevis_luks_common_functions bins += join_paths(meson.current_source_dir(), 'clevis-luks-list') mans += join_paths(meson.current_source_dir(), 'clevis-luks-list.1') bins += join_paths(meson.current_source_dir(), 'clevis-luks-unlock') mans += join_paths(meson.current_source_dir(), 'clevis-luks-unlock.1') bins += join_paths(meson.current_source_dir(), 'clevis-luks-regen') mans += join_paths(meson.current_source_dir(), 'clevis-luks-regen.1') bins += join_paths(meson.current_source_dir(), 'clevis-luks-report') mans += join_paths(meson.current_source_dir(), 'clevis-luks-report.1') bins += join_paths(meson.current_source_dir(), 'clevis-luks-edit') mans += join_paths(meson.current_source_dir(), 'clevis-luks-edit.1') bins += join_paths(meson.current_source_dir(), 'clevis-luks-pass') mans += join_paths(meson.current_source_dir(), 'clevis-luks-pass.1') if not get_option('tpm1').disabled() install_data(clevis_luks_tpm1_functions, install_dir: libexecdir) endif else warning('Will not install LUKS support due to missing dependencies!') endif # Tests. if not meson.is_cross_build() subdir('tests') endif latchset-clevis-6df9b69/src/luks/systemd/000077500000000000000000000000001520110721700204705ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-askpass.in000077500000000000000000000046171520110721700247370ustar00rootroot00000000000000#!/bin/bash set -eu # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Harald Hoyer # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions # Make sure to exit cleanly if SIGTERM is received. trap 'echo "Exiting due to SIGTERM" && exit 0' TERM loop= path=/run/systemd/ask-password while getopts ":lp:" o; do case "${o}" in l) loop=true;; p) path="${OPTARG}";; *) ;; esac done while true; do for question in "${path}"/ask.*; do # question will expand to itself, in case no files match, so we verify # whether it actually exists, before proceeding. [ ! -e "${question}" ] && continue d= s= while read -r line; do case "$line" in Id=cryptsetup:*) d="${line##Id=cryptsetup:}";; Socket=*) s="${line##Socket=}";; esac done < "$question" [ -e "${d}" ] || continue [ -S "${s}" ] || continue if ! pt="$(clevis_luks_unlock_device "${d}")" || [ -z "${pt}" ]; then continue fi uuid="$(cryptsetup luksUUID "${d}")" if ! printf '%s' "${pt}" | @SYSTEMD_REPLY_PASS@ 1 "${s}"; then echo "Unable to unlock ${d} (UUID=${uuid}) with recovered passphrase" >&2 continue fi echo "Unlocked ${d} (UUID=${uuid}) successfully" >&2 done [ "${loop}" != true ] && break # Checking for pending devices to be unlocked. remaining_crypttab=$(clevis_devices_to_unlock) ||: remaining_askfiles=$(ls "${path}"/ask.* 2>/dev/null) ||: if [ -z "${remaining_crypttab}" ] && [ -z "${remaining_askfiles}" ]; then break; fi sleep 0.5 done latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-askpass.path000066400000000000000000000005371520110721700252570ustar00rootroot00000000000000[Unit] Description=Forward Password Requests to Clevis Directory Watch Documentation=man:clevis-luks-unlockers(7) DefaultDependencies=no Before=cryptsetup-pre.target Wants=cryptsetup-pre.target Before=shutdown.target Conflicts=shutdown.target [Path] DirectoryNotEmpty=/run/systemd/ask-password MakeDirectory=yes [Install] WantedBy=cryptsetup.target latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-askpass.service.in000066400000000000000000000004111520110721700263570ustar00rootroot00000000000000[Unit] Description=Forward Password Requests to Clevis Documentation=man:clevis-luks-unlockers(7) DefaultDependencies=no @TCSD_AFTER@ @TCSD_WANTS@ Before=shutdown.target Conflicts=shutdown.target [Service] Type=simple ExecStart=@libexecdir@/clevis-luks-askpass -l latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-pkcs11-askpass.in000077500000000000000000000020001520110721700260170ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # @libexecdir@/clevis-luks-pkcs11-askpin -r & # Wait 60 seconds to attend key requests from systemd # If control socket starts receiving information, this time is cancelled clevis-pkcs11-afunix-socket-unlock -l /run/systemd/clevis-pkcs11-systemd.log -f /run/systemd/clevis-pkcs11.sock -s 60 latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-pkcs11-askpass.service.in000066400000000000000000000002651520110721700274660ustar00rootroot00000000000000[Unit] Description=Unencrypt through PKCS11 DefaultDependencies=no PartOf=clevis-luks-pkcs11-askpass.socket [Service] Type=simple ExecStart=@libexecdir@/clevis-luks-pkcs11-askpass latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-pkcs11-askpass.socket000066400000000000000000000004751520110721700267140ustar00rootroot00000000000000[Unit] Description=Clevis PKCS11 socket handler Requires=clevis-luks-pkcs11-askpass.service After=cryptsetup-pre.target sockets.target systemd-ask-password-wall.service [Socket] ListenDatagram=/run/systemd/clevis-pkcs11.sock [Install] WantedBy=cryptsetup-pre.target sockets.target systemd-ask-password-wall.service latchset-clevis-6df9b69/src/luks/systemd/clevis-luks-pkcs11-askpin.in000077500000000000000000000137441520110721700256600ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . clevis-luks-common-functions . clevis-pkcs11-common pkcs11_device="" dracut_mode=false retry_mode=false too_many_errors=3 while getopts ":dr" o; do case "${o}" in d) dracut_mode=true;; r) retry_mode=true;; *) ;; esac done get_pkcs11_error() { if journalctl -u clevis-luks-pkcs11-askpass.service -b 0 | tail -3 \ | egrep -E "A TPM2 device.{1,}needed" >/dev/null 2>&1; then echo "ERROR:TPM2 device not found. " elif journalctl -u clevis-luks-pkcs11-askpass.service -b 0 | tail -3 \ | egrep -E "Error.{1,}server" >/dev/null 2>&1; then echo "ERROR:Tang communication error. " elif journalctl -u clevis-luks-pkcs11-askpass.service -b 0 | tail -3 \ | grep "Invalid PIN" >/dev/null 2>&1; then echo "ERROR:Invalid PIN. " else echo "ERROR:Unknown error. " fi return 0 } clevis_start_pcscd_server if [ "${dracut_mode}" != true ]; then pkcs11-tool -L fi devices_array=() # Let's analyze all entries from /etc/crypttab that contain clevis-pkcs11.sock entries while read -r line; do if echo "${line}" | grep -E "clevis-pkcs11.sock" 1>/dev/null; then next_device=0 errors=0 msg="" # Store passphrases to send to control socket systemd_device=$(echo "${line}" | awk '{print $1}') while [ ${next_device} -ne 1 ]; do uuid=$(echo "${line}" | awk '{print $2}') if ! mapped_device=$(clevis_map_device "${uuid}"); then echo "Could not check mapped device for UID:${uuid}" next_device=1 continue fi if [ "${dracut_mode}" != true ]; then if grep "${mapped_device}" /run/systemd/clevis-pkcs11-dracut.devices; then next_device=1 continue fi fi # If no PKCS#11 configuration, advance to next device if ! clevis luks list -d "${mapped_device}" | grep pkcs11 >/dev/null 2>&1; then echo "Device:${mapped_device} does not contain PKCS#11 configuration" >&2 # Send a wrong passphrase echo -n "${systemd_device},NOPASSWORDFOR${systemd_device}" | socat UNIX-CONNECT:/run/systemd/clevis-pkcs11.control.sock - next_device=1 continue fi if ! pkcs11_device=$(clevis_detect_pkcs11_device "${dracut_mode}" "${retry_mode}"); then echo "No PKCS11 device detected" >&2 exit 0 else echo "Detected PKCS11 device:${pkcs11_device}" >&2 fi # Get configuration PKCS#11 URI uri=$(clevis luks list -d "${mapped_device}" | awk -F '"uri":' '{print $2}' | awk -F '"' '{print $2}' | awk -F '"' '{print $1}') slot_opt="" if ! slot=$(clevis_get_pkcs11_final_slot_from_uri "${uri}"); then echo "Could not find slot for uri:${uri}" >&2 else slot_opt="--slot-index ${slot}" fi module_opt="" module=$(clevis_get_module_path_from_uri "${uri}") if [ -n "${module}" ]; then module_opt="--module ${module}" fi echo "Device:${mapped_device}, slot_opt:${slot_opt}, module_opt:${module_opt}" if ! pkcs11-tool -O ${module_opt} ${slot_opt} 2>/dev/null 1>/dev/null; then echo "No objects on slot:${slot}, module_opt:${module_opt}" >&2 echo -n "${systemd_device},NOPASSWORDFOR${systemd_device}" | socat UNIX-CONNECT:/run/systemd/clevis-pkcs11.control.sock - next_device=1 continue fi if ! model=$(clevis_get_model_from_uri "${uri}"); then if ! model="device with serial number:$(clevis_get_serial_from_uri ${uri})"; then model=${pkcs11_device} fi fi if ! pin=$(clevis_get_pin_value_from_uri "${uri}"); then pin=$(systemd-ask-password "${msg}Please, insert PIN for ${model} (${uuid}):") fi # Get key from PKCS11 pin here and feed AF_UNIX socket program echo "${pin}" > /run/systemd/clevis-pkcs11.pin if ! passphrase=$(clevis_luks_unlock_device "${mapped_device}") || [ -z "${passphrase}" ]; then echo "Could not unlock device:${mapped_device}" >&2 msg="$(get_pkcs11_error)" ((errors++)) if [ ${errors} -eq ${too_many_errors} ]; then echo "Too many errors !!!" >&2 next_device=1 fi continue fi next_device=1 echo "Device:${mapped_device} unlocked successfully by clevis" >&2 if [ "${dracut_mode}" == true ]; then echo "${mapped_device}" >> /run/systemd/clevis-pkcs11-dracut.devices fi # Store passphrases to send to control socket devices_array+=("${systemd_device},${passphrase}") done fi done < <(grep -v "^#" /etc/crypttab) # Send passphrases to control socket for ((ix=${#devices_array[*]}-1; ix>=0; ix--)) do echo -n "${devices_array[$ix]}" | socat UNIX-CONNECT:/run/systemd/clevis-pkcs11.control.sock - done latchset-clevis-6df9b69/src/luks/systemd/clevis-tcsd.conf000066400000000000000000000006701520110721700235620ustar00rootroot00000000000000[Unit] DefaultDependencies=no # /var/lib/tpm is required to run RequiresMountsFor=/var/lib/tpm # systemd-remount-fs is required, it makes root read-write # systemd-modules-load is just to be sure that all required modules are loaded # No Wants=, we want to start after them, but not start them - initrd not necessarily have them After=systemd-remount-fs.service systemd-modules-load.service Before=shutdown.target Conflicts=shutdown.target latchset-clevis-6df9b69/src/luks/systemd/meson.build000066400000000000000000000033121520110721700226310ustar00rootroot00000000000000if systemd.found() and sd_reply_pass.found() systemd_data = configuration_data() systemd_data.merge_from(data) systemd_data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) if not get_option('tpm1').disabled() systemd_data.set('TCSD_AFTER', 'After=tcsd.service') systemd_data.set('TCSD_WANTS', 'Wants=tcsd.service') else systemd_data.set('TCSD_AFTER', '') systemd_data.set('TCSD_WANTS', '') endif unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') configure_file( input: 'clevis-luks-askpass.service.in', output: 'clevis-luks-askpass.service', install_dir: unitdir, configuration: systemd_data, ) configure_file( input: 'clevis-luks-pkcs11-askpass.service.in', output: 'clevis-luks-pkcs11-askpass.service', install_dir: unitdir, configuration: systemd_data, ) configure_file( input: 'clevis-luks-askpass.in', output: 'clevis-luks-askpass', install_dir: libexecdir, configuration: systemd_data ) configure_file( input: 'clevis-luks-pkcs11-askpass.in', output: 'clevis-luks-pkcs11-askpass', install_dir: libexecdir, configuration: systemd_data ) configure_file( input: 'clevis-luks-pkcs11-askpin.in', output: 'clevis-luks-pkcs11-askpin', install_dir: libexecdir, configuration: systemd_data ) install_data('clevis-luks-askpass.path', install_dir: unitdir) install_data('clevis-luks-pkcs11-askpass.socket', install_dir: unitdir) if not get_option('tpm1').disabled() tcsdoverridedir = join_paths(unitdir, 'tcsd.service.d') install_data('clevis-tcsd.conf', install_dir: tcsdoverridedir) endif else warning('Will not install systemd support due to missing dependencies!') endif latchset-clevis-6df9b69/src/luks/tests/000077500000000000000000000000001520110721700201425ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/tests/assume-yes000077500000000000000000000064161520110721700221720ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" cfg=$(printf '{"url":"%s"}' "$url") test_tang() { local url="${1}" local cfg data pt cfg=$(printf '{"url":"%s"}' "$url") for data in "foo" "bar" "foo bar" "some-password-here"; do if ! pt="$(echo "${data}" | clevis encrypt tang "${cfg}" -y \ | clevis decrypt)"; then error "${TEST}: tang - encrypt should succeed." fi if [ "${pt}" != "${data}" ]; then error "${TEST}: tang - pt(${pt}) != data("${data}")." fi done } test_sss() { local url="${1}" local sss1 sss2 data pt sss1=$(printf '{"t":1, "pins": {"tang": [{"url": "%s"}]}}' "${url}") sss2=$(printf '{"t":2, "pins": {"tang": [{"url": "%s"}, {"url": "%s"}]}}' \ "${url}" "${url}") for data in "foo" "bar" "foo bar" "some-password-here"; do if ! pt="$(echo "${data}" | clevis encrypt sss "${sss1}" -y \ | clevis decrypt)"; then error "${TEST}: sss1 - encrypt should succeed." fi if [ "${pt}" != "${data}" ]; then error "${TEST}: sss1 - pt(${pt}) != data("${data}")." fi if ! pt="$(echo "${data}" | clevis encrypt sss "${sss2}" -y \ | clevis decrypt)"; then error "${TEST}: sss2 - encrypt should succeed." fi if [ "${pt}" != "${data}" ]; then error "${TEST}: sss2 - pt(${pt}) != data("${data}")." fi done } test_tang "${url}" test_sss "${url}" # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" # tang. if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded (tang - $DEV)." fi if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we were unable to unlock ${DEV} (tang)." fi # sss. new_device "luks1" "${DEV}" sss=$(printf '{"t":2, "pins": {"tang": [{"url": "%s"}, {"url": "%s"}]}}' \ "${url}" "${url}") if ! clevis luks bind -y -d "${DEV}" sss "${sss}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded (sss - $DEV)." fi if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we were unable to unlock ${DEV} (sss)." fi latchset-clevis-6df9b69/src/luks/tests/assume-yes-luks2000077500000000000000000000035521520110721700232260ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" cfg=$(printf '{"url":"%s"}' "$url") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" # tang. if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded (tang - $DEV)." fi if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we were unable to unlock ${DEV} (tang)." fi # sss. new_device "luks2" "${DEV}" sss=$(printf '{"t":2, "pins": {"tang": [{"url": "%s"}, {"url": "%s"}]}}' \ "${url}" "${url}") if ! clevis luks bind -y -d "${DEV}" sss "${sss}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded (sss - $DEV)." fi if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we were unable to unlock ${DEV} (sss)." fi latchset-clevis-6df9b69/src/luks/tests/backup-restore-luks1000077500000000000000000000045601520110721700240600ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" for slt in 6 2 3; do if ! clevis luks bind -f -d "${DEV}" -s "${slt}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: [slot: $slt] Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2 fi done # Backup device. if ! clevis_luks_backup_dev "${DEV}" "${TMP}"; then error "${TEST}: problem performing device backup" fi # Save the original device for conference later. BKPDEV="${TMP}"/device-for-conference cp "${DEV}" "${BKPDEV}" # Recreate device new_device "luks1" "${DEV}" used_slots=$(clevis_luks_used_slots "${DEV}") if [ "${used_slots}" -ne 0 ]; then error "${TEST}: only used slot shold be 0 ($used_slots)" fi if compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then error "${TEST}: LUKS headers should not match" fi if compare_luks1_metadata "${DEV}" "${BKPDEV}"; then error "${TEST}: LUKS metadata should not match" fi # Restore from backup. if ! clevis_luks_restore_dev "${TMP}"; then error "${TEST}: problem performing device restore" fi if ! compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then error "${TEST}: LUKS headers should match" fi if ! compare_luks1_metadata "${DEV}" "${BKPDEV}"; then error "${TEST}: metadata should match" fi latchset-clevis-6df9b69/src/luks/tests/backup-restore-luks2000077500000000000000000000045601520110721700240610ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" for slt in 6 2 3; do if ! clevis luks bind -f -d "${DEV}" -s "${slt}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: [slot: $slt] Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2 fi done # Backup device. if ! clevis_luks_backup_dev "${DEV}" "${TMP}"; then error "${TEST}: problem performing device backup" fi # Save the original device for conference later. BKPDEV="${TMP}"/device-for-conference cp "${DEV}" "${BKPDEV}" # Recreate device new_device "luks2" "${DEV}" used_slots=$(clevis_luks_used_slots "${DEV}") if [ "${used_slots}" -ne 0 ]; then error "${TEST}: only used slot shold be 0 ($used_slots)" fi if compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then error "${TEST}: LUKS headers should not match" fi if compare_luks2_metadata "${DEV}" "${BKPDEV}"; then error "${TEST}: LUKS metadata should not match" fi # Restore from backup. if ! clevis_luks_restore_dev "${TMP}"; then error "${TEST}: problem performing device restore" fi if ! compare_luks_header "${DEV}" "${BKPDEV}" "${TMP}"; then error "${TEST}: LUKS headers should match" fi if ! compare_luks2_metadata "${DEV}" "${BKPDEV}"; then error "${TEST}: metadata should match" fi latchset-clevis-6df9b69/src/luks/tests/bad-sss000077500000000000000000000023421520110721700214250ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" CFG='{"t":1, "pins":{"tang":[{"url":"foo bar"}]}}' # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if clevis luks bind -f -d "${DEV}" sss "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is not expected to succeed when given a bad sss config." >&2 fi latchset-clevis-6df9b69/src/luks/tests/bind-already-used-luksmeta-slot000077500000000000000000000066031520110721700261700ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" # We can have a "partially" used if it is an inactive slot that has an UUID # already: # 1 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e # We end up in this situation if the cryptsetup step adding the key failed, # for instance because we provided a wrong pass phrase, and luksmeta saved # data anyway. We used to have an issue with clevis luks bind script, in which # we would still run luksmeta save even if the cryptsetup step failed. bind_and_verify() { local DEV="${1}" local PASS="${2}" local SLT="${3}" if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${PASS}) password." >&2 fi if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "active" ]; then error "${TEST}: state (${state}) is expected to be 'active'." >&2 fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2 fi } SLT=1 NEW_PASS="new-pass" PASS="${DEFAULT_PASS}" WRONG_PASS="wrong-password-here" new_device "luks1" "${DEV}" luksmeta init -f -d "${DEV}" if cryptsetup luksAddKey "${DEV}" < <(echo "${WRONG_PASS}"; echo -n "${NEW_PASS}"); then error "${TEST}: cryptsetup should not succeed in adding key when given a wrong passphrase." >&2 fi # Ok, the cryptsetup step failed, since we gave a wrong password. That means # that right now the luksmeta slot is inactive. Let's simulate the bad # condition by saving the UUID there anyway. echo "foo" | luksmeta save -d "${DEV}" -u "${UUID}" # Verify we have slot 1 like this: # # 1 inactive cb6e8904-81ff-40da-a84a-07ab9ab5715e if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "inactive" ]; then error "${TEST}: state (${state}) is expected to be 'inactive', in case #1." >&2 fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}', in case #1." >&2 fi # Verify if can bind correctly in this situation. bind_and_verify "${DEV}" "${PASS}" "1" latchset-clevis-6df9b69/src/luks/tests/bind-binary-keyfile-luks1000077500000000000000000000033131520110721700247510ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions on_exit() { local exit_status=$? tang_stop "${TMP}" [ -d "${TMP}" ] && rm -rf "${TMP}" exit "${exit_status}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" ADV="${TMP}/adv.jws" tang_get_adv "${port}" "${ADV}" CFG="$(printf '{"url":"%s","adv":"%s"}' "${url}" "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" KEYFILE="${TMP}/key" # Using a binary key file. dd if=/dev/urandom bs=4096 count=1 2>/dev/null > "${KEYFILE}" new_device_keyfile "luks1" "${DEV}" "${KEYFILE}" if ! clevis luks bind -f -k "${KEYFILE}" -d "${DEV}" tang "${CFG}"; then error "${TEST}: Binding is expected to succeed when given a correct (${KEYFILE}) keyfile." fi if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then error "${TEST}: Unlock of ${DEV} is expected to work." fi latchset-clevis-6df9b69/src/luks/tests/bind-hash-luks1000077500000000000000000000040001520110721700227540ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1 with sha512. DEV="${TMP}/luks1-device-sha512" new_device_hash "luks1" "${DEV}" "sha512" # Verify the device was created with sha512. hash_before=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_before}" != "sha512" ]; then error "${TEST}: Device should have been formatted with sha512, got ${hash_before}." fi if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed." fi # Verify the clevis slot also uses sha512. hash_after=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_after}" != "sha512" ]; then error "${TEST}: After binding, hash should be sha512, got ${hash_after}." fi # Also check individual key slot hash via clevis_luks_get_hash. hash_fn=$(clevis_luks_get_hash "${DEV}") if [ "${hash_fn}" != "sha512" ]; then error "${TEST}: clevis_luks_get_hash should return sha512, got ${hash_fn}." fi latchset-clevis-6df9b69/src/luks/tests/bind-hash-luks2000077500000000000000000000043621520110721700227700ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS2 with sha512. DEV="${TMP}/luks2-device-sha512" if ! luks2_supported; then skip_test "LUKS2 is not supported." fi new_device_hash "luks2" "${DEV}" "sha512" # Verify the device was created with sha512. hash_before=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^\s+Hash:\s+(\S+)$|\1|p' | head -1) if [ "${hash_before}" != "sha512" ]; then error "${TEST}: Device should have been formatted with sha512, got ${hash_before}." fi if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed." fi # After binding, check that the new clevis slot also uses sha512. # For LUKS2, we check the hash of the clevis key slot (slot 1). # LUKS2 luksDump format lists slots as " 1: luks2", not "Key Slot 1". hash_slot1=$(cryptsetup luksDump "${DEV}" \ | awk '/^[[:space:]]+1: luks/{found=1} found && /Hash:/{print $2; exit}') if [ "${hash_slot1}" != "sha512" ]; then error "${TEST}: Clevis slot hash should be sha512, got ${hash_slot1}." fi # Also check via clevis_luks_get_hash. hash_fn=$(clevis_luks_get_hash "${DEV}") if [ "${hash_fn}" != "sha512" ]; then error "${TEST}: clevis_luks_get_hash should return sha512, got ${hash_fn}." fi latchset-clevis-6df9b69/src/luks/tests/bind-key-file-non-interactive-luks1000077500000000000000000000043251520110721700266530ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" KEYFILE="${TMP}/key" PASS=$(new_passphrase) echo -n "${PASS}" > "${KEYFILE}" # LUKS1. DEV="${TMP}/luks1-device" new_device_keyfile "luks1" "${DEV}" "${KEYFILE}" verify_bind() { local TDEV=${1} local TSLT=${2} local state uuid if ! read -r _ state uuid < <(luksmeta show -d "${TDEV}" | grep "^${TSLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${TSLT} of ${TDEV}." >&2 fi if [ "${state}" != "active" ]; then error "${TEST}: state (${state}) is expected to be 'active'." >&2 fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2 fi } if ! clevis luks bind -f -k "${KEYFILE}" -d "${DEV}" tang "${CFG}"; then error "${TEST}: Binding is expected to succeed when given a correct keyfile (${KEYFILE})." >&2 fi verify_bind "${DEV}" "1" # Now let's pass the keyfile via stdin, non-interactively. if ! echo -n "${PASS}" | clevis luks bind -f -k- -d "${DEV}" tang "${CFG}"; then error "${TEST}: Binding is expected to succeed when given a correct passphrase (${PASS})." >&2 fi verify_bind "${DEV}" "2" latchset-clevis-6df9b69/src/luks/tests/bind-luks1000077500000000000000000000041431520110721700220430ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." fi if [ "${state}" != "active" ]; then error "${TEST}: state (${state}) is expected to be 'active'." fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." fi # Empty password (issue #494) DEV="${TMP}/luks1-device-empty-pw" new_device "luks1" "${DEV}" # Let's create an empty password. printf '%s\n\n' "${DEFAULT_PASS}" | cryptsetup luksChangeKey \ --force-password --batch-mode "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< ""; then error "${TEST}: Binding is expected to succeed when the password is empty." fi latchset-clevis-6df9b69/src/luks/tests/bind-luks1-avoid-luksmeta-corruption000077500000000000000000000030171520110721700271670ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2021 Red Hat, Inc. # Author: Sergio Arroutbi Braojos # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" ADV_AMOUNT=50 # Create LUKS1 device DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" # TANG server specifics tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" # Initial binding to ensure luksmeta gets corrupted for ADV_NU in $(seq 0 ${ADV_AMOUNT}); do "${TANGD_KEYGEN}" "${TMP}/db" done tang_new_keys "${TMP}" CFG=$(printf '{"url":"%s"}' "${url}") # At this point, luks bind must return an error. If not, test fails if clevis luks bind -y -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to fail when given a too long adv" fi latchset-clevis-6df9b69/src/luks/tests/bind-luks2000077500000000000000000000036501520110721700220460ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR if ! luks2_supported; then error "{TEST}: LUKS2 is not supported." fi TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS2. DEV="${TMP}/luks2-device" TOKEN_ID=5 new_device "luks2" "${DEV}" if ! clevis luks bind -d "${DEV}" -t "$TOKEN_ID" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi if ! cryptsetup token export --token-id=5 "${DEV}"; then error "${TEST}: Clevis did not add the LUKS2 token to the correct slot." fi # Empty password (issue #494) DEV="${TMP}/luks2-device-empty-pw" new_device "luks2" "${DEV}" # Let's create an empty password. printf '%s\n\n' "${DEFAULT_PASS}" | cryptsetup luksChangeKey \ --force-password --batch-mode "${DEV}" if ! clevis luks bind -d "${DEV}" tang "${CFG}" <<< ""; then error "${TEST}: Binding is expected to succeed when the password is empty." fi latchset-clevis-6df9b69/src/luks/tests/bind-luks2-ext-token000077500000000000000000000045131520110721700237610ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2022 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } create_existing_token_id_from_keyring() { local DEV="${1}" local KEYDESC="${2}" local TOKEN_ID="${3}" local PASS="${4}" if [[ -z "${DEV}" ]] || [[ -z "${KEYDESC}" ]] || [[ -z "${TOKEN_ID}" ]]; then return 1 fi KEYRING_ID=$(keyctl add user "${KEYDESC}" "${PASS}" @s) keyctl print "${KEYRING_ID}" 2>/dev/null 1>/dev/null cryptsetup token add --token-id "${TOKEN_ID}" --key-description "${KEYDESC}" "${DEV}" } if ! luks2_supported; then skip_test "${TEST}: LUKS2 is not supported." fi if ! luks2_existing_token_id_supported; then skip_test "${TEST}: Existing token ID not supported" fi trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" EXISTING_TOKEN_ID=5 KEYDESC="testkey" PASS="123exttokenid_" DEV="${TMP}/luks2-device-ext-token" new_device "luks2" "${DEV}" "${PASS}" create_existing_token_id_from_keyring "${DEV}" "${KEYDESC}" "${EXISTING_TOKEN_ID}" "${PASS}" if ! clevis luks bind -y -d "${DEV}" -e "${EXISTING_TOKEN_ID}" tang "${CFG}"; then error "${TEST}: Binding expected to succeed with existing token id:${EXISTING_TOKEN_ID}" >&2 fi KEYFILE="${TMP}/keyfile.txt" touch "${KEYFILE}" if clevis luks bind -y -d "${DEV}" -e "${EXISTING_TOKEN_ID}" -k "${KEYFILE}" tang "${CFG}"; then error "${TEST}: Using existing token id and keyfile should dump an error" >&2 fi latchset-clevis-6df9b69/src/luks/tests/bind-pass-with-newline-keyfile-luks1000077500000000000000000000036051520110721700270470ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" # Using newlines and special chars in the passphrase. PASS="foo bar \\\&#@@&@*!)( $$$ " KEYFILE="${TMP}/key" echo -n "${PASS}" > "${KEYFILE}" new_device_keyfile "luks1" "${DEV}" "${KEYFILE}" if ! clevis luks bind -f -k "${KEYFILE}" -d "${DEV}" tang "${CFG}"; then error "${TEST}: Binding is expected to succeed when given a correct (${KEYFILE}) password." >&2 fi SLT=1 if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "active" ]; then error "${TEST}: state (${state}) is expected to be 'active'." >&2 fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2 fi latchset-clevis-6df9b69/src/luks/tests/bind-pass-with-newline-luks1000077500000000000000000000034761520110721700254270ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" # Using newlines and special chars in the passphrase. PASS="foo bar \\\&#@@&@*!)( $$$ " new_device "luks1" "${DEV}" "${PASS}" if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${PASS}) password." >&2 fi SLT=1 if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "active" ]; then error "${TEST}: state (${state}) is expected to be 'active'." >&2 fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2 fi latchset-clevis-6df9b69/src/luks/tests/bind-wrong-pass-luks1000077500000000000000000000032601520110721700241400ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "wrong-passphrase"; then error "${TEST}: Binding is expected to fail when given a wrong password." >&2 fi SLT=1 if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "inactive" ]; then error "${TEST}: state (${state}) is expected to be 'inactive'." >&2 fi if [ "${uuid}" != "empty" ]; then error "${TEST}: UUID ($uuid) is expected to be 'empty'." >&2 fi latchset-clevis-6df9b69/src/luks/tests/bind-wrong-pass-luks2000077500000000000000000000025631520110721700241460ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR if ! luks2_supported; then error "{TEST}: LUKS2 is not supported." fi TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" if clevis luks bind -d "${DEV}" tang "${CFG}" <<< "wrong-passphrase"; then error "${TEST}: Binding is expected to fail when given a wrong password." >&2 fi latchset-clevis-6df9b69/src/luks/tests/edit-hash-luks1000077500000000000000000000056271520110721700230050ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { local d for d in "${TMP}" "${TMP2}"; do [ ! -d "${d}" ] && continue tang_stop "${d}" rm -rf "${d}" done } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" cfg=$(printf '{"url":"%s"}' "${url}") # LUKS1 with sha512. DEV="${TMP}/luks1-device-sha512" new_device_hash "luks1" "${DEV}" "sha512" # Verify the device was created with sha512. hash_before=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_before}" != "sha512" ]; then error "${TEST}: Device should have been formatted with sha512, got ${hash_before}." fi if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Verify hash is still sha512 after binding. hash_after_bind=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_after_bind}" != "sha512" ]; then error "${TEST}: After binding, hash should be sha512, got ${hash_after_bind}." fi # Now let's have another tang instance running and change the config to use # the new one. TMP2="$(mktemp -d)" tang_run "${TMP2}" port2=$(tang_get_port "${TMP2}") new_url="http://localhost:${port2}" new_cfg=$(printf '{"url":"%s"}' "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded." fi # Verify hash is still sha512 after edit. hash_after_edit=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_after_edit}" != "sha512" ]; then error "${TEST}: After edit, hash should be sha512, got ${hash_after_edit}." fi # Also check via clevis_luks_get_hash. hash_fn=$(clevis_luks_get_hash "${DEV}") if [ "${hash_fn}" != "sha512" ]; then error "${TEST}: clevis_luks_get_hash should return sha512, got ${hash_fn}." fi # Make sure we can still unlock the device. if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then error "${TEST}: we should have been able to unlock the device" fi latchset-clevis-6df9b69/src/luks/tests/edit-hash-luks2000077500000000000000000000061571520110721700230050ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { local d for d in "${TMP}" "${TMP2}"; do [ ! -d "${d}" ] && continue tang_stop "${d}" rm -rf "${d}" done } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" # LUKS2. if ! luks2_supported; then skip_test "LUKS2 is not supported." fi tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" cfg=$(printf '{"url":"%s"}' "${url}") # LUKS2 with sha512. DEV="${TMP}/luks2-device-sha512" new_device_hash "luks2" "${DEV}" "sha512" # Verify the device was created with sha512. hash_before=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^\s+Hash:\s+(\S+)$|\1|p' | head -1) if [ "${hash_before}" != "sha512" ]; then error "${TEST}: Device should have been formatted with sha512, got ${hash_before}." fi if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Verify the clevis slot (slot 1) uses sha512 after binding. hash_after_bind=$(cryptsetup luksDump "${DEV}" \ | awk '/^[[:space:]]+1: luks/{found=1} found && /Hash:/{print $2; exit}') if [ "${hash_after_bind}" != "sha512" ]; then error "${TEST}: After binding, clevis slot hash should be sha512, got ${hash_after_bind}." fi # Now let's have another tang instance running and change the config to use # the new one. TMP2="$(mktemp -d)" tang_run "${TMP2}" port2=$(tang_get_port "${TMP2}") new_url="http://localhost:${port2}" new_cfg=$(printf '{"url":"%s"}' "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded." fi # Verify the clevis slot (slot 1) still uses sha512 after edit. hash_after_edit=$(cryptsetup luksDump "${DEV}" \ | awk '/^[[:space:]]+1: luks/{found=1} found && /Hash:/{print $2; exit}') if [ "${hash_after_edit}" != "sha512" ]; then error "${TEST}: After edit, clevis slot hash should be sha512, got ${hash_after_edit}." fi # Also check via clevis_luks_get_hash. hash_fn=$(clevis_luks_get_hash "${DEV}") if [ "${hash_fn}" != "sha512" ]; then error "${TEST}: clevis_luks_get_hash should return sha512, got ${hash_fn}." fi # Make sure we can still unlock the device. if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then error "${TEST}: we should have been able to unlock the device" fi latchset-clevis-6df9b69/src/luks/tests/edit-tang-luks1000077500000000000000000000072431520110721700230070ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { local d for d in "${TMP}" "${TMP2}"; do [ ! -d "${d}" ] && continue tang_stop "${d}" rm -rf "${d}" done } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" cfg=$(printf '{"url":"%s"}' "${url}") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's try to change the config but using the same one we already have. if clevis luks edit -d "${DEV}" -s 1 -c "${cfg}"; then error "${TEST}: edit should have failed because the config is the same." fi # And now, just a broken config. new_cfg=$(printf '{"url&:"%s"}' "${url}") if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have failed because of invalid JSON" fi # Now let's have another tang instance running and change the config to use # the new one. TMP2="$(mktemp -d)" tang_run "${TMP2}" port2=$(tang_get_port "${TMP2}") new_url="http://localhost:${port2}" new_cfg=$(printf '{"url":"%s"}' "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded." fi # Now we test an invalid server. new_cfg='{"url":"localhost:1"}' if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should not have succeeded with a wrong server." fi # Make sure we can still unlock the device. if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then error "${TEST}: we should have been able to unlock the device" fi # And now let's use sss and start with a single tang server, then add a second # one. new_device "luks1" "${DEV}" cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"}]}}' "${url}") if ! clevis luks bind -y -d "${DEV}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi new_cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ "${url}" "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded and added a new tang server" fi # Now let's change the threshold to 2. new_cfg=$(printf '{"t":2,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ "${url}" "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded and added a new tang server" fi # And finally, let's try a broken config, with a wrong threshold. new_cfg=$(printf '{"t":3,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ "${url}" "${new_url}") if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have failed because threshold > number of servers" fi latchset-clevis-6df9b69/src/luks/tests/edit-tang-luks2000077500000000000000000000072431520110721700230100ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { local d for d in "${TMP}" "${TMP2}"; do [ ! -d "${d}" ] && continue tang_stop "${d}" rm -rf "${d}" done } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" cfg=$(printf '{"url":"%s"}' "${url}") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's try to change the config but using the same one we already have. if clevis luks edit -d "${DEV}" -s 1 -c "${cfg}"; then error "${TEST}: edit should have failed because the config is the same." fi # And now, just a broken config. new_cfg=$(printf '{"url&:"%s"}' "${url}") if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have failed because of invalid JSON" fi # Now let's have another tang instance running and change the config to use # the new one. TMP2="$(mktemp -d)" tang_run "${TMP2}" port2=$(tang_get_port "${TMP2}") new_url="http://localhost:${port2}" new_cfg=$(printf '{"url":"%s"}' "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded." fi # Now we test an invalid server. new_cfg='{"url":"localhost:1"}' if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should not have succeeded with a wrong server." fi # Make sure we can still unlock the device. if ! clevis_luks_unlock_device "${DEV}" >/dev/null; then error "${TEST}: we should have been able to unlock the device" fi # And now let's use sss and start with a single tang server, then add a second # one. new_device "luks2" "${DEV}" cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"}]}}' "${url}") if ! clevis luks bind -y -d "${DEV}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi new_cfg=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ "${url}" "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded and added a new tang server" fi # Now let's change the threshold to 2. new_cfg=$(printf '{"t":2,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ "${url}" "${new_url}") if ! clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have succeeded and added a new tang server" fi # And finally, let's try a broken config, with a wrong threshold. new_cfg=$(printf '{"t":3,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ "${url}" "${new_url}") if clevis luks edit -d "${DEV}" -s 1 -c "${new_cfg}"; then error "${TEST}: edit should have failed because threshold > number of servers" fi latchset-clevis-6df9b69/src/luks/tests/get-hash-luks1000077500000000000000000000057111520110721700226310ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" # Test 1: clevis_luks_get_hash with empty argument should fail. if clevis_luks_get_hash ""; then error "${TEST}: clevis_luks_get_hash should fail with empty argument." fi # Test 2: clevis_luks_get_hash with non-existent device should fail. if clevis_luks_get_hash "/dev/nonexistent-device-XYZ"; then error "${TEST}: clevis_luks_get_hash should fail with non-existent device." fi # Test 3: LUKS1 device with default hash (sha256) — verify correct extraction. DEV="${TMP}/luks1-device-sha256" new_device_hash "luks1" "${DEV}" "sha256" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha256" ]; then error "${TEST}: expected sha256, got '${hash}'." fi # Test 4: LUKS1 device with sha512 — verify correct extraction. DEV="${TMP}/luks1-device-sha512" new_device_hash "luks1" "${DEV}" "sha512" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha512" ]; then error "${TEST}: expected sha512, got '${hash}'." fi # Test 5: LUKS1 device with sha384 — verify correct extraction. DEV="${TMP}/luks1-device-sha384" new_device_hash "luks1" "${DEV}" "sha384" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha384" ]; then error "${TEST}: expected sha384, got '${hash}'." fi # Test 6: LUKS1 device with sha1 — verify correct extraction. DEV="${TMP}/luks1-device-sha1" new_device_hash "luks1" "${DEV}" "sha1" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha1" ]; then error "${TEST}: expected sha1, got '${hash}'." fi # Test 7: LUKS1 device with ripemd160 — verify correct extraction. DEV="${TMP}/luks1-device-ripemd160" new_device_hash "luks1" "${DEV}" "ripemd160" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "ripemd160" ]; then error "${TEST}: expected ripemd160, got '${hash}'." fi # Test 8: clevis_luks_get_hash with a non-LUKS file should fail. DEV="${TMP}/not-a-luks-device" fallocate -l64M "${DEV}" 2>/dev/null \ || dd if=/dev/zero of="${DEV}" bs=1M count=64 status=none if clevis_luks_get_hash "${DEV}"; then error "${TEST}: clevis_luks_get_hash should fail on a non-LUKS file." fi latchset-clevis-6df9b69/src/luks/tests/get-hash-luks2000077500000000000000000000057621520110721700226400ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions if ! luks2_supported; then skip_test "LUKS2 is not supported." fi on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" # Test 1: LUKS2 device with default hash (sha256) — verify correct extraction. DEV="${TMP}/luks2-device-sha256" new_device_hash "luks2" "${DEV}" "sha256" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha256" ]; then error "${TEST}: expected sha256, got '${hash}'." fi # Test 2: LUKS2 device with sha512 — verify correct extraction. DEV="${TMP}/luks2-device-sha512" new_device_hash "luks2" "${DEV}" "sha512" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha512" ]; then error "${TEST}: expected sha512, got '${hash}'." fi # Test 3: LUKS2 device with sha384 — verify correct extraction. DEV="${TMP}/luks2-device-sha384" new_device_hash "luks2" "${DEV}" "sha384" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha384" ]; then error "${TEST}: expected sha384, got '${hash}'." fi # Test 4: LUKS2 — verify we get keyslot hash, not digest hash. # Create device with sha512. The digest section uses sha256 by default. # Our function must return the keyslot hash (sha512), not the digest hash. DEV="${TMP}/luks2-device-keyslot-check" new_device_hash "luks2" "${DEV}" "sha512" # Verify that the digest section does contain a different hash. digest_hash=$(cryptsetup luksDump "${DEV}" \ | awk '/^Digests:/{in_digest=1} in_digest && /Hash:/{print $2; exit}') keyslot_hash=$(clevis_luks_get_hash "${DEV}") if [ "${keyslot_hash}" != "sha512" ]; then error "${TEST}: keyslot hash should be sha512, got '${keyslot_hash}'." fi # If the digest hash differs, this confirms we are targeting the right section. if [ -n "${digest_hash}" ] && [ "${digest_hash}" != "sha512" ]; then echo "${TEST}: confirmed digest hash (${digest_hash}) differs from" \ "keyslot hash (${keyslot_hash}) — extraction targets correct section." fi # Test 5: LUKS2 with sha1 — verify correct extraction. DEV="${TMP}/luks2-device-sha1" new_device_hash "luks2" "${DEV}" "sha1" hash=$(clevis_luks_get_hash "${DEV}") if [ "${hash}" != "sha1" ]; then error "${TEST}: expected sha1, got '${hash}'." fi latchset-clevis-6df9b69/src/luks/tests/get-hash-validation000077500000000000000000000047251520110721700237300ustar00rootroot00000000000000#!/bin/bash -ex # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Test the hash validation used in clevis_luks_get_hash(). # This test does not require root or LUKS devices — it exercises the # validation logic directly. TEST=$(basename "${0}") . luks-common-test-functions # is_valid_hash() mirrors the validation logic in clevis_luks_get_hash(): # case "${hash}" in *[!a-zA-Z0-9_-]*|"") return 1;; esac is_valid_hash() { local hash="${1}" case "${hash}" in *[!a-zA-Z0-9_-]*|"") return 1;; esac return 0 } # Test valid hashes — these must pass validation. for h in sha256 sha512 sha384 sha1 sha224 ripemd160 \ SHA256 SHA512 whirlpool blake2b-256 sha3_256; do if ! is_valid_hash "${h}"; then error "${TEST}: valid hash '${h}' was rejected." fi done # Test invalid hashes — injection attempts and malformed values. # Each is tested individually to handle special characters safely. if is_valid_hash 'sha256; rm -rf /'; then error "${TEST}: 'sha256; rm -rf /' should be rejected (semicolon)." fi if is_valid_hash 'sha256$(reboot)'; then error "${TEST}: 'sha256\$(reboot)' should be rejected (command substitution)." fi if is_valid_hash 'sha256`reboot`'; then error "${TEST}: 'sha256\`reboot\`' should be rejected (backticks)." fi if is_valid_hash 'sha256 --force'; then error "${TEST}: 'sha256 --force' should be rejected (space)." fi if is_valid_hash 'sha256|cat /etc/shadow'; then error "${TEST}: 'sha256|cat /etc/shadow' should be rejected (pipe)." fi if is_valid_hash 'sha256&bg_cmd'; then error "${TEST}: 'sha256&bg_cmd' should be rejected (ampersand)." fi if is_valid_hash '--hash sha256'; then error "${TEST}: '--hash sha256' should be rejected (space)." fi if is_valid_hash ''; then error "${TEST}: empty string should be rejected." fi latchset-clevis-6df9b69/src/luks/tests/list-multiple-slots-luks1000077500000000000000000000140401520110721700250720ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN1="sss" PINS1="sss tang" CFG1=$(printf ' { "t": 1, "pins": { "tang": [ { "url": "ADDR","adv": "%s" } ] } } ' "${ADV}") PIN2="null" PINS2="null" CFG2='{}' ALLPINS="null sss tang" # LUKS1 DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" "${PIN1}" "${CFG1}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi if ! clevis luks bind -f -d "${DEV}" "${PIN2}" "${CFG2}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed for null pin." fi test_values() { local SLT="$1" local slot="$2" local pin="$3" local cfg="$4" case $SLT in 1) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN1}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN1}'" fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") cfg_for_cmp=${CFG1//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi ;; 2) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN2}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN2}'" fi if ! pin_cfg_equal "${cfg}" "${CFG2}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${CFG2})" fi ;; *) error "${TEST}: unexpected slot ${SLT}" ;; esac } test_pin_values() { local SLT="$1" local slot="$2" local pins="$3" case $SLT in 1) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS1}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS1}'" fi ;; 2) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS2}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS2}'" fi ;; *) error "${TEST}: unexpected slot ${SLT}" ;; esac } SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_values "${SLT}" "${slot}" "${pin}" "${cfg}" if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_pin_values "${SLT}" "${slot}" "${pins}" SLT=2 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_values "${SLT}" "${slot}" "${pin}" "${cfg}" if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_pin_values "${SLT}" "${slot}" "${pins}" # Test both slots reading if ! slots=$(clevis luks list -d "${DEV}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV})" fi read_slots= while read -r slot pin cfg; do read_slots="${read_slots}${read_slots:+ }${slot%:}" test_values "${slot%:}" "${slot}" "${pin}" "${cfg}" done <<< "$slots" if [[ "${read_slots}" != "1 2" ]]; then error "${TEST}: clevis luks list did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" fi if ! slots=$(clevis luks list -d "${DEV}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV})" fi read_slots= while read -r slot pins; do read_slots="${read_slots}${read_slots:+ }${slot%:}" test_pin_values "${slot%:}" "${slot}" "${pins}" done <<< "$slots" if [[ "${read_slots}" != "1 2" ]]; then error "${TEST}: clevis luks list -p did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" fi # Test clevis_luks_read_used_pins . clevis-luks-common-functions if ! pins=$(clevis_luks_read_used_pins "${DEV}"); then error "${TEST}: clevis_luks_read_used_pins is expected to succeed for device(${DEV})" fi if [[ "${pins}" != "${ALLPINS}" ]]; then error "${TEST}: clevis_luks_read_used_pins did not return all expected pins (${ALLPINS}), it was (${pins}) for device(${DEV})" fi latchset-clevis-6df9b69/src/luks/tests/list-multiple-slots-luks2000077500000000000000000000140401520110721700250730ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN1="sss" PINS1="sss tang" CFG1=$(printf ' { "t": 1, "pins": { "tang": [ { "url": "ADDR","adv": "%s" } ] } } ' "${ADV}") PIN2="null" PINS2="null" CFG2='{}' ALLPINS="null sss tang" # LUKS2 DEV="${TMP}/luks1-device" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" "${PIN1}" "${CFG1}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi if ! clevis luks bind -f -d "${DEV}" "${PIN2}" "${CFG2}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed for null pin." fi test_values() { local SLT="$1" local slot="$2" local pin="$3" local cfg="$4" case $SLT in 1) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN1}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN1}'" fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") cfg_for_cmp=${CFG1//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi ;; 2) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN2}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN2}'" fi if ! pin_cfg_equal "${cfg}" "${CFG2}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${CFG2})" fi ;; *) error "${TEST}: unexpected slot ${SLT}" ;; esac } test_pin_values() { local SLT="$1" local slot="$2" local pins="$3" case $SLT in 1) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS1}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS1}'" fi ;; 2) if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS2}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS2}'" fi ;; *) error "${TEST}: unexpected slot ${SLT}" ;; esac } SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_values "${SLT}" "${slot}" "${pin}" "${cfg}" if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_pin_values "${SLT}" "${slot}" "${pins}" SLT=2 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_values "${SLT}" "${slot}" "${pin}" "${cfg}" if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi test_pin_values "${SLT}" "${slot}" "${pins}" # Test both slots reading if ! slots=$(clevis luks list -d "${DEV}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV})" fi read_slots= while read -r slot pin cfg; do read_slots="${read_slots}${read_slots:+ }${slot%:}" test_values "${slot%:}" "${slot}" "${pin}" "${cfg}" done <<< "$slots" if [[ "${read_slots}" != "1 2" ]]; then error "${TEST}: clevis luks list did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" fi if ! slots=$(clevis luks list -d "${DEV}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV})" fi read_slots= while read -r slot pins; do read_slots="${read_slots}${read_slots:+ }${slot%:}" test_pin_values "${slot%:}" "${slot}" "${pins}" done <<< "$slots" if [[ "${read_slots}" != "1 2" ]]; then error "${TEST}: clevis luks list -p did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" fi # Test clevis_luks_read_used_pins . clevis-luks-common-functions if ! pins=$(clevis_luks_read_used_pins "${DEV}"); then error "${TEST}: clevis_luks_read_used_pins is expected to succeed for device(${DEV})" fi if [[ "${pins}" != "${ALLPINS}" ]]; then error "${TEST}: clevis_luks_read_used_pins did not return all expected pins (${ALLPINS}), it was (${pins}) for device(${DEV})" fi latchset-clevis-6df9b69/src/luks/tests/list-recursive-luks1000077500000000000000000000053031520110721700241060ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" PINS="sss tang" CFG=$(printf ' { "t": 1, "pins": { "sss": { "t": 1, "pins": { "sss": { "t": 1, "pins": { "tang": [ { "url": "ADDR","adv": "%s" } ] } } } } } } ' "${ADV}") # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" "${PIN}" "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" fi latchset-clevis-6df9b69/src/luks/tests/list-recursive-luks2000077500000000000000000000053031520110721700241070ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" PINS="sss tang" CFG=$(printf ' { "t": 1, "pins": { "sss": { "t": 1, "pins": { "sss": { "t": 1, "pins": { "tang": [ { "url": "ADDR","adv": "%s" } ] } } } } } } ' "${ADV}") # LUKS2. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" "${PIN}" "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" fi latchset-clevis-6df9b69/src/luks/tests/list-sss-tang-luks1000077500000000000000000000052671520110721700236470ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" PINS="sss tang" CFG=$(printf ' { "t": 2, "pins": { "tang": [ {"url":"ADDR1","adv":"%s"}, {"url":"ADDR2","adv":"%s"}, {"url":"ADDR3","adv":"%s"}, {"url":"ADDR4","adv":"%s"}, {"url":"ADDR5","adv":"%s"} ] } } ' "${ADV}" "${ADV}" "${ADV}" "${ADV}" "${ADV}") # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" ${PIN} "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi to_remove_from_cfg=$(printf ',"adv":"%s"' "${ADV}") cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" fi latchset-clevis-6df9b69/src/luks/tests/list-sss-tang-luks2000077500000000000000000000052671520110721700236500ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" PINS="sss tang" CFG=$(printf ' { "t": 2, "pins": { "tang": [ {"url":"ADDR1","adv":"%s"}, {"url":"ADDR2","adv":"%s"}, {"url":"ADDR3","adv":"%s"}, {"url":"ADDR4","adv":"%s"}, {"url":"ADDR5","adv":"%s"} ] } } ' "${ADV}" "${ADV}" "${ADV}" "${ADV}" "${ADV}") # LUKS2. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" ${PIN} "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi to_remove_from_cfg=$(printf ',"adv":"%s"' "${ADV}") cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" fi latchset-clevis-6df9b69/src/luks/tests/list-tang-luks1000077500000000000000000000047101520110721700230310ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="tang" PINS="tang" CFG=$(printf '{"url": "ADDR","adv": "%s"}' "${ADV}") # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" "${PIN}" "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" fi latchset-clevis-6df9b69/src/luks/tests/list-tang-luks2000077500000000000000000000047101520110721700230320ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="tang" PINS="tang" CFG=$(printf '{"url": "ADDR","adv": "%s"}' "${ADV}") # LUKS2. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" "${PIN}" "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" fi if [[ "${pins}" != "${PINS}" ]]; then error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" fi latchset-clevis-6df9b69/src/luks/tests/luks-common-test-functions.in000077500000000000000000000171021520110721700257250ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tests-common-functions . tang-common-test-functions # We require cryptsetup >= 2.0.4 to fully support LUKSv2. # Support is determined at build time. luks2_supported() { # shellcheck disable=SC2152 return @OLD_CRYPTSETUP@ } # We require cryptsetup >= 2.6.0 to fully support LUKSv2 addkey/open by token ID # Support is determined at build time. luks2_existing_token_id_supported() { # shellcheck disable=SC2152 return @OLD_CRYPTSETUP_EXISTING_TOKEN_ID@ } # Creates a new LUKS1 or LUKS2 device to be used. new_device() { local LUKS="${1}" local DEV="${2}" local PASS="${3}" # Some builders fail if the cryptsetup steps are not ran as root, so let's # skip the test now if not running as root. if [ "$(id -u)" != 0 ]; then skip_test "WARNING: You must be root to run this test; test skipped." fi # Using a default password, if none has been provided. if [ -z "${PASS}" ]; then PASS="${DEFAULT_PASS}" fi local DEV_CACHED="${TMP}/${LUKS}.cached" # Let's reuse an existing device, if there is one. if [ -f "${DEV_CACHED}" ]; then echo "Reusing cached ${LUKS} device..." cp -f "${DEV_CACHED}" "${DEV}" return 0 fi fallocate -l64M "${DEV}" cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \ --pbkdf-force-iterations 1000 --key-size 512 --batch-mode \ --force-password "${DEV}" <<< "${PASS}" # Caching the just-formatted device for possible reuse. cp -f "${DEV}" "${DEV_CACHED}" } # Creates a new LUKS1 or LUKS2 device with a specific hash algorithm. new_device_hash() { local LUKS="${1}" local DEV="${2}" local HASH="${3}" local PASS="${4}" # Some builders fail if the cryptsetup steps are not ran as root, so let's # skip the test now if not running as root. if [ "$(id -u)" != 0 ]; then skip_test "WARNING: You must be root to run this test; test skipped." fi # Using a default password, if none has been provided. if [ -z "${PASS}" ]; then PASS="${DEFAULT_PASS}" fi fallocate -l64M "${DEV}" 2>/dev/null \ || dd if=/dev/zero of="${DEV}" bs=1M count=64 status=none cryptsetup luksFormat --type "${LUKS}" --hash "${HASH}" --pbkdf pbkdf2 \ --pbkdf-force-iterations 1000 --key-size 512 --batch-mode \ --force-password "${DEV}" <<< "${PASS}" } # Creates a new LUKS1 or LUKS2 device to be used, using a keyfile. new_device_keyfile() { local LUKS="${1}" local DEV="${2}" local KEYFILE="${3}" # Some builders fail if the cryptsetup steps are not ran as root, so let's # skip the test now if not running as root. if [ "$(id -u)" != 0 ]; then skip_test "WARNING: You must be root to run this test; test skipped." fi if [[ -z "${KEYFILE}" ]] || [[ ! -f "${KEYFILE}" ]]; then error "Invalid keyfile (${KEYFILE})." fi fallocate -l64M "${DEV}" cryptsetup luksFormat --type "${LUKS}" --pbkdf pbkdf2 \ --pbkdf-force-iterations 1000 --key-size 512 --batch-mode \ "${DEV}" "${KEYFILE}" } pin_cfg_equal() { # Let's remove the single quotes from the pin configuration. local cfg1="${1//\'/}" local cfg2="${2//\'/}" # Now we sort and present them in compact form. local sorted_cfg1 sorted_cfg2 sorted_cfg1="$(jq --compact-output --sort-keys . < <(echo -n "${cfg1}"))" sorted_cfg2="$(jq --compact-output --sort-keys . < <(echo -n "${cfg2}"))" # And we finally compare. if [ "${sorted_cfg1}" = "${sorted_cfg2}" ]; then return 0 fi return 1 } compare_luks_header() { DEV1="${1}" DEV2="${2}" TMP="${3}" cryptsetup luksHeaderBackup "${DEV1}" \ --header-backup-file "${TMP}"/check-header1 cryptsetup luksHeaderBackup "${DEV2}" \ --header-backup-file "${TMP}"/check-header2 local cs1 cs2 cs1=$(cksum "${TMP}"/check-header1 | cut -d' ' -f 1) cs2=$(cksum "${TMP}"/check-header2 | cut -d' ' -f 1) rm -f "${TMP}"/check-header{1,2} if [ "${cs1}" == "${cs2}" ]; then return 0 fi return 1 } used_luks1_metadata_slots() { DEV="${1}" if ! luksmeta test -d "${DEV}"; then echo "" return 0 fi local clevis_uuid="cb6e8904-81ff-40da-a84a-07ab9ab5715e" luksmeta show -d "${DEV}" \ | sed -rn "s|^([0-9]+)\s+active\s+${clevis_uuid}$|\1|p" \ | tr '\n' ' ' | sed 's/ $//' } used_luks2_metadata_slots() { DEV="${1}" cryptsetup luksDump "${DEV}" \ | grep -E -A1 "^\s+[0-9]+:\s+clevis$" \ | sed -rn 's|^\s+Keyslot:\s+([0-9]+)$|\1|p' | sort -n \ | tr '\n' ' ' | sed 's/ $//' } used_luks2_metadata_tokens() { DEV="${1}" cryptsetup luksDump "${DEV}" \ | grep -E -B1 "^\s+Keyslot:\s+[0-9]+$" \ | sed -rn 's|^\s+([0-9]+): clevis|\1|p' \ | tr '\n' ' ' | sed 's/ $//' } compare_luks1_metadata() { DEV1="${1}" DEV2="${2}" # If both are non-initialized, metadata is the same. ! luksmeta test -d "${DEV1}" && ! luksmeta test -d "${DEV2}" && return 0 # Otherwise, metadata differ. ! luksmeta test -d "${DEV1}" && return 1 ! luksmeta test -d "${DEV2}" && return 1 local slt1 slt2 slt1=$(used_luks1_metadata_slots "${DEV1}") slt2=$(used_luks1_metadata_slots "${DEV2}") if [ "${slt1}" != "${slt2}" ]; then echo "used slots did not match ($slt1) ($slt2)" >&2 return 1 fi local slt md1 md2 for slt in ${slt1}; do md1="$(luksmeta load -d "${DEV}" -s "${slt}")" md2="$(luksmeta load -d "${DEV2}" -s "${slt}")" if [ "${md1}" != "${md2}" ]; then echo "metadata in slot ${slt} did not match" >&2 return 1 fi done return 0 } compare_luks2_metadata() { DEV1="${1}" DEV2="${2}" local slt1 slt2 slt1=$(used_luks2_metadata_slots "${DEV1}") slt2=$(used_luks2_metadata_slots "${DEV2}") if [ "${slt1}" != "${slt2}" ]; then echo "used slots did not match ($slt1) ($slt2)" >&2 return 1 fi local tkn1 tkn2 tkn1=$(used_luks2_metadata_tokens "${DEV1}") tkn2=$(used_luks2_metadata_tokens "${DEV2}") if [ "${tkn1}" != "${tkn2}" ]; then echo "used tokens did not match ($tkn1) ($tkn2)" >&2 return 1 fi local tkn md1 md2 for tkn in ${tkn1}; do md1="$(cryptsetup token export --token-id "${tkn}" "${DEV1}")" md2="$(cryptsetup token export --token-id "${tkn}" "${DEV2}")" if [ "${md1}" != "${md2}" ]; then echo "metadata in token ${tkn} did not match" >&2 return 1 fi done return 0 } new_passphrase() { jose jwk gen --input='{"kty":"oct","bytes":8}' --output=- \ | jose fmt --json=- --object --get k --unquote=- } export DEFAULT_PASS=' just-some- test-password-here 1.+?~!@#$%^&*();:'"'"'"[]{}_=/`\ ' latchset-clevis-6df9b69/src/luks/tests/meson.build000066400000000000000000000151171520110721700223110ustar00rootroot00000000000000# We use jq for comparing the pin config in the clevis luks list tests. jq = find_program('jq', required: false) # We use cryptsetup for testing LUKS2 binding and saving the token in a # given token slot. cryptsetup = find_program('cryptsetup', required: true) # Use keyctl to check an existing token id can be created from # kernel keyring password keyutils = find_program('keyctl', required: false) keyutils_usable = false if keyutils.found() keyutils_usable = run_command(keyutils, 'session', '-', '/bin/true', capture: false, check: false).returncode() == 0 if keyutils_usable message('keyutils installed') else warning('keyutils installed, but running fails (are you inside Docker?), unable to test existing token id binding') endif else warning('keyutils not installed, unable to test existing token id binding') endif common_functions = configure_file(input: 'luks-common-test-functions.in', output: 'luks-common-test-functions', configuration: luksmeta_data, install: false ) env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'luks'), join_paths(meson.source_root(), 'src', 'pins', 'sss'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), join_paths(meson.source_root(), 'src', 'pins', 'tpm1'), join_paths(meson.source_root(), 'src', 'pins', 'tpm2'), meson.current_source_dir(), meson.current_build_dir(), join_paths(meson.build_root(), 'src'), join_paths(meson.build_root(), 'src', 'luks'), join_paths(meson.build_root(), 'src', 'pins', 'sss'), join_paths(meson.build_root(), 'src', 'pins', 'tang'), join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'tpm1'), join_paths(meson.build_root(), 'src', 'pins', 'tpm2'), separator: ':' ) test('bind-wrong-pass-luks1', find_program('bind-wrong-pass-luks1'), env: env) test('bind-luks1', find_program('bind-luks1'), env: env) test('unbind-unbound-slot-luks1', find_program('unbind-unbound-slot-luks1'), env: env) test('unbind-luks1', find_program('unbind-luks1'), env: env) test('bind-key-file-non-interactive', find_program('bind-key-file-non-interactive-luks1'), env: env) test('bind-pass-with-newline', find_program('bind-pass-with-newline-luks1'), env: env) test('bind-pass-with-newline-keyfile', find_program('bind-pass-with-newline-keyfile-luks1'), env: env) test('bind-binary-keyfile', find_program('bind-binary-keyfile-luks1'), env: env) # Bug #70. test('bind-already-used-luksmeta-slot', find_program('bind-already-used-luksmeta-slot'), env: env, timeout: 60) test('bad-sss', find_program('bad-sss'), env: env) if jq.found() test('list-recursive-luks1', find_program('list-recursive-luks1'), env: env) test('list-tang-luks1', find_program('list-tang-luks1'), env: env) test('list-sss-tang-luks1', find_program('list-sss-tang-luks1'), env: env) test('list-multiple-slots-luks1', find_program('list-multiple-slots-luks1'), env: env) else warning('Will not run "clevis luks list" tests due to missing jq dependency') endif test('unlock-tang-luks1', find_program('unlock-tang-luks1'), env: env, timeout: 90) test('assume-yes', find_program('assume-yes'), env: env, timeout: 60) test('regen-inplace-luks1', find_program('regen-inplace-luks1'), env: env, timeout: 90) test('regen-not-inplace-luks1', find_program('regen-not-inplace-luks1'), env: env, timeout: 90) test('report-tang-luks1', find_program('report-tang-luks1'), env: env, timeout: 90) test('report-sss-luks1', find_program('report-sss-luks1'), env: env, timeout: 90) test('edit-tang-luks1', find_program('edit-tang-luks1'), env: env, timeout: 150) test('backup-restore-luks1', find_program('backup-restore-luks1'), env: env, timeout: 60) test('pass-tang-luks1', find_program('pass-tang-luks1'), env: env, timeout: 60) test('bind-luks1-avoid-luksmeta-corruption', find_program('bind-luks1-avoid-luksmeta-corruption'), env: env, timeout: 60) test('bind-hash-luks1', find_program('bind-hash-luks1'), env: env) test('regen-hash-luks1', find_program('regen-hash-luks1'), env: env, timeout: 60) test('edit-hash-luks1', find_program('edit-hash-luks1'), env: env, timeout: 60) test('get-hash-luks1', find_program('get-hash-luks1'), env: env, timeout: 60) test('get-hash-validation', find_program('get-hash-validation'), env: env) # LUKS2 tests go here, and they get included if we get support for it, based # on the cryptsetup version. # Binding LUKS2 takes longer, so timeout is increased for a few tests. if luksmeta_data.get('OLD_CRYPTSETUP') == '0' test('bind-wrong-pass-luks2', find_program('bind-wrong-pass-luks2'), env: env) test('bind-luks2', find_program('bind-luks2'), env: env, timeout: 60) test('unbind-unbound-slot-luks2', find_program('unbind-unbound-slot-luks2'), env: env) test('unbind-luks2', find_program('unbind-luks2'), env: env, timeout: 60) if keyutils.found() and keyutils_usable and luksmeta_data.get('OLD_CRYPTSETUP_EXISTING_TOKEN_ID') == '0' test('bind-luks2-ext-token', find_program('bind-luks2-ext-token'), env: env, timeout: 60) endif if jq.found() test('list-recursive-luks2', find_program('list-recursive-luks2'), env: env, timeout: 60) test('list-tang-luks2', find_program('list-tang-luks2'), env: env, timeout: 60) test('list-sss-tang-luks2', find_program('list-sss-tang-luks2'), env: env, timeout: 60) test('list-multiple-slots-luks2', find_program('list-multiple-slots-luks2'), env: env, timeout: 60) endif test('unlock-tang-luks2', find_program('unlock-tang-luks2'), env: env, timeout: 120) test('assume-yes-luks2', find_program('assume-yes-luks2'), env: env, timeout: 90) test('regen-inplace-luks2', find_program('regen-inplace-luks2'), env: env, timeout: 120) test('regen-not-inplace-luks2', find_program('regen-not-inplace-luks2'), env: env, timeout: 120) test('report-tang-luks2', find_program('report-tang-luks2'), env: env, timeout: 120) test('report-sss-luks2', find_program('report-sss-luks2'), env: env, timeout: 120) test('edit-tang-luks2', find_program('edit-tang-luks2'), env: env, timeout: 210) test('backup-restore-luks2', find_program('backup-restore-luks2'), env: env, timeout: 120) test('pass-tang-luks2', find_program('pass-tang-luks2'), env: env, timeout: 60) test('bind-hash-luks2', find_program('bind-hash-luks2'), env: env, timeout: 60) test('regen-hash-luks2', find_program('regen-hash-luks2'), env: env, timeout: 60) test('edit-hash-luks2', find_program('edit-hash-luks2'), env: env, timeout: 60) test('get-hash-luks2', find_program('get-hash-luks2'), env: env, timeout: 60) endif test('unlock-arbitrary-parameter', find_program('unlock-arbitrary-parameter'), env: env) latchset-clevis-6df9b69/src/luks/tests/pass-tang-luks1000077500000000000000000000032041520110721700230210ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's test the passphrase. SLT=1 PASS=$(clevis luks pass -d "${DEV}" -s "${SLT}") echo $PASS >&2 if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" "${PASS}" "" "${SLT}"; then error "Passphrase obtained from clevis luks pass failed." fi latchset-clevis-6df9b69/src/luks/tests/pass-tang-luks2000077500000000000000000000031701520110721700230240ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's test the passphrase. SLT=1 PASS=$(clevis luks pass -d "${DEV}" -s "${SLT}") echo $PASS >&2 if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" "${PASS}" "" "${SLT}"; then error "Passphrase obtained from clevis luks pass failed." fi latchset-clevis-6df9b69/src/luks/tests/regen-hash-luks1000077500000000000000000000061571520110721700231570ustar00rootroot00000000000000#!/bin/bash -x # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS1 with sha512. DEV="${TMP}/luks1-device-sha512" new_device_hash "luks1" "${DEV}" "sha512" # Verify the device was created with sha512. hash_before=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_before}" != "sha512" ]; then error "${TEST}: Device should have been formatted with sha512, got ${hash_before}." fi if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Verify hash is still sha512 after binding. hash_after_bind=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_after_bind}" != "sha512" ]; then error "${TEST}: After binding, hash should be sha512, got ${hash_after_bind}." fi # Now let's remove the initial passphrase. if ! cryptsetup luksRemoveKey --batch-mode "${DEV}" <<< "${DEFAULT_PASS}"; then error "${TEST}: error removing the default password from ${DEV}." fi # Making sure we have a single slot enabled. enabled=$(clevis_luks_used_slots "${DEV}" | wc -l) if [ "${enabled}" -ne 1 ]; then error "${TEST}: we should have only one slot enabled (${enabled})." fi SLT=1 old_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") # Now let's try regen. if ! clevis luks regen -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: clevis luks regen failed" fi new_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") if [ "${old_key}" = "${new_key}" ]; then error "${TEST}: the passphrases should be different" fi # Verify hash is still sha512 after regen. hash_after_regen=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^Hash spec:\s+(\S+)$|\1|p') if [ "${hash_after_regen}" != "sha512" ]; then error "${TEST}: After regen, hash should be sha512, got ${hash_after_regen}." fi # Also check via clevis_luks_get_hash. hash_fn=$(clevis_luks_get_hash "${DEV}") if [ "${hash_fn}" != "sha512" ]; then error "${TEST}: clevis_luks_get_hash should return sha512, got ${hash_fn}." fi latchset-clevis-6df9b69/src/luks/tests/regen-hash-luks2000077500000000000000000000065141520110721700231550ustar00rootroot00000000000000#!/bin/bash -x # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) # LUKS2. if ! luks2_supported; then skip_test "LUKS2 is not supported." fi tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS2 with sha512. DEV="${TMP}/luks2-device-sha512" new_device_hash "luks2" "${DEV}" "sha512" # Verify the device was created with sha512. hash_before=$(cryptsetup luksDump "${DEV}" \ | sed -rn 's|^\s+Hash:\s+(\S+)$|\1|p' | head -1) if [ "${hash_before}" != "sha512" ]; then error "${TEST}: Device should have been formatted with sha512, got ${hash_before}." fi if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Verify the clevis slot (slot 1) also uses sha512 after binding. hash_after_bind=$(cryptsetup luksDump "${DEV}" \ | awk '/^[[:space:]]+1: luks/{found=1} found && /Hash:/{print $2; exit}') if [ "${hash_after_bind}" != "sha512" ]; then error "${TEST}: After binding, clevis slot hash should be sha512, got ${hash_after_bind}." fi # Now let's remove the initial passphrase. if ! cryptsetup luksRemoveKey --batch-mode "${DEV}" <<< "${DEFAULT_PASS}"; then error "${TEST}: error removing the default password from ${DEV}." fi # Making sure we have a single slot enabled. enabled=$(clevis_luks_used_slots "${DEV}" | wc -l) if [ "${enabled}" -ne 1 ]; then error "${TEST}: we should have only one slot enabled (${enabled})." fi SLT=1 old_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") # Now let's try regen. if ! clevis luks regen -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: clevis luks regen failed" fi new_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") if [ "${old_key}" = "${new_key}" ]; then error "${TEST}: the passphrases should be different" fi # Verify the clevis slot (slot 1) still uses sha512 after regen. hash_after_regen=$(cryptsetup luksDump "${DEV}" \ | awk '/^[[:space:]]+1: luks/{found=1} found && /Hash:/{print $2; exit}') if [ "${hash_after_regen}" != "sha512" ]; then error "${TEST}: After regen, clevis slot hash should be sha512, got ${hash_after_regen}." fi # Also check via clevis_luks_get_hash. hash_fn=$(clevis_luks_get_hash "${DEV}") if [ "${hash_fn}" != "sha512" ]; then error "${TEST}: clevis_luks_get_hash should return sha512, got ${hash_fn}." fi latchset-clevis-6df9b69/src/luks/tests/regen-inplace-luks1000077500000000000000000000041761520110721700236460ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's remove the initial passphrase. if ! cryptsetup luksRemoveKey --batch-mode "${DEV}" <<< "${DEFAULT_PASS}"; then error "${TEST}: error removing the default password from ${DEV}." fi # Making sure we have a single slot enabled. enabled=$(clevis_luks_used_slots "${DEV}" | wc -l) if [ "${enabled}" -ne 1 ]; then error "${TEST}: we should have only one slot enabled (${enabled})." fi SLT=1 old_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") # Now let's try regen. if ! clevis luks regen -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: clevis luks regen failed" fi new_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") if [ "${old_key}" = "${new_key}" ]; then error "${TEST}: the passphrases should be different" fi latchset-clevis-6df9b69/src/luks/tests/regen-inplace-luks2000077500000000000000000000041761520110721700236470ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's remove the initial passphrase. if ! cryptsetup luksRemoveKey --batch-mode "${DEV}" <<< "${DEFAULT_PASS}"; then error "${TEST}: error removing the default password from ${DEV}." fi # Making sure we have a single slot enabled. enabled=$(clevis_luks_used_slots "${DEV}" | wc -l) if [ "${enabled}" -ne 1 ]; then error "${TEST}: we should have only one slot enabled (${enabled})." fi SLT=1 old_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") # Now let's try regen. if ! clevis luks regen -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: clevis luks regen failed" fi new_key=$(clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}") if [ "${old_key}" = "${new_key}" ]; then error "${TEST}: the passphrases should be different" fi latchset-clevis-6df9b69/src/luks/tests/regen-not-inplace-luks1000077500000000000000000000045011520110721700244340ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT export TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" SLT=1 if ! clevis luks bind -f -s "${SLT}" -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's rotate the keys in the server and remove the old ones, so that we # will be unable to unlock the volume using clevis and will have to provide # manually a password for clevis luks regen. tang_new_keys "${TMP}" "rotate-keys" tang_remove_rotated_keys "${TMP}" # Making sure we have two slots enabled. enabled=$(clevis_luks_used_slots "${DEV}" | wc -l) if [ "${enabled}" -ne 2 ]; then error "${TEST}: we should have two slots enabled (${enabled})." fi # Make sure we cannot unlock the device. if clevis_luks_unlock_device_by_slot "${DEV}"; then error "${TEST}: we should NOT be able to unlock ${DEV}" fi # Now let's try regen. if ! clevis luks regen -q -d "${DEV}" -s "${SLT}" <<< "${DEFAULT_PASS}"; then error "${TEST}: clevis luks regen failed" fi # Make sure we can unlock the device. if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}"; then error "${TEST}: we should be able to unlock ${DEV}" fi latchset-clevis-6df9b69/src/luks/tests/regen-not-inplace-luks2000077500000000000000000000045011520110721700244350ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT export TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" SLT=1 if ! clevis luks bind -f -s "${SLT}" -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Now let's rotate the keys in the server and remove the old ones, so that we # will be unable to unlock the volume using clevis and will have to provide # manually a password for clevis luks regen. tang_new_keys "${TMP}" "rotate-keys" tang_remove_rotated_keys "${TMP}" # Making sure we have two slots enabled. enabled=$(clevis_luks_used_slots "${DEV}" | wc -l) if [ "${enabled}" -ne 2 ]; then error "${TEST}: we should have two slots enabled (${enabled})." fi # Make sure we cannot unlock the device. if clevis_luks_unlock_device_by_slot "${DEV}"; then error "${TEST}: we should NOT be able to unlock ${DEV}" fi # Now let's try regen. if ! clevis luks regen -q -d "${DEV}" -s "${SLT}" <<< "${DEFAULT_PASS}"; then error "${TEST}: clevis luks regen failed" fi # Make sure we can unlock the device. if ! clevis_luks_unlock_device_by_slot "${DEV}" "${SLT}"; then error "${TEST}: we should be able to unlock ${DEV}" fi latchset-clevis-6df9b69/src/luks/tests/report-sss-luks1000077500000000000000000000042451520110721700232530ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"t": 1, "pins":{"tang":[{"url":"%s"}], "sss":{"t":1,"pins":{"tang":[{"url":"%s"}]}}}}' "${url}" "${url}") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" SLT=3 if ! clevis luks bind -y -d "${DEV}" -s "${SLT}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Report should return 0, as no keys are rotated. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded, since no keys were rotated" fi # Now let's rotate the keys. tang_new_keys "${TMP}" "rotate-keys" # Report should now return 1. if clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have indicated keys were rotated" fi # Now let's regen the keys. if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then error "${TEST}: report with regen should have succeeded" fi # Report should return 0 again. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded after regen" fi latchset-clevis-6df9b69/src/luks/tests/report-sss-luks2000077500000000000000000000042451520110721700232540ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"t": 1, "pins":{"tang":[{"url":"%s"}], "sss":{"t":1,"pins":{"tang":[{"url":"%s"}]}}}}' "${url}" "${url}") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" SLT=3 if ! clevis luks bind -y -d "${DEV}" -s "${SLT}" sss "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Report should return 0, as no keys are rotated. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded, since no keys were rotated" fi # Now let's rotate the keys. tang_new_keys "${TMP}" "rotate-keys" # Report should now return 1. if clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have indicated keys were rotated" fi # Now let's regen the keys. if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then error "${TEST}: report with regen should have succeeded" fi # Report should return 0 again. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded after regen" fi latchset-clevis-6df9b69/src/luks/tests/report-tang-luks1000077500000000000000000000041431520110721700233710ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" SLT=3 if ! clevis luks bind -f -d "${DEV}" -s "${SLT}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Report should return 0, as no keys are rotated. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded, since no keys were rotated" fi # Now let's rotate the keys. tang_new_keys "${TMP}" "rotate-keys" # Report should now return 1. if clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have indicated keys were rotated" fi # Now let's regen the keys. if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then error "${TEST}: report with regen should have succeeded" fi # Report should return 0 again. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded after regen" fi latchset-clevis-6df9b69/src/luks/tests/report-tang-luks2000077500000000000000000000041431520110721700233720ustar00rootroot00000000000000#!/bin/bash -x # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST="${0}" . luks-common-test-functions . clevis-luks-common-functions function on_exit() { [ -d "${TMP}" ] || return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT TMP=$(mktemp -d) tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" SLT=3 if ! clevis luks bind -f -d "${DEV}" -s "${SLT}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Report should return 0, as no keys are rotated. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded, since no keys were rotated" fi # Now let's rotate the keys. tang_new_keys "${TMP}" "rotate-keys" # Report should now return 1. if clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have indicated keys were rotated" fi # Now let's regen the keys. if ! clevis luks report -q -r -d "${DEV}" -s "${SLT}"; then error "${TEST}: report with regen should have succeeded" fi # Report should return 0 again. if ! clevis luks report -q -d "${DEV}" -s "${SLT}"; then error "${TEST}: report should have succeeded after regen" fi latchset-clevis-6df9b69/src/luks/tests/unbind-luks1000077500000000000000000000044511520110721700224100ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS1. DEV="${TMP}/luks1-device" UUID="cb6e8904-81ff-40da-a84a-07ab9ab5715e" new_device "luks1" "${DEV}" # Bind, initially. if ! clevis luks bind -f -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." >&2 fi SLT=1 if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "active" ]; then error "${TEST}: state (${state}) is expected to be 'active'." >&2 fi if [ "${uuid}" != "${UUID}" ]; then error "${TEST}: UUID ($uuid) is expected to be '${UUID}'." >&2 fi # Now unbind. if ! clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to succeed for device ${DEV} and slot ${SLT}" >&2 fi if ! read -r _ state uuid < <(luksmeta show -d "${DEV}" | grep "^${SLT} *"); then error "${TEST}: Error reading LUKSmeta info for slot ${SLT} of ${DEV}." >&2 fi if [ "${state}" != "inactive" ]; then error "${TEST}: state (${state}) is expected to be 'inactive'." >&2 fi if [ "${uuid}" != "empty" ]; then error "${TEST}: UUID ($uuid) is expected to be 'empty'." >&2 fi latchset-clevis-6df9b69/src/luks/tests/unbind-luks2000077500000000000000000000037521520110721700224140ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR if ! luks2_supported; then error "{TEST}: LUKS2 is not supported." fi TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" CFG="$(printf '{"url":"foobar","adv":"%s"}' "$ADV")" # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" # Binding. if ! clevis luks bind -d "${DEV}" tang "${CFG}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed." fi SLT=1 if ! clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to succeed for device ${DEV} and slot ${SLT}" fi SLT=0 if ! echo "${DEFAULT_PASS}" | cryptsetup open --test-passphrase "${DEV}" --key-slot "${SLT}"; then error "${TEST}: Unable to open device ${DEV}:${SLT}" fi if clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to fail for device ${DEV}:${SLT} that is not bound with clevis" fi if ! echo "${DEFAULT_PASS}" | cryptsetup open --test-passphrase "${DEV}" --key-slot "${SLT}"; then error "${TEST}: Unbind is expected not to remove non clevis slots" fi latchset-clevis-6df9b69/src/luks/tests/unbind-unbound-slot-luks1000077500000000000000000000022561520110721700250400ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR TMP="$(mktemp -d)" # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" SLT=2 if clevis luks unbind -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to fail for device ${DEV} and slot ${SLT}" >&2 fi latchset-clevis-6df9b69/src/luks/tests/unbind-unbound-slot-luks2000077500000000000000000000023571520110721700250430ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") . luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'exit' ERR if ! luks2_supported; then error "{TEST}: LUKS2 is not supported." fi TMP="$(mktemp -d)" DEV="${TMP}/luks2-device" SLT=2 new_device "luks2" "${DEV}" if clevis luks unbind -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to fail for device ${DEV} and slot ${SLT}" >&2 fi latchset-clevis-6df9b69/src/luks/tests/unlock-arbitrary-parameter000077500000000000000000000036301520110721700253400ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi TESTPARAM="arbitrarytestparameter" #set up a "cryptsetup" function, to hijack the command cryptsetup () { #need to handle "cryptsetup isLuks" from clevis-luks-unlock, among others if [[ $1 == "isLuks" ]]; then exit 0; elif [[ $1 == "luksUUID" ]]; then echo "TESTINGLUKSUUID" exit 0; else echo "$*" | grep -q -- "${TESTPARAM}" exit $? fi } export -f cryptsetup if ! clevis-luks-unlock -o "$TESTPARAM" -d ${DEV} -n clevis_unlock_test; then error "${TEST}: clevis luks unlock did not match arbitrary test parameter \"$TESTPARAM\"." fi latchset-clevis-6df9b69/src/luks/tests/unlock-tang-luks1000077500000000000000000000045631520110721700233570ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS1. DEV="${TMP}/luks1-device" new_device "luks1" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we were unable to unlock ${DEV}." fi # Let's rotate the tang keys and add another binding with the new key. tang_new_keys "${TMP}" "rotate-keys" # Unlock should still work now. if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we should still be able to unlock ${DEV}" fi # Now let's remove the rotated keys. tang_remove_rotated_keys "${TMP}" # Unlock should not work anymore. if clevis_luks_unlock_dev "${DEV}"; then error "${TEST}: we should not be able to unlock ${DEV}" fi # Now let's add another binding with the new keys. tang_get_adv "${port}" "${adv}" # Updating the advertisement. if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Unlock should work again, using the new keys. if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we should be able to unlock ${DEV} with the new keys" fi latchset-clevis-6df9b69/src/luks/tests/unlock-tang-luks2000077500000000000000000000045661520110721700233630ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . TEST=$(basename "${0}") . luks-common-test-functions . clevis-luks-common-functions on_exit() { [ ! -d "${TMP}" ] && return 0 tang_stop "${TMP}" rm -rf "${TMP}" } trap 'on_exit' EXIT trap 'on_exit' ERR TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" adv="${TMP}/adv" tang_get_adv "${port}" "${adv}" cfg=$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv") # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we were unable to unlock ${DEV}." fi # Let's rotate the tang keys and add another binding with the new key. tang_new_keys "${TMP}" "rotate-keys" # Unlock should still work now. if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we should still be able to unlock ${DEV}" fi # Now let's remove the rotated keys. tang_remove_rotated_keys "${TMP}" # Unlock should not work anymore. if clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we should not be able to unlock ${DEV}" fi # Now let's add another binding with the new keys. tang_get_adv "${port}" "${adv}" # Updating the advertisement. if ! clevis luks bind -f -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then error "${TEST}: Bind should have succeeded." fi # Unlock should work again, using the new keys. if ! clevis_luks_unlock_device "${DEV}"; then error "${TEST}: we should be able to unlock ${DEV} with the new keys" fi latchset-clevis-6df9b69/src/luks/udisks2/000077500000000000000000000000001520110721700203645ustar00rootroot00000000000000latchset-clevis-6df9b69/src/luks/udisks2/clevis-luks-udisks2.c000066400000000000000000000407651520110721700243670ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "token-to-jwe.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define UERR ((uid_t) -1) #define GERR ((gid_t) -1) #define UUID_TMPL \ "%02hhx%02hhx%02hhx%02hhx-" \ "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-" \ "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" #define UUID_ARGS(u) \ u[0x0], u[0x1], u[0x2], u[0x3], u[0x4], u[0x5], u[0x6], u[0x7], \ u[0x8], u[0x9], u[0xa], u[0xb], u[0xc], u[0xd], u[0xe], u[0xf] enum { PIPE_RD = 0, PIPE_WR = 1 }; struct context { UDisksClient *clt; GMainLoop *loop; GList *lst; int sock; }; static const luksmeta_uuid_t CLEVIS_LUKS_UUID = { 0xcb, 0x6e, 0x89, 0x04, 0x81, 0xff, 0x40, 0xda, 0xa8, 0x4a, 0x07, 0xab, 0x9a, 0xb5, 0x71, 0x5e }; static void remove_path(GList **lst, const char *path) { GList *i = NULL; while ((i = g_list_find_custom(*lst, path, (GCompareFunc) g_strcmp0))) { *lst = g_list_remove(*lst, i->data); g_free(i->data); } } static gboolean idle(gpointer misc) { struct context *ctx = misc; GVariant *options = NULL; options = g_variant_new_parsed("@a{sv} { %s: }", "auth.no_user_interaction"); if (!options) goto error; g_variant_ref_sink(options); for (GList *i = ctx->lst; i; i = i->next) { UDisksEncrypted *enc = NULL; const char *path = i->data; UDisksObject *uobj = NULL; UDisksBlock *block = NULL; const char *dev = NULL; pkt_t pkt = {}; uobj = udisks_client_peek_object(ctx->clt, path); if (!uobj) continue; enc = udisks_object_peek_encrypted(uobj); if (!enc) continue; block = udisks_object_peek_block(uobj); if (!block) continue; dev = udisks_block_get_device(block); if (!dev) continue; pkt.used = strlen(dev) + 1; if ((size_t) pkt.used > sizeof(pkt.data)) continue; strcpy(pkt.data, dev); if (send(ctx->sock, pkt.data, pkt.used, 0) != pkt.used) { g_main_loop_quit(ctx->loop); break; } memset(&pkt, 0, sizeof(pkt)); pkt.used = recv(ctx->sock, pkt.data, sizeof(pkt.data), 0); if (pkt.used == 0) continue; else if (pkt.used < 0 || (size_t) pkt.used >= sizeof(pkt.data)) { g_main_loop_quit(ctx->loop); break; } /* NOTE: pkt.data is now implicitly NULL terminated regardless of * whether or not the plaintext inside the JWE was terminated. */ udisks_encrypted_call_unlock_sync(enc, pkt.data, options, NULL, NULL, NULL); memset(&pkt, 0, sizeof(pkt)); } error: g_list_free_full(ctx->lst, g_free); g_variant_unref(options); ctx->lst = NULL; return FALSE; } static void oadd(GDBusObjectManager *mgr, GDBusObject *obj, gpointer misc) { struct context *ctx = misc; UDisksObject *uobj = NULL; const char *path = NULL; const char *back = NULL; UDisksBlock *ct = NULL; UDisksBlock *pt = NULL; GList *tmp = NULL; char *ptmp = NULL; path = g_dbus_object_get_object_path(obj); if (!path) return; uobj = udisks_client_peek_object(ctx->clt, path); if (!uobj) return; ct = udisks_object_peek_block(uobj); if (!ct) return; back = udisks_block_get_crypto_backing_device(ct); if (back) remove_path(&ctx->lst, back); if (!udisks_block_get_hint_auto(ct)) return; if (!udisks_object_peek_encrypted(uobj)) return; pt = udisks_client_get_cleartext_block(ctx->clt, ct); if (pt) { g_object_unref(pt); return; } ptmp = g_strdup(path); if (!ptmp) return; tmp = g_list_prepend(ctx->lst, ptmp); if (!tmp) { g_free(ptmp); return; } ctx->lst = tmp; g_idle_add(idle, ctx); } static void orem(GDBusObjectManager *mgr, GDBusObject *obj, gpointer misc) { struct context *ctx = misc; remove_path(&ctx->lst, g_dbus_object_get_object_path(obj)); } static gboolean sockerr(gint fd, GIOCondition cond, gpointer misc) { struct context *ctx = misc; close(fd); g_main_loop_quit(ctx->loop); return FALSE; } static int child_main(int sock) { struct context ctx = { .sock = sock }; int exit_status = EXIT_FAILURE; GDBusObjectManager *mgr = NULL; gulong id = 0; ctx.loop = g_main_loop_new(NULL, FALSE); if (!ctx.loop) goto error; ctx.clt = udisks_client_new_sync(NULL, NULL); if (!ctx.clt) goto error; mgr = udisks_client_get_object_manager(ctx.clt); if (!mgr) goto error; id = g_signal_connect(mgr, "object-added", G_CALLBACK(oadd), &ctx); if (id == 0) goto error; id = g_signal_connect(mgr, "object-removed", G_CALLBACK(orem), &ctx); if (id == 0) goto error; id = g_unix_fd_add(sock, G_IO_ERR, sockerr, &ctx); if (id == 0) goto error; g_main_loop_run(ctx.loop); exit_status = EXIT_SUCCESS; error: g_list_free_full(ctx.lst, g_free); if (ctx.loop) g_main_loop_unref(ctx.loop); if (ctx.clt) g_object_unref(ctx.clt); close(sock); return exit_status; } /* * ========================================================================== * Caution, code below this point runs with euid = 0! * ========================================================================== */ static int pair[2] = { -1, -1 }; pid_t pid = 0; static void safeclose(int *fd) { if (*fd >= 0) close(*fd); *fd = -1; } static void on_signal(int sig) { if (sig == SIGCHLD) { if (wait(NULL) != pid) return; pid = -1; } safeclose(&pair[0]); } static uint32_t recover_key(const pkt_t *jwe, char *out, int32_t max, uid_t uid, gid_t gid) { int push[2] = { -1, -1 }; int pull[2] = { -1, -1 }; int32_t bytes = 0; pid_t chld = 0; if (pipe(push) != 0) goto error; if (pipe(pull) != 0) goto error; chld = fork(); if (chld < 0) { perror("fork"); goto error; } if (chld == 0) { char *const env[] = { "PATH=" BINDIR, NULL }; int r = 0; if (geteuid() != 0) { if (setgroups(1, &gid) != 0) { perror("setgroups"); /* Can fail if missing permissions */ } } if (initgroups(CLEVIS_USER, gid) != 0) { perror("initgroups"); exit(EXIT_FAILURE); } if (setgid(gid) != 0) { perror("setgid"); exit(EXIT_FAILURE); } if (setuid(uid) != 0) { perror("setuid"); exit(EXIT_FAILURE); } r = dup2(push[PIPE_RD], STDIN_FILENO); if (r != STDIN_FILENO) { perror("dup2"); exit(EXIT_FAILURE); } r = dup2(pull[PIPE_WR], STDOUT_FILENO); if (r != STDOUT_FILENO) { perror("dup2"); exit(EXIT_FAILURE); } safeclose(&push[PIPE_RD]); safeclose(&push[PIPE_WR]); safeclose(&pull[PIPE_RD]); safeclose(&pull[PIPE_WR]); execle(BINDIR "/clevis", "clevis", "decrypt", NULL, env); perror("execle"); exit(EXIT_FAILURE); } safeclose(&push[PIPE_RD]); safeclose(&pull[PIPE_WR]); bytes = write(push[PIPE_WR], jwe->data, jwe->used); safeclose(&push[PIPE_WR]); if (bytes < 0 || bytes != jwe->used) { errno = errno == 0 ? EIO : errno; kill(chld, SIGTERM); goto error; } bytes = 0; ssize_t block = 0; while (max > 0 && max > bytes) { do { block = read(pull[PIPE_RD], &out[bytes], max - bytes); } while (block < 0 && errno == EINTR); if (block < 0 || block < INT32_MIN || block > INT32_MAX) { kill(chld, SIGTERM); goto error; } if (block == 0) break; bytes += block; } safeclose(&pull[PIPE_RD]); return bytes; error: safeclose(&push[PIPE_RD]); safeclose(&push[PIPE_WR]); safeclose(&pull[PIPE_RD]); safeclose(&pull[PIPE_WR]); return -errno; } static bool log_attempt(int log, struct crypt_device *cd, bool success) { const char *uuid = NULL; char msg[4096] = {}; char *dev = NULL; int r = 0; uuid = crypt_get_uuid(cd); if (!uuid) return false; dev = audit_encode_nv_string("device", crypt_get_device_name(cd), 0); if (!dev) return false; r = snprintf(msg, sizeof(msg), "op=recovered-key-for uuid=%s %s", uuid, dev); free(dev); if (r < 0 || (size_t) r >= sizeof(msg)) return false; return audit_log_user_message(log, AUDIT_USER_DEVICE, msg, NULL, NULL, NULL, success) > 0; } static const char *sopts = "hu:g:"; static const struct option lopts[] = { { "help", no_argument, .val = 'h' }, { "user", required_argument, .val = 'u' }, { "group", required_argument, .val = 'g' }, {} }; static uid_t usr2uid(const char *usr) { const struct passwd *tmp = getpwnam(usr); return tmp ? tmp->pw_uid : UERR; } static gid_t grp2gid(const char *grp) { const struct group *tmp = getgrnam(grp); return tmp ? tmp->gr_gid : GERR; } int main(int argc, char *const argv[]) { gid_t recg = grp2gid(CLEVIS_GROUP); /* Recovery group */ uid_t recu = usr2uid(CLEVIS_USER); /* Recovery user */ gid_t unlg = getgid(); /* Unlock group */ uid_t unlu = getuid(); /* Unlock user */ int log = -1; if (recu == UERR) { fprintf(stderr, "Invalid user name '%s'!\n", CLEVIS_USER); return EXIT_FAILURE; } if (recg == GERR) { fprintf(stderr, "Invalid group name '%s'!\n", CLEVIS_GROUP); return EXIT_FAILURE; } if (geteuid() != 0) { fprintf(stderr, "Root privileges required!\n"); return EXIT_FAILURE; } for (int c; (c = getopt_long(argc, argv, sopts, lopts, NULL)) >= 0; ) { switch (c) { case 'u': if (getuid() != 0) { fprintf(stderr, "You can only specify the user as root!\n"); return EXIT_FAILURE; } unlu = usr2uid(optarg); if (unlu == 0 || unlu == UERR) { fprintf(stderr, "Invalid user name '%s'!\n", optarg); return EXIT_FAILURE; } break; case 'g': if (getuid() != 0) { fprintf(stderr, "You can only specify the group as root!\n"); return EXIT_FAILURE; } unlg = grp2gid(optarg); if (unlg == 0 || unlg == GERR) { fprintf(stderr, "Invalid group name '%s'!\n", optarg); return EXIT_FAILURE; } break; default: fprintf(stderr, "Usage: clevis-luks-udisks2 [-u USER -g GROUP]\n"); return EXIT_FAILURE; } } if (unlu == 0 || unlg == 0) { fprintf(stderr, "Either run as SETUID=root or use -u/-g!\n"); return EXIT_FAILURE; } if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) == -1) return EXIT_FAILURE; pid = fork(); if (pid < 0) { safeclose(&pair[0]); safeclose(&pair[1]); return EXIT_FAILURE; } if (pid == 0) { int status = EXIT_FAILURE; safeclose(&pair[0]); if (setgid(unlg) == 0 && setegid(unlg) == 0 && setuid(unlu) == 0 && seteuid(unlu) == 0) status = child_main(pair[1]); safeclose(&pair[1]); return status; } safeclose(&pair[1]); signal(SIGHUP, on_signal); signal(SIGINT, on_signal); signal(SIGPIPE, on_signal); signal(SIGTERM, on_signal); signal(SIGUSR1, on_signal); signal(SIGUSR2, on_signal); signal(SIGCHLD, on_signal); if (setgid(0) == -1 || setegid(0) == -1 || setuid(0) == -1 || seteuid(0) == -1) goto error; log = audit_open(); if (log < 0) goto error; for (pkt_t req = {}, jwe = {}, key = {}; ; key = (pkt_t) {}) { struct crypt_device *cd = NULL; /* Receive a request. Ensure that it is null terminated. */ req.used = recv(pair[0], req.data, sizeof(req.data), 0); if (req.used < 1 || req.data[req.used - 1]) break; /* Technically, req.data is already NULL-terminated, but let's * be explicit. */ req.data[req.used - 1] = '\0'; if (crypt_init(&cd, req.data) < 0) goto next; if (crypt_load(cd, CRYPT_LUKS1, NULL) >= 0) { const int slotlen = crypt_keyslot_max(CRYPT_LUKS1); luksmeta_uuid_t uuid = {}; for (uint8_t s = 0; s < slotlen && key.used <= 0; s++) { fprintf(stderr, "%s\tSLOT\t%hhu\n", req.data, s); switch (crypt_keyslot_status(cd, s)) { case CRYPT_SLOT_ACTIVE: case CRYPT_SLOT_ACTIVE_LAST: break; default: continue; } jwe.used = luksmeta_load(cd, s, uuid, jwe.data, sizeof(jwe.data)); fprintf(stderr, "%s\tMETA\t%s\n", req.data, strerror(jwe.used < 0 ? -jwe.used : 0)); if (jwe.used <= 0) continue; fprintf(stderr, "%s\tUUID\t" UUID_TMPL "\n", req.data, UUID_ARGS(uuid)); if (memcmp(uuid, CLEVIS_LUKS_UUID, sizeof(uuid)) != 0) continue; /* Recover the key from the JWE. */ key.used = recover_key(&jwe, key.data, sizeof(key.data), recu, recg); fprintf(stderr, "%s\tRCVR\t%s (%zd)\n", req.data, strerror(key.used < 0 ? -key.used : 0), key.used); } } else if (crypt_load(cd, CRYPT_LUKS2, NULL) >= 0) { for (int t = 0; key.used <= 0; t++) { const char *json = NULL; const char *type = NULL; int r = 0; r = crypt_token_status(cd, t, &type); if (r == CRYPT_TOKEN_INVALID) break; else if (r != CRYPT_TOKEN_EXTERNAL_UNKNOWN) continue; fprintf(stderr, "%s\tTOKN\t%d\t%s\n", req.data, t, type); if (strcmp(type, "clevis") != 0) continue; r = crypt_token_json_get(cd, t, &json); fprintf(stderr, "%s\tMETA\t%s\n", req.data, strerror(r < 0 ? -r : 0)); if (!token_to_jwe(json, &jwe)) continue; /* Recover the key from the JWE. */ key.used = recover_key(&jwe, key.data, sizeof(key.data), recu, recg); fprintf(stderr, "%s\tRCVR\t%s (%zd)\n", req.data, strerror(key.used < 0 ? -key.used : 0), key.used); } } if (key.used < 0) key.used = 0; /* Don't return the key unless auditing succeeds. */ if (!log_attempt(log, cd, key.used > 0)) memset(&key, 0, sizeof(key)); next: crypt_free(cd); /* Send the key as a reply. */ if (send(pair[0], key.data, key.used, 0) != key.used) break; } error: safeclose(&log); safeclose(&pair[0]); if (pid != -1) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); } return EXIT_FAILURE; } latchset-clevis-6df9b69/src/luks/udisks2/clevis-luks-udisks2.desktop.in000066400000000000000000000001571520110721700262120ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=clevis-luks-udisks2 Exec=@libexecdir@/clevis-luks-udisks2 NoDisplay=true latchset-clevis-6df9b69/src/luks/udisks2/meson.build000066400000000000000000000016271520110721700225340ustar00rootroot00000000000000audit = dependency('audit', version: '>=2.7.8', required: false) udisks2 = dependency('udisks2', required: false) gio = dependency('gio-2.0', required: false) if udisks2.found() and audit.found() and gio.found() autostartdir = join_paths(sysconfdir, 'xdg', 'autostart') configure_file( input: 'clevis-luks-udisks2.desktop.in', output: 'clevis-luks-udisks2.desktop', install_dir: autostartdir, configuration: data, ) executable('clevis-luks-udisks2', ['clevis-luks-udisks2.c', 'token-to-jwe.c'], dependencies: [udisks2, luksmeta, audit, jansson], install_dir: libexecdir, install: true, ) else warning('Will not build udisks2 support due to missing dependencies!') endif if jansson.found() test_token_to_jwe = executable('test-token-to-jwe', ['test-token-to-jwe.c', 'token-to-jwe.c'], dependencies: [jansson], ) test('token-to-jwe', test_token_to_jwe) endif latchset-clevis-6df9b69/src/luks/udisks2/test-token-to-jwe.c000066400000000000000000000067301520110721700240360ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2026 Red Hat, Inc. * Author: Sergio Correia * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "token-to-jwe.h" #include #include #include #include static const char valid_token[] = "{" " \"type\": \"clevis\"," " \"keyslots\": [\"1\"]," " \"jwe\": {" " \"protected\": \"eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0\"," " \"encrypted_key\": \"\"," " \"iv\": \"oB2uB6_a2LCQnhNk\"," " \"ciphertext\": \"Gss774jh5EcnMA5NacAxuX8\"," " \"tag\": \"6L9KBrn6-R1---wTikJTrA\"" " }" "}"; static void test_basic_conversion(void) { pkt_t pkt = {}; const char *expected = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0" "." "." "oB2uB6_a2LCQnhNk" "." "Gss774jh5EcnMA5NacAxuX8" "." "6L9KBrn6-R1---wTikJTrA"; assert(token_to_jwe(valid_token, &pkt)); assert(strcmp(pkt.data, expected) == 0); fprintf(stderr, "test_basic_conversion: PASS\n"); } static void test_used_equals_strlen(void) { pkt_t pkt = {}; assert(token_to_jwe(valid_token, &pkt)); assert(pkt.used == (ssize_t) strlen(pkt.data)); fprintf(stderr, "test_used_equals_strlen: PASS\n"); } static void test_invalid_json(void) { pkt_t pkt = {}; assert(!token_to_jwe(NULL, &pkt)); assert(!token_to_jwe("not json", &pkt)); assert(!token_to_jwe("{}", &pkt)); assert(!token_to_jwe("{\"jwe\":{}}", &pkt)); assert(!token_to_jwe("{\"jwe\":{\"protected\":\"a\"}}", &pkt)); fprintf(stderr, "test_invalid_json: PASS\n"); } static void test_empty_components(void) { const char *json = "{\"jwe\":{" "\"protected\":\"\"," "\"encrypted_key\":\"\"," "\"iv\":\"\"," "\"ciphertext\":\"\"," "\"tag\":\"\"" "}}"; pkt_t pkt = {}; assert(token_to_jwe(json, &pkt)); assert(strcmp(pkt.data, "....") == 0); assert(pkt.used == 4); assert(pkt.used == (ssize_t) strlen(pkt.data)); fprintf(stderr, "test_empty_components: PASS\n"); } static void test_single_char_components(void) { const char *json = "{\"jwe\":{" "\"protected\":\"a\"," "\"encrypted_key\":\"b\"," "\"iv\":\"c\"," "\"ciphertext\":\"d\"," "\"tag\":\"e\"" "}}"; pkt_t pkt = {}; assert(token_to_jwe(json, &pkt)); assert(strcmp(pkt.data, "a.b.c.d.e") == 0); assert(pkt.used == 9); assert(pkt.used == (ssize_t) strlen(pkt.data)); fprintf(stderr, "test_single_char_components: PASS\n"); } int main(void) { test_basic_conversion(); test_used_equals_strlen(); test_invalid_json(); test_empty_components(); test_single_char_components(); fprintf(stderr, "All tests passed.\n"); return EXIT_SUCCESS; } latchset-clevis-6df9b69/src/luks/udisks2/token-to-jwe.c000066400000000000000000000034161520110721700230570ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "token-to-jwe.h" #include #include #include bool token_to_jwe(const char *json, pkt_t *pkt) { json_auto_t *tokn = NULL; const json_t *jwe = NULL; const char *prt = NULL; const char *key = NULL; const char *tag = NULL; const char *iv = NULL; const char *ct = NULL; if (!json) return false; tokn = json_loads(json, 0, NULL); if (!tokn) return false; jwe = json_object_get(tokn, "jwe"); if (!jwe) return false; if (json_unpack((json_t *) jwe, "{s:s,s:s,s:s,s:s,s:s}", "protected", &prt, "encrypted_key", &key, "iv", &iv, "ciphertext", &ct, "tag", &tag) < 0) return false; pkt->used = snprintf(pkt->data, sizeof(pkt->data), "%s.%s.%s.%s.%s", prt, key, iv, ct, tag); if (pkt->used < 0 || (size_t) pkt->used >= sizeof(pkt->data)) return false; return true; } latchset-clevis-6df9b69/src/luks/udisks2/token-to-jwe.h000066400000000000000000000021101520110721700230520ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef TOKEN_TO_JWE_H #define TOKEN_TO_JWE_H #include #include #define MAX_UDP 65507 typedef struct { ssize_t used; char data[MAX_UDP]; } pkt_t; bool token_to_jwe(const char *json, pkt_t *pkt); #endif /* TOKEN_TO_JWE_H */ latchset-clevis-6df9b69/src/meson.build000066400000000000000000000005011520110721700201600ustar00rootroot00000000000000subdir('bash') subdir('luks') subdir('pins') subdir('initramfs-tools') bins += join_paths(meson.current_source_dir(), 'clevis-decrypt') mans += join_paths(meson.current_source_dir(), 'clevis-decrypt.1') bins += join_paths(meson.current_source_dir(), 'clevis') mans += join_paths(meson.current_source_dir(), 'clevis.1') latchset-clevis-6df9b69/src/pins/000077500000000000000000000000001520110721700167735ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/file/000077500000000000000000000000001520110721700177125ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/file/clevis-decrypt-file000077500000000000000000000032441520110721700235150ustar00rootroot00000000000000#!/bin/bash set -eu # Copyright (c) 2020 Christoph Biedl # Author: Christoph Biedl # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # [ $# -eq 1 ] && [ "${1:-}" = "--summary" ] && exit 2 if [ -t 0 ] ; then echo >&2 echo 'Usage: clevis decrypt file < JWE > PLAINTEXT' >&2 echo >&2 exit 1 fi read -d . hdr64 if ! hdr="$(jose fmt --quote="$hdr64" --string --b64load --object --output=-)" ; then echo 'JWE header corrupt' >&2 exit 1 fi if [ "$(jose fmt --json="$hdr" --get clevis --get pin --unquote=-)" != 'file' ] ; then echo 'JWE pin mismatch!' >&2 exit 1 fi if ! name="$(jose fmt --json="$hdr" --get clevis --get file --get name --unquote=-)" ; then echo 'JWE missing 'clevis.file.name' header parameter!' >&2 exit 1 fi if [ ! -f "$name" ] ; then echo "Key file $name not found" >&2 exit 1 fi jwk="$(cat "$name")" if ! jose fmt --json="$jwk" --object --output=/dev/null 2>/dev/null ; then echo "Key file $name is malformed" >&2 exit 1 fi ( printf '%s' "$jwk$hdr64." ; cat ) | exec jose jwe dec --key=- --input=- latchset-clevis-6df9b69/src/pins/file/clevis-encrypt-file000077500000000000000000000035771520110721700235400ustar00rootroot00000000000000#!/bin/sh set -eu # Copyright (c) 2020 Christoph Biedl # Author: Christoph Biedl # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # SUMMARY='Encrypts using a jwk stored in a file policy' if [ "${1:-}" = '--summary' ] ; then echo "$SUMMARY" exit 0 fi if [ -t 0 ] ; then exec >&2 echo echo 'Usage: clevis encrypt file CONFIG < PLAINTEXT > JWE' echo echo "$SUMMARY" echo echo 'his command uses the following configuration properties:' echo echo ' name: The file that holds the encryption key (REQUIRED)' echo exit 2 fi if ! cfg="$(jose fmt --json="$1" --object --output=- 2>/dev/null)" ; then echo 'Configuration is malformed!' >&2 exit 1 fi if ! name="$(jose fmt --json="$cfg" --object --get name --unquote=-)" ; then echo 'Missing the required name property!' >&2 exit 1 fi if [ -e "$name" ] ; then echo "File $name already exists" >&2 exit 1 fi jwk="$(jose jwk gen --input='{"alg":"A256GCM"}')" ( umask 0377 ; echo "$jwk" >"$name" ) jwe='{"protected":{"clevis":{"pin":"file","file":{}}}}' jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get file --quote "$name" --set name -UUUU --output=-)" ( printf '%s' "$jwe$jwk" ; cat ) | exec jose jwe enc --input=- --key=- --detached=- --compact latchset-clevis-6df9b69/src/pins/file/clevis-encrypt-file.1.adoc000066400000000000000000000015531520110721700245710ustar00rootroot00000000000000CLEVIS-ENCRYPT-FILE(1) ====================== :doctype: manpage == NAME clevis-encrypt-file - Encrypts using a file policy == SYNOPSIS *clevis encrypt file* CONFIG < PT > JWE == OVERVIEW The *clevis encrypt file* command encrypts using a file policy. Its only argument is the JSON configuration object. Encrypting data using the file pin works like this: $ clevis encrypt file '{"name":"/path/to/file"}' < PT > JWE The given file must not exist yet. To decrypt the data, just pass it to the *clevis decrypt* command: $ clevis decrypt < JWE > PT == CONFIG This command uses the following configuration properties: * *name* (string) : The name to the file where the jwk is stored (REQUIRED) == BUGS Requires that directories for that file already exist. Rather for educational purposes. == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/pins/file/dracut.module-setup.sh.in000077500000000000000000000016041520110721700245630ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2020 Christoph Biedl # Author: Christoph Biedl # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # check() { require_binaries clevis-decrypt-file || return 1 } depends() { echo clevis return 0 } install() { inst clevis-decrypt-file } latchset-clevis-6df9b69/src/pins/file/initramfs.in000077500000000000000000000020711520110721700222410ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2020 Christoph Biedl # Author: Christoph Biedl # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # case $1 in prereqs) exit 0 ;; esac . @initramfstoolsdir@/hook-functions die() { code="$1" msg="$2" echo " (ERROR): $msg" >&2 exit "$code" } if [ -x @bindir@/clevis-decrypt-file ]; then copy_exec @bindir@/clevis-decrypt-file || die 1 "Unable to copy @bindir@/clevis-decrypt-file to initrd image" fi latchset-clevis-6df9b69/src/pins/file/meson.build000066400000000000000000000026561520110721700220650ustar00rootroot00000000000000 dracut = dependency('dracut', required: false) initramfs_tools = find_program('update-initramfs', required: false) bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-file') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-file') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-file.1') env = environment() env.append('PATH', join_paths(meson.source_root(), 'src'), meson.current_source_dir(), '/usr/libexec', libexecdir, separator: ':' ) test('pin-file', find_program('./pin-file'), env: env) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-file' configure_file( input: 'dracut.module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) else warning('Will not install dracut module clevis-pin-file due to missing dependencies!') endif if initramfs_tools.found() initramfstools_dir = '/usr/share/initramfs-tools' initramfs_hooks_dir = '/usr/share/initramfs-tools/hooks' initramfs_data = configuration_data() initramfs_data.merge_from(data) initramfs_data.set('initramfstoolsdir', initramfstools_dir) configure_file( input: 'initramfs.in', output: 'clevis-pin-file', install_dir: initramfs_hooks_dir, configuration: initramfs_data, ) else warning('Will not install initramfs module clevis-pin-file due to missing dependencies!') endif latchset-clevis-6df9b69/src/pins/file/pin-file000077500000000000000000000020231520110721700213400ustar00rootroot00000000000000#!/bin/sh set -e # Copyright (c) 2020 Christoph Biedl # Author: Christoph Biedl # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TMP="$(mktemp -d)" # shellcheck disable=SC2064 # Expanding TMP right now is intended trap "rm -rf \"$TMP\"" EXIT cfg="$(printf '{"name":"%s"}' "$TMP/key")" inp='hi' enc="$(printf '%s' "$inp" | clevis encrypt file "$cfg")" dec="$(printf '%s' "$enc" | clevis decrypt)" test "$dec" = "$inp" latchset-clevis-6df9b69/src/pins/meson.build000066400000000000000000000002101520110721700211260ustar00rootroot00000000000000subdir('file') subdir('sss') subdir('tang') if not get_option('tpm1').disabled() subdir('tpm1') endif subdir('tpm2') subdir('pkcs11') latchset-clevis-6df9b69/src/pins/pkcs11/000077500000000000000000000000001520110721700200755ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/pkcs11/clevis-decrypt-pkcs11000077500000000000000000000075631520110721700240730ustar00rootroot00000000000000#!/bin/bash set -eo pipefail # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # PIN_NAME=pkcs11 PIN_FILE="/run/systemd/clevis-pkcs11.pin" . clevis-pkcs11-common [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis decrypt pkcs11 < JWE > PLAINTEXT" echo exit 2 fi on_exit() { [ -d "${CLEVIS_PKCS11}" ] || exit 0 rm -rf "${CLEVIS_PKCS11}" } unset CLEVIS_PKCS11 read -r -d . hdr64 # Check for corruption in the header. if ! hdr="$(jose fmt --quote="${hdr64}" --string --b64load --object \ --output=- 2>/dev/null)" ; then echo "JWE header corrupt" >&2 exit 1 fi # Check if the pin is the expected one. if ! pin="$(jose fmt --json="${hdr}" --get clevis --get pin \ --unquote=- 2>/dev/null)" || [ -z "${pin}" ]; then echo "Invalid JWE header: unable to identify 'pin'" >&2 exit 1 fi if [ "${pin}" != "${PIN_NAME}" ]; then echo "JWE pin mismatch: found: ${pin}; expected: ${PIN_NAME}" >&2 exit 1 fi if ! uri="$(jose fmt -j- -Og clevis -g "${PIN_NAME}" -g uri -Su- <<< "${hdr}")"; then echo "URI missing required 'clevis.pkcs11.uri' header parameter!" >&2 exit 1 fi mechanism_option="" mechanism="$(jose fmt -j- -Og clevis -g "${PIN_NAME}" -g mechanism -Su- <<< "${hdr}")" \ 2>/dev/null || : if [ -n "${mechanism}" ]; then mechanism_option="--mechanism ${mechanism}" fi if ! clevis_valid_pkcs11_uri "${uri}"; then echo "PKCS#11 URI with invalid format:[${uri}]" >&2 echo "PKCS#11 URI expected format:[${URI_EXPECTED_FORMAT}]" >&2 exit 1 fi module_opt="" if module_path="$(clevis_get_module_path_from_uri ${uri})"; then module_opt="--module ${module_path}" fi if ! slot=$(clevis_get_pkcs11_final_slot_from_uri "${uri}"); then slot_opt="" else slot_opt=" --slot-index=${slot}" fi # Check if key parameter is present. if ! enc_jwk="$(jose fmt --json="${hdr}" --get clevis --get "${PIN_NAME}" \ --get key --unquote=- 2>/dev/null)" \ || [ -z "${enc_jwk}" ]; then echo "JWE missing 'clevis.${PIN_NAME}.key' header parameter" >&2 exit 1 fi if ! CLEVIS_PKCS11="$(mktemp -d)" || [ -z "${CLEVIS_PKCS11}" ]; then echo "Creating a temporary dir for PKCS11 files failed" >&2 exit 1 fi trap 'on_exit' EXIT # Error file ERR="${CLEVIS_PKCS11}/decerr" # Decrypt the key. ENC="${CLEVIS_PKCS11}/enc" if ! printf '%s' "${enc_jwk}" | jose b64 dec -i- > "${ENC}" 2>"${ERR}"; then cat "${ERR}" >&2 echo "Unable to base64-decode the JWK" >&2 exit 1 fi PIN_value="" if ! PIN_value="$(clevis_get_pin_value_from_uri ${uri})"; then PIN_value=$(cat "${PIN_FILE}" 2>/dev/null || :) fi if ! jwk="$(pkcs11-tool --login --decrypt --input-file ${ENC} \ -p ${PIN_value} ${module_opt} ${mechanism_option} ${slot_opt} 2>${ERR})" \ || [ -z "${jwk}" ]; then cat "${ERR}" >&2 echo "Unable to decrypt the JWK" >&2 # TODO: Verify invalid PIN more accurately echo "Invalid PIN?" >&2 exit 1 fi rm -rf "${PIN_FILE}" 2>/dev/null || : # Decrypt the data using the decrypted JWK. ( printf '%s' "${jwk}${hdr64}." ; cat ) | exec jose jwe dec --key=- --input=- latchset-clevis-6df9b69/src/pins/pkcs11/clevis-encrypt-pkcs11000077500000000000000000000101621520110721700240720ustar00rootroot00000000000000#!/bin/bash set -eo pipefail # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . PIN_NAME=pkcs11 SUMMARY="Encrypts using a PKCS#11 token" if [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis encrypt pkcs11 CONFIG < PLAINTEXT > JWE" echo echo "$SUMMARY" echo echo "This command uses the following configuration properties:" echo echo " uri: The PKCS#11 URI (REQUIRED)" echo exit 2 fi . clevis-pkcs11-common on_exit() { [ -d "${CLEVIS_PKCS11}" ] || exit 0 rm -rf "${CLEVIS_PKCS11}" } unset CLEVIS_PKCS11 # We first check whether the configuration is a valid JSON. if ! cfg="$(jose fmt --json="$1" --object --output=-)" ; then echo 'Configuration is malformed' >&2 exit 1 fi if ! uri="$(jose fmt -j- -Og uri -u- <<< "$cfg")"; then uri="pkcs11:" fi mechanism="$(jose fmt -j- -Og mechanism -u- <<< "$cfg" 2>/dev/null || :)" if ! clevis_valid_pkcs11_uri "${uri}"; then echo "PKCS#11 URI with invalid format:[${uri}]" >&2 echo "PKCS#11 URI expected format:[${URI_EXPECTED_FORMAT}]" >&2 exit 1 fi if ! module_path=$(clevis_get_module_path_from_uri "${uri}"); then module_opt="" else module_opt=" --module ${module_path}" fi if ! slot=$(clevis_get_pkcs11_final_slot_from_uri "${uri}"); then slot_opt="" else slot_opt=" --slot-index=${slot}" fi if ! CLEVIS_PKCS11="$(mktemp -d)" || [ -z "${CLEVIS_PKCS11}" ]; then echo "Creating a temporary dir for PKCS11 files failed" >&2 exit 1 fi trap 'on_exit' EXIT # Error file ERR="${CLEVIS_PKCS11}/encerr" # Let's generate a key. if ! jwk="$(jose jwk gen --input='{"alg":"A256GCM"}')" \ || [ -z "${jwk}" ]; then echo "Unable to generate JWK" >&2 exit 1 fi # Now let's encrypt it with the device public key. PKEY="${CLEVIS_PKCS11}/pubkey" if ! id=$(pkcs11-tool ${slot_opt} -O 2>${ERR} ${module_opt} \ | grep -i 'Public' -A10 2>${ERR} | grep 'ID:' \ | head -1 | awk -F 'ID:' '{ val = $2 if (match(val, /\(0x[^)]+\)/)) { val = substr(val, RSTART+3, RLENGTH-4) print val } else { gsub(/^[[:space:]]+|[[:space:]]+$/, "", val) print val } }'); then cat "${ERR}" >&2 echo "Unable to obtain public key ID from PKCS#11 device" >&2 exit 1 fi if ! pkcs11-tool ${slot_opt} ${module_opt} --read-object --type pubkey --id "${id}" \ 2> "${ERR}" > "${PKEY}"; then cat "${ERR}" >&2 echo "Unable to obtain a public key from PKCS#11 device" >&2 exit 1 fi if ! jwk_enc="$(printf '%s' "${jwk}" | openssl rsautl -encrypt -pubin -keyform DER \ -inkey "${PKEY}" 2>${ERR} \ | jose b64 enc -I-)"; then cat "${ERR}" >&2 echo "Unable to encrypt JWK with PKCS#11 public key" >&2 exit 1 fi # And the JWE. template=$(printf '{"protected":{"clevis":{"pin":"%s","%s":{"uri":"%s", "mechanism":"%s"}}}}' \ "${PIN_NAME}" "${PIN_NAME}" "${uri}" "${mechanism}") # Save key. jwe="$(jose fmt --json="${template}" --get protected --get clevis \ --get "${PIN_NAME}" --quote "${jwk_enc}" --set key -UUUU --output=-)" # Now we encrypt the data using our key. ( printf '%s' "${jwe}${jwk}" ; cat ) | exec jose jwe enc \ --input=- --key=- \ --detached=- --compact latchset-clevis-6df9b69/src/pins/pkcs11/clevis-encrypt-pkcs11.1.adoc000066400000000000000000000053341520110721700251400ustar00rootroot00000000000000CLEVIS-ENCRYPT-PKCS11(1) ======================== :doctype: manpage == NAME clevis-encrypt-pkcs11 - Encrypts using a PKCS#11 device == SYNOPSIS *clevis encrypt pkcs11* CONFIG < PT > JWE == OVERVIEW The *clevis encrypt pkcs11* command encrypts using a PKCS#11 device. Its only argument is the JSON configuration object. When using this pin, we create a new random key which is encrypted using the PKCS#11 chip. Then at decryption time, the key is decrypted again using the PKCS#11 chip, normally, by providing a PIN (Personal Identity Number) at boot time. Configuration object must be provided with *uri* JSON key, and JSON value associated to *uri* key must start with *pkcs11:* word: $ clevis encrypt pkcs11 '{"uri":"pkcs11:"}' < PT > JWE As an alternative, PIN can be stored at configuration time. For security reasons, this is NOT recommended. But, if still required, it can be done through *pin-value* parameter: $ clevis encrypt pkcs11 '{"uri":"pkcs11:?pin-value=123456"}' < PT > JWE In case it is required to provide a module library, it can be done through URI *module-path" parameter: $ clevis encrypt pkcs11 '{"uri":"pkcs11:module-path=/usr/lib64/libykcs11.so"}' < PT > JWE Clevis will be used in top of OpenSC to provide PKCS#11 functionality. OpenSC, and, in particular, `pkcs11-tool`, provides an option to indicate the mechanism to use for decryption. For testing purposes, some libraries, such as https://www.opendnssec.org/softhsm[SoftHSM], don't work with default `pkcs11-tool` mechanism, so it is required to provide a particular mechanism for them to work. For this reason, Clevis can be provided with the mechanism to use, in case the default one, *RSA-PKCS-OAEP*, is not valid: $ clevis luks bind -d /dev/sda1 pkcs11 '{"uri": "pkcs11:", "mechanism":"RSA-PKCS"}' To decrypt the data, simply provide the ciphertext (JWE): $ clevis decrypt < JWE > PT Note that like other pins no configuration is used for decryption, this is due clevis storing the public and private keys to unseal the encrypted object in the JWE so clevis can fetch that information from there. == CONFIG This command uses the following configuration properties: * *uri* (string) : The PKCS#11 URI to use (REQUIRED) * *mechanism* (string) : Mechanism to be used when working with pkcs11-tool. It must be supported by pkcs11-tool. Examples of supported mechanisms are: - *RSA-PKCS-OAEP* (default one) - *RSA-PKCS* For a complete list of supported mechanisms, execute next command: $ pkcs11-tool -M It must be highlighted that previous command will show if the mechanism allows encryption/decryption, something that is required for PKCS#11 Clevis pin to work appropriately. == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/pins/pkcs11/clevis-pkcs11-afunix-socket-unlock.c000066400000000000000000000274211520110721700267030ustar00rootroot00000000000000/* * Copyright (c) 2024 Red Hat, Inc. * Author: Sergio Arroutbi * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef GIT_VERSION const char* VERSION = GIT_VERSION; #else const char* VERSION = "v0.0.1"; #endif #define MAX_DEVICE 1024 #define MAX_ENTRIES 1024 #define MAX_KEY 1024 const uint16_t DEFAULT_MAX_ITERATIONS = 3; const uint16_t MAX_PATH = 1024; const uint16_t MAX_CONTROL_MSG = 1024; const uint8_t WAIT_CONTROL_THREAD_TIMER = 1; // Time to wait before trying to write key const uint16_t DEFAULT_START_DELAY = 0; typedef struct { char dev[MAX_DEVICE+1]; char key[MAX_KEY+1]; } key_entry_t; key_entry_t keys[MAX_ENTRIES]; uint16_t entry_counter = 0; uint8_t thread_loop = 1; uint8_t control_thread_info = 0; pthread_mutex_t mutex; FILE* logfile = NULL; static void get_control_socket_name(const char* file_sock, char* control_sock, uint32_t control_sock_len) { char *p = strstr(file_sock, ".sock"); size_t prefix_length = strlen(file_sock) - strlen(p); memset(control_sock, 0, control_sock_len); memcpy(control_sock, file_sock, prefix_length); if (prefix_length + strlen(".control.sock") < control_sock_len) { strcat(control_sock + prefix_length, ".control.sock"); } } static void insert_device(const char* dev) { pthread_mutex_lock(&mutex); if(MAX_ENTRIES == entry_counter) { pthread_mutex_unlock(&mutex); perror("No more entries accepted\n"); return; } strncpy(keys[entry_counter].dev, dev, MAX_DEVICE); pthread_mutex_unlock(&mutex); } static void insert_key(const char* key) { pthread_mutex_lock(&mutex); if(MAX_ENTRIES == entry_counter) { pthread_mutex_unlock(&mutex); perror("No more entries accepted\n"); return; } strncpy(keys[entry_counter++].key, key, MAX_KEY); pthread_mutex_unlock(&mutex); } static const char* get_key(const char* dev) { for(int e = 0; e < entry_counter; e++) { pthread_mutex_lock(&mutex); if(0 == strcmp(keys[e].dev, dev)) { pthread_mutex_unlock(&mutex); return keys[e].key; } pthread_mutex_unlock(&mutex); } return NULL; } static void* control_thread(void *targ) { // Create a socket to listen on control socket struct sockaddr_un control_addr, accept_addr; int s = 0, a = 0, r = 0; char control_msg[MAX_CONTROL_MSG+1]; const char* control_sock = (const char*)targ; socklen_t len = 0; memset(&control_addr, 0, sizeof(control_addr)); control_addr.sun_family = AF_UNIX; strncpy(control_addr.sun_path, control_sock, sizeof(control_addr.sun_path)-1); unlink(control_sock); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) { perror("control socket"); fprintf(logfile, "Control socket error\n"); pthread_exit("control socket"); } if (bind(s, (struct sockaddr *)&control_addr, sizeof(control_addr)) == -1) { perror("control bind"); fprintf(logfile, "Control bind error\n"); pthread_exit("control bind"); } if (listen(s, SOMAXCONN) == -1) { perror("control listen"); fprintf(logfile, "Control listen error\n"); pthread_exit("control listen"); } while (thread_loop) { a = accept(s, (struct sockaddr *)&accept_addr, &len); if (a == -1) { perror("control accept"); fprintf(logfile, "Control accept\n"); pthread_exit("control accept"); } memset(control_msg, 0, MAX_CONTROL_MSG); if((r = recv(a, control_msg, MAX_CONTROL_MSG, 0)) < 0) { perror("recv error"); fprintf(logfile, "Error on reception\n"); close(a); pthread_exit("control recv"); } else { control_msg[r] = '\0'; } char* t = control_msg; int is_device = 1; while((t = strtok(t, ","))) { if (is_device) { fprintf(logfile, "Adding device:%s\n", t); insert_device(t); is_device = 0; } else { fprintf(logfile, "Adding key:%s\n", t); insert_key(t); // As long as some key is inserted, we store it // in the control_thread_info variable control_thread_info = 1; } t = strtok(NULL, ","); } close(a); } return NULL; } static int usage(const char* name, uint32_t ecode) { printf("\nUsage:\n\t%s -f socket_file [-c control_socket] [-k key] " "[-l logfile] [-t iterations, 3 by default]" "[-s start delay, 0s by default] [-v(version)] [-h(help)]\n\n", name); exit(ecode); } static void dump_version(void) { printf("VERSION: [%s]\n", VERSION); } static void dump_wide_version(void) { printf("\n"); dump_version(); printf("\n"); } static void int_handler(int s) { if(logfile) { fprintf(logfile, "Closing, received signal:[%d]\n", s); fclose(logfile); } exit(EXIT_FAILURE); } int main(int argc, char* argv[]) { int s, a, opt; struct sockaddr_un sock_addr, accept_addr, peer_addr; socklen_t pathlen; char key[MAX_KEY]; char lfile[MAX_PATH]; char sock_file[MAX_PATH]; char sock_control_file[MAX_PATH]; socklen_t len = sizeof(accept_addr); uint8_t wait_control_thread = 1; uint32_t iterations = DEFAULT_MAX_ITERATIONS; uint32_t startdelay = DEFAULT_START_DELAY; uint32_t ic = 0; uint32_t time = 0; memset(lfile, 0, MAX_PATH); memset(sock_file, 0, MAX_PATH); memset(sock_control_file, 0, MAX_PATH); memset(key, 0, MAX_KEY); signal(SIGTERM | SIGKILL, int_handler); for (uint16_t e = 0; e < MAX_ENTRIES; e++) { memset(&keys[e], 0, sizeof(key_entry_t)); } while ((opt = getopt(argc, argv, "c:f:k:i:l:s:t:hv")) != -1) { int ret_code = EXIT_FAILURE; switch (opt) { case 'c': strncpy(sock_control_file, optarg, MAX_PATH - 1); break; case 'f': strncpy(sock_file, optarg, MAX_PATH - 1); unlink(sock_file); break; case 'k': strncpy(key, optarg, MAX_KEY - 1); break; case 'l': strncpy(lfile, optarg, MAX_PATH - 1); logfile = fopen(lfile, "w+"); break; case 't': iterations = strtoul(optarg, 0, 10); break; case 's': startdelay = strtoul(optarg, 0, 10); break; case 'v': dump_wide_version(); exit(EXIT_SUCCESS); break; case 'h': ret_code = EXIT_SUCCESS; __attribute__ ((fallthrough)); default: usage(argv[0], ret_code); } } if(!logfile) { logfile = stdout; strncpy(lfile, "stdout", MAX_PATH - 1); } if(0 == strlen(sock_file)) { fprintf(logfile, "\nSocket file name must be provided\n"); usage(argv[0], EXIT_FAILURE); } if(0 == strlen(sock_control_file) ) { get_control_socket_name(sock_file, sock_control_file, MAX_PATH); } fprintf(logfile, "LOG FILE: [%s]\n", lfile); fprintf(logfile, "FILE: [%s]\n", sock_file); fprintf(logfile, "KEY: [%s]\n", key); fprintf(logfile, "START DELAY: [%u] seconds\n", startdelay); fprintf(logfile, "TRY ITERATIONS: [%u]\n", iterations); dump_version(); pthread_t thid; void* tret; if (pthread_create(&thid, NULL, control_thread, sock_control_file) != 0) { perror("pthread_create() error"); goto efailure; } memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sun_family = AF_UNIX; strncpy(sock_addr.sun_path, sock_file, sizeof(sock_addr.sun_path)-1); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) { perror("socket"); goto efailure; } if (bind(s, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) { perror("bind"); goto efailure; } if (listen(s, SOMAXCONN) == -1) { perror("listen"); goto efailure; } while (ic < iterations) { if (time++ < startdelay && !control_thread_info) { sleep(1); fprintf(logfile, "Start time elapsed: [%u/%u] seconds\n", time, startdelay); continue; } if (control_thread_info && wait_control_thread) { sleep(WAIT_CONTROL_THREAD_TIMER); fprintf(logfile, "Waiting %d second for control thread " "to receive complete information\n", WAIT_CONTROL_THREAD_TIMER); wait_control_thread = 0; } a = accept(s, (struct sockaddr *)&accept_addr, &len); if (a == -1) { perror("accept"); goto efailure; } pathlen = len - offsetof(struct sockaddr_un, sun_path); len = sizeof(peer_addr); if (getpeername(a, (struct sockaddr *)&peer_addr, &len)== -1) { perror("getpeername"); goto efailure; } pathlen = len - offsetof(struct sockaddr_un, sun_path); char peer[pathlen]; memset(peer, 0, pathlen); strncpy(peer, peer_addr.sun_path+1, pathlen-1); fprintf(logfile, "Try: [%u/%u]\n", ic, iterations); fprintf(logfile, "getpeername sun_path(peer): [%s]\n", peer); char* t = peer; const char* unlocking_device = ""; while((t = strtok(t, "/"))) { if(t) { unlocking_device = t; } t = strtok(NULL, ","); } fprintf(logfile, "Trying to unlock device:[%s]\n", unlocking_device); // Now we have all the information in peer, something like: // \099226072855ae2d8/cryptsetup/luks-6e38d5e1-7f83-43cc-819a-7416bcbf9f84 // NUL random /cryptsetup/ DEVICE // If we need to unencrypt device, pick it from peer information // To return the key, just respond to socket returned by accept if(strlen(key)) { if (send(a, key, strlen(key), 0) < 0) { perror("key send error"); goto efailure; } } else { const char* entry_key; if((entry_key = get_key(unlocking_device))) { if (send(a, entry_key, strlen(entry_key), 0)< 0) { perror("key entry send error"); goto efailure; } fprintf(logfile, "Sending passphrase to device:[%s]\n", unlocking_device); } else { fprintf(logfile, "Device not found: [%s]\n", unlocking_device); } } close(a); ic++; } fprintf(logfile, "Closing (max tries reached)\n"); pthread_kill(thid, SIGKILL); thread_loop = 0; if (pthread_join(thid, &tret) != 0) { perror("pthread_join error"); goto efailure; } return EXIT_SUCCESS; efailure: if(logfile) { fclose(logfile); logfile = NULL; } exit(EXIT_FAILURE); } latchset-clevis-6df9b69/src/pins/pkcs11/clevis-pkcs11-common000077500000000000000000000243241520110721700237030ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # if [ "$1" = "--summary" ]; then exit 1 fi . clevis-luks-common-functions serial_devices_array="" URI_EXPECTED_FORMAT="pkcs11:" DEFAULT_CRYPTTAB_FILE="/etc/crypttab" clevis_detect_pkcs11_device() { dracut_mode="${1:false}" retry_mode="${2:false}" if ! pkcs11_device=$(pkcs11-tool -L 2>/dev/null | grep "Slot" | head -1 | \ awk -F ":" '{print $2}' | sed -e 's@^ *@@g'); then echo "" return 1 fi if ! pkcs11-tool -O 2>/dev/null 1>/dev/null; then pkcs11_device="" echo "No objects in PKCS11 device detected" >&2 fi while [ -z "${pkcs11_device}" ]; do if [ "${dracut_mode}" != true ]; then module_paths=$(clevis_get_module_path_from_pkcs11_config "/etc/crypttab") if [ -n "${module_paths}" ]; then modules=$(echo ${module_paths} | tr ";" "\n") for module in $modules; do pkcs11_device=$(pkcs11-tool -L --module ${module} | grep "Slot" \ | head -1 | awk -F ":" '{print $2}' | sed -e 's@^ *@@g') if [ -n "${pkcs11_device}" ]; then break; fi done fi fi if [ -z "${pkcs11_device}" ]; then if [ "${retry_mode}" == true ]; then option=$(systemd-ask-password --echo "Detected no PKCS#11 device, retry PKCS#11 detection? [yY/nN]") if [ "${option}" == "N" ] || [ "${option}" == "n" ] ; then echo "" # Straight Forward Mode return 0 fi pkcs11_device=$(pkcs11-tool -L | grep "Slot" \ | head -1 | awk -F ":" '{print $2}' | sed -e 's@^ *@@g') if ! pkcs11-tool -O 2>/dev/null 1>/dev/null; then pkcs11_device="" echo "No objects in PKCS11 device detected" >&2 fi else echo "${pkcs11_device}" return 0 fi fi done } clevis_start_pcscd_server() { if command -v pcscd; then echo "clevis-pkcs11: starting pcscd if not available ..." PCSCD_PID=$(ps auxf | grep "[p]cscd") echo -e "clevis-pkcs11: pcscd running?:[${PCSCD_PID}]\n" if ! ps auxf | grep "[p]cscd"; then if pcscd --help | grep disable-polkit 1>/dev/null 2>/dev/null; then echo "clevis-pkcs11: starting pcscd with --disable-polkit option ..." pcscd --disable-polkit else echo "clevis-pkcs11: starting pcscd ..." pcscd fi fi fi } clevis_parse_devices_array() { INPUT_ARRAY=$(pkcs11-tool -L | grep Slot) counter=0 while read -r; do serial=$(pkcs11-tool -L | sed -n "/Slot ${counter}/,/Slot $((counter+1))/p" | grep -i "serial num"| awk -F ":" '{print $2}' | tr -d ' ') serial_devices_array[$counter]="${serial}" ((counter++)) done <<< "${INPUT_ARRAY}" } clevis_get_serial_by_slot() { clevis_parse_devices_array serial=${serial_devices_array[$1]} if [ -z "${serial}" ]; then return 1 fi echo "${serial}" return 0 } clevis_valid_pkcs11_uri() { echo "$1" | grep -E "^${URI_EXPECTED_FORMAT}" >/dev/null 2>&1 || return 1 } clevis_get_module_path_from_uri() { echo "$1" | grep -E "module-path=" >/dev/null 2>&1 || return 1 echo "$1" | awk -F 'module-path=' '{print $2}' | awk -F ";" '{print $1}' \ | awk -F "?" '{print $1}' } clevis_get_module_path_from_pkcs11_config() { CRYPTTABFILE="$1" module_list="" [ -z "${CRYPTTABFILE}" ] && CRYPTTABFILE="${DEFAULT_CRYPTTAB_FILE}" while read -r line; do uuid=$(echo "${line}" | awk '{print $2}') if ! mapped_device=$(clevis_map_device "${uuid}"); then continue fi # If no PKCS#11 configuration, advance to next device if ! clevis luks list -d "${mapped_device}" | grep pkcs11 >/dev/null 2>&1; then continue fi # Get configuration PKCS#11 URI uri=$(clevis luks list -d "${mapped_device}" | awk -F '"uri":' '{print $2}' \ | awk -F '"' '{print $2}' | awk -F '"' '{print $1}') if module_path=$(clevis_get_module_path_from_uri "${uri}"); then if [ -z "${module_list}" ]; then module_list="${module_path}" else module_list="${module_list};${module_path}" fi fi done < <(grep -v "^#" "${CRYPTTABFILE}") echo "${module_list}" } clevis_get_pin_value_from_uri() { echo "$1" | grep -E "pin-value=" >/dev/null 2>&1 || return 1 echo "$1" | awk -F 'pin-value=' '{print $2}' | awk -F ";" '{print $1}' } clevis_get_slot_from_uri() { echo "$1" | grep -E "slot-id=" >/dev/null 2>&1 || return 1 echo "$1" | awk -F 'slot-id=' '{print $2}' | awk -F ";" '{print $1}' \ | awk -F "?" '{print $1}' } clevis_percent_unencoding() { echo "$1" | sed -E 's@%20@ @g' | sed -E 's@%21@!@g' | sed -E 's@%22@"@g' \ | sed -E 's@%23@#@g' | sed -E 's@%24@$@g' | sed -E 's@%25@%@g' \ | sed -E 's@%26@&@g' | sed -E "s@%27@'@g" | sed -E 's@%28@(@g' \ | sed -E 's@%29@)@g' | sed -E "s@%2A@*@g" | sed -E 's@%2B@+@g' \ | sed -E 's@%2C@,@g' | sed -E "s@%2F@/@g" | sed -E 's@%3A@:@g' \ | sed -E 's@%3B@;@g' | sed -E "s@%3D@=@g" | sed -E 's@%3F@?@g' \ | sed -E 's!%40!@!g' | sed -E "s@%5B@[@g" | sed -E 's@%5D@]@g' } clevis_get_model_from_uri() { if ! echo "$1" | grep -E "model=" >/dev/null 2>&1; then return 1 fi model=$(echo "$1" | awk -F 'model=' '{print $2}' | awk -F ";" '{print $1}' \ | awk -F "?" '{print $1}') clevis_percent_unencoding "${model}" } clevis_get_token_from_uri() { if ! echo "$1" | grep -E "token=" >/dev/null 2>&1; then return 1 fi token=$(echo "$1" | awk -F 'token=' '{print $2}' | awk -F ";" '{print $1}' \ | awk -F "?" '{print $1}') clevis_percent_unencoding "${token}" } clevis_get_serial_from_uri() { if ! echo "$1" | grep -E "serial=" >/dev/null 2>&1; then return 1 fi serial=$(echo "$1" | awk -F 'serial=' '{print $2}' | awk -F ";" '{print $1}' \ | awk -F "?" '{print $1}') clevis_percent_unencoding "${serial}" } clevis_get_max_pkcs11_slot() { if [ -z "${1}" ]; then module_opt="" else module_opt="--module ${1}" fi if ! pkcs11-tool -L ${module_opt} | grep -i "^slot" | awk '{print $2}' | tail -1; then echo "-1" fi } clevis_pkcs11_filter_uri() { echo "$1" | sed -E 's@module-path=[a-z,A-Z,0-9,/,.]{1,};{0,1}@@' | sed -E 's@;$@@g' } clevis_get_slot_by_serial_from_uri() { module_path="" if ! module_path=$(clevis_get_module_path_from_uri "${1}"); then module_opt="" else module_opt=" --module ${module_path}" fi serialuri=$(clevis_get_serial_from_uri "${1}") for ((s=0; s<=$(clevis_get_max_pkcs11_slot "${module_path}"); s++)) do if ! pkcs11-tool -O --slot-index=${s} 1>/dev/null 2>/dev/null; then continue fi serial=$(clevis_get_serial_by_slot "${s}") if [ "${serial}" = "${serialuri}" ]; then echo "${s}" return 0 fi done return 1 } clevis_get_slot_by_serial_and_token_from_uri() { module_path="" if ! module_path=$(clevis_get_module_path_from_uri "${1}"); then module_opt="" else module_opt=" --module ${module_path}" fi serialuri=$(clevis_get_serial_from_uri "${1}") tokenuri="$(clevis_get_token_from_uri "${1}")" for ((s=0; s<=$(clevis_get_max_pkcs11_slot "${module_path}"); s++)) do if pkcs11-tool -L ${module_opt} 2>/dev/null | \ grep -i 'serial num' | head -$((s+1)) | tail -1 | \ awk -F ':' '{print $2}' | tr -d ' ' | \ grep "${serialuri}" 2>/dev/null 1>/dev/null; then pkcs11tokenuri="$(pkcs11-tool -L ${module_opt} 2>/dev/null \ | grep -i 'token label' | head -$((s+1)) | tail -1 \ | awk -F ':' '{print $2}' | sed -E 's@^ {0,}@@g')" if [ "${tokenuri}" = "${pkcs11tokenuri}" ]; then echo "${s}" return 0 fi fi done return 1 } clevis_get_pkcs11_pubkey_slot_from_uri() { module_path="" if ! module_path=$(clevis_get_module_path_from_uri "${1}"); then module_opt="" else module_opt=" --module ${module_path}" fi uri=$(clevis_pkcs11_filter_uri "${1}") for ((s=0; s<=$(clevis_get_max_pkcs11_slot "${module_path}"); s++)) do if pkcs11-tool -O ${module_opt} --slot-index ${s} --type pubkey 2>/dev/null \ | grep -i 'uri:' | awk -F 'uri:' '{print $2}' | tr -d ' ' \ | grep "${uri}" 2>/dev/null 1>/dev/null; then echo "${s}" return 0 fi done return 1 } clevis_get_pkcs11_final_slot_from_uri() { if slot=$(clevis_get_slot_from_uri "${1}"); then echo "${slot}" return 0 fi if [ -z "${slot}" ]; then if slot=$(clevis_get_pkcs11_pubkey_slot_from_uri "${1}"); then echo "${slot}" return 0 fi fi if [ -z "${slot}" ]; then if slot=$(clevis_get_slot_by_serial_and_token_from_uri "${1}"); then echo "${slot}" return 0 fi fi if [ -z "${slot}" ]; then if slot=$(clevis_get_slot_by_serial_from_uri "${1}"); then echo "${slot}" return 0 fi fi return 1 } latchset-clevis-6df9b69/src/pins/pkcs11/meson.build000066400000000000000000000033521520110721700222420ustar00rootroot00000000000000pcscd = find_program('pcscd', required: false) pkcs11tool = find_program('pkcs11-tool', required: false) pcscd_disable_polkit = false git = find_program('git', required: false) if git.found() git_version_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false) git_version = 'release-v' + meson.project_version() + '-' + git_version_cmd.stdout().strip() else git_version = 'release-v' + meson.project_version() + '-' + 'UNKNOWN_GIT_VERSION' endif GIT_VERSION_FLAG = '-DGIT_VERSION="' + git_version + '"' if pcscd.found() pcscd_options = run_command('pcscd', '--help', check: false) pcscd_disable_polkit = pcscd_options.stdout().strip().contains('disable-polkit') if not pcscd_disable_polkit warning('pcscd does not have --disable-polkit option') endif endif if pcscd.found() and pkcs11tool.found() bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-pkcs11') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-pkcs11') bins += join_paths(meson.current_source_dir(), 'clevis-pkcs11-common') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-pkcs11.1') ### TODO: Include man pages # mans += join_paths(meson.current_source_dir(), 'clevis-decrypt-pkcs11.1') if not meson.is_cross_build() subdir('tests') endif threads_dep = dependency('threads', required: true) executable('clevis-pkcs11-afunix-socket-unlock', ['clevis-pkcs11-afunix-socket-unlock.c'], dependencies: threads_dep, install_dir: bindir, install: true, c_args: GIT_VERSION_FLAG ) else warning('Will not install pkcs11 pin due to missing dependencies!') if not pcscd.found() warning('pcscd not found') endif if not pkcs11tool.found() warning('pkcs11-tool not found') endif endif latchset-clevis-6df9b69/src/pins/pkcs11/tests/000077500000000000000000000000001520110721700212375ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/pkcs11/tests/meson.build000066400000000000000000000021431520110721700234010ustar00rootroot00000000000000env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'pins', 'pkcs11'), join_paths(meson.source_root(), 'src', 'pins', 'pkcs11', 'tests'), join_paths(meson.build_root(), 'src'), join_paths(meson.build_root(), 'src', 'pins'), join_paths(meson.build_root(), 'src', 'pins', 'pkcs11'), join_paths(meson.build_root(), 'src', 'pins', 'pkcs11', 'tests'), join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'luks'), join_paths(meson.source_root(), 'src', 'luks', 'tests'), join_paths(meson.build_root(), 'src'), join_paths(meson.build_root(), 'src', 'luks'), join_paths(meson.build_root(), 'src', 'luks', 'tests'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), join_paths(meson.source_root(), 'src', 'pins', 'tang', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'tang'), join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), separator: ':' ) test('pin-pkcs11', find_program('pin-pkcs11'), env: env) test('pkcs11-id-parsing', find_program('pkcs11-id-parsing'), env: env) latchset-clevis-6df9b69/src/pins/pkcs11/tests/pin-pkcs11000077500000000000000000000124771520110721700230660ustar00rootroot00000000000000#!/bin/bash -xe # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # shellcheck disable=SC1091 . pkcs11-common-tests . luks-common-test-functions . clevis-luks-common-functions . clevis-pkcs11-common on_exit() { exit_status=$? [ -d "$TMP" ] && rm -rf "$TMP" exit "${exit_status}" } if [[ ! -f "${P11LIB}" ]]; then skip_test "WARNING: The SoftHSM is not installed. Can not run this test" fi trap 'on_exit' EXIT TMP="$(mktemp -d)" softhsm_lib_setup test "$?" == 0 SECRET_WORD="secret" SUPPORTED_MECHANISM="RSA-PKCS" CLEVIS_PIN="pkcs11" DEFAULT_SLOT="0" sword=$(echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=${PIN}\"\ ,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" | clevis decrypt) test "${sword}" == "${SECRET_WORD}" sword=$(echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=${P11LIB};slot=${DEFAULT_SLOT}\ ?pin-value=${PIN}\",\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" \ | clevis decrypt) test "${sword}" == "${SECRET_WORD}" ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=${PIN}\" \ ,\"mechanism\":\"INVALID_MECHANISM\"}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=${PIN}\"\ ,\"mechanism\":\"INVALID_MECHANISM\"}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=${P11LIB};slot=1?pin-value=${PIN}\" \ ,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null sword=$(echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=INVALID_PIN\"\ ,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null | clevis decrypt 2>/dev/null || :) test "${sword}" != "${SECRET_WORD}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:module-path=/usr/lib/wrong_modulepath.so?pin-value=${PIN}\"\ ,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \ "{\"uri\":\"pkcs11:?pin-value=${PIN}\"\ ,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":\"pkcs12:\"}" \ 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":\":\"}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":\"\"}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":}" 2>/dev/null ! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{}" 2>/dev/null # Let's try some bindings # LUKS2. DEV="${TMP}/luks2-device" new_device "luks2" "${DEV}" CFG=$(printf '{"uri": "pkcs11:module-path=%s?pin-value=%s", "mechanism":"%s"}' \ "${P11LIB}" "${PIN}" "${SUPPORTED_MECHANISM}") if ! clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${CFG}" <<< \ "${DEFAULT_PASS}"; then error "${TEST}: Binding is expected to succeed when given a correct \ (${DEFAULT_PASS}) password." fi SLT=1 if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) \ and slot (${SLT})" fi if [[ "${slot}" != "${SLT}:" ]]; then error "${TEST}: slot (${slot}) is expected to be ${SLT}" fi if [[ "${pin}" != "${CLEVIS_PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${CLEVIS_PIN}'" fi # Check configuration has "uri:" if ! [[ "${cfg}" == *"uri"* ]]; then error "${TEST}: configuration (${cfg}) is expected to be contain uri" fi # Test the passphrase SLT=1 PASS=$(clevis luks pass -d "${DEV}" -s "${SLT}") if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" "${PASS}" "" "${SLT}"; then error "Passphrase obtained from clevis luks pass failed." fi if ! clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to succeed for device ${DEV} and slot ${SLT}" fi SLT=0 if clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then error "${TEST}: Unbind is expected to fail for device ${DEV}:${SLT} \ that is not bound with clevis" fi WRONGCFG=$(printf '{"uri": "pkcs12:"}') if clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${WRONGCFG}" <<< "${DEFAULT_PASS}"; \ then error "${TEST}: Binding is expected to fail when given an incorrect configuration:\ (${WRONGCFG})" fi WRONGCFG=$(printf '{"uri":""}') if clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${WRONGCFG}" <<< "${DEFAULT_PASS}"; \ then error "${TEST}: Binding is expected to fail when given an empty uri:\ (${WRONGCFG})" fi if command -v ps && command -v killall; then if ! clevis_start_pcscd_server; then error "${TEST}: Could not start pcscd server" fi if ! killall -9 pcscd; then error "${TEST}: Could not kill pcscd server" fi fi softhsm_lib_cleanup test "$?" == 0 latchset-clevis-6df9b69/src/pins/pkcs11/tests/pkcs11-common-tests000066400000000000000000000057031520110721700247170ustar00rootroot00000000000000#!/bin/bash # # Copyright (c) 2024 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see # # This file is based on OpenSC common test infrastructure: # https://github.com/OpenSC/OpenSC/blob/master/tests/common.sh # SOPIN="12345678" PIN="123456" P11LIB="" SOFTHSM_PATHS="/usr/local/lib/softhsm/libsofthsm2.so \ /usr/lib/softhsm/libsofthsm2.so \ /usr/lib64/pkcs11/libsofthsm2.so" for hsmlib in ${SOFTHSM_PATHS}; do if [[ -f "${hsmlib}" ]]; then P11LIB="${hsmlib}" break fi done # Check required libraries if [[ -z "${P11LIB}" ]]; then echo "Warning: Could not find the softhsm pkcs11 module library" echo "Warning: Searched files:${SOFTHSM_PATHS}" fi # Check required commands if ! type pkcs11-tool; then echo "Warning: Could not find pkcs11-tool" fi if ! type openssl; then echo "Warning: Could not find openssl command" fi function generate_public_key() { TYPE="$1" ID="$2" LABEL="$3" # Generate key pair if ! pkcs11-tool --keypairgen --key-type="${TYPE}" --login --pin="${PIN}" \ --module="${P11LIB}" --label="${LABEL}" --id="${ID}"; then echo "Couldn't generate ${TYPE} key pair" return 1 fi # Extract public key if ! pkcs11-tool --read-object --id "${ID}" --type pubkey --output-file \ "${ID}".der --module="${P11LIB}"; then echo "Couldn't read generated ${TYPE} public key" return 1 fi if [[ ${TYPE:0:3} == "RSA" ]]; then openssl rsa -inform DER -outform PEM -in "${ID}".der -pubin > "${ID}".pub else echo "Unsupported key type:${TYPE}" return 1 fi rm "${ID}".der } function softhsm_initialize() { echo "directories.tokendir = $(realpath .tokens)" > .softhsm2.conf if [ -d ".tokens" ]; then rm -rf ".tokens" fi mkdir ".tokens" SOFTHSM2_CONF=$(realpath ".softhsm2.conf") export SOFTHSM2_CONF # Init token softhsm2-util --init-token --slot 0 --label "Clevis PKCS11 test" \ --so-pin="${SOPIN}" --pin="${PIN}" } function softhsm_lib_setup() { softhsm_initialize # Generate 2048b RSA Key pair (this will generate 00.pub file) generate_public_key "RSA:2048" "00" "RSA2048" || return 1 } function softhsm_cleanup() { rm -rf .softhsm2.conf rm -rf ".tokens" } function softhsm_lib_cleanup() { softhsm_cleanup rm 00.pub } latchset-clevis-6df9b69/src/pins/pkcs11/tests/pkcs11-id-parsing000077500000000000000000000077111520110721700243300ustar00rootroot00000000000000#!/bin/bash -xe # # Copyright (c) 2026 Red Hat, Inc. # Author: Sergio Arroutbi # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Unit test for pkcs11 public key ID parsing from pkcs11-tool -O output. # Verifies the ID extraction pipeline handles both old and new output # formats of pkcs11-tool. The new format (OpenSC >= 0.27.1) shows IDs as # "DECIMAL (0xHEX)" for small CKA_IDs; the parser must extract the hex # value from inside the parentheses since pkcs11-tool --read-object --id # expects hex input. # . tests-common-functions parse_pkcs11_id() { echo "$1" | grep -i 'Public' -A10 | grep 'ID:' \ | head -1 | awk -F 'ID:' '{ val = $2 if (match(val, /\(0x[^)]+\)/)) { val = substr(val, RSTART+3, RLENGTH-4) print val } else { gsub(/^[[:space:]]+|[[:space:]]+$/, "", val) print val } }' } # New pkcs11-tool format: decimal with hex in parentheses # Must extract hex "03", not decimal "3" PKCS11_OUTPUT_NEW_FORMAT="Public Key Object; RSA 2048 bits label: KEY MAN pubkey ID: 3 (0x03) Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_NEW_FORMAT}") test "${id}" == "03" || error "New format: expected '03', got '${id}'" # Old pkcs11-tool format: plain hex without annotation PKCS11_OUTPUT_OLD_FORMAT="Public Key Object; RSA 2048 bits label: KEY MAN pubkey ID: 03 Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_OLD_FORMAT}") test "${id}" == "03" || error "Old format: expected '03', got '${id}'" # Decimal differs from hex: decimal 10 is hex 0a # Must extract "0a", not "10" PKCS11_OUTPUT_DEC_HEX_DIFFER="Public Key Object; RSA 2048 bits label: KEY MAN pubkey ID: 10 (0x0a) Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_DEC_HEX_DIFFER}") test "${id}" == "0a" || error "Decimal/hex differ: expected '0a', got '${id}'" # Two-byte hex ID PKCS11_OUTPUT_TWO_BYTE="Public Key Object; RSA 4096 bits label: KEY MAN pubkey ID: 65281 (0xff01) Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_TWO_BYTE}") test "${id}" == "ff01" || error "Two-byte hex: expected 'ff01', got '${id}'" # ID where decimal == hex (single digit, 0-9) PKCS11_OUTPUT_SINGLE_DIGIT="Public Key Object; RSA 2048 bits label: KEY MAN pubkey ID: 1 (0x01) Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_SINGLE_DIGIT}") test "${id}" == "01" || error "Single digit: expected '01', got '${id}'" # Multiple objects: should pick the first Public Key ID hex value PKCS11_OUTPUT_MULTIPLE="Certificate Object; type = X.509 cert label: Certificate for Key Management ID: 99 Public Key Object; RSA 2048 bits label: KEY MAN pubkey ID: 42 (0x2a) Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_MULTIPLE}") test "${id}" == "2a" || error "Multiple objects: expected '2a', got '${id}'" # Old format with multi-byte hex (no parentheses) PKCS11_OUTPUT_OLD_MULTI="Public Key Object; RSA 2048 bits label: KEY MAN pubkey ID: ff01 Usage: encrypt, wrap Access: none" id=$(parse_pkcs11_id "${PKCS11_OUTPUT_OLD_MULTI}") test "${id}" == "ff01" || error "Old multi-byte: expected 'ff01', got '${id}'" latchset-clevis-6df9b69/src/pins/sss/000077500000000000000000000000001520110721700176035ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/sss/clevis-decrypt-null000077500000000000000000000021741520110721700234420ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 read -r -d . hdr if [ "$(jose fmt -q "$hdr" -SyOg clevis -g pin -u-)" != "null" ]; then echo "JWE pin mismatch!" >&2 exit 1 fi jwk="$(jose fmt -q "$hdr" -SyOg clevis -g null -g jwk -Oo-)" || exit 1 exec jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat) latchset-clevis-6df9b69/src/pins/sss/clevis-decrypt-sss.c000066400000000000000000000207661520110721700235250ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GPLv3 section 7: * * In the following paragraph, "GPL" means the GNU General Public * License, version 3 or any later version, and "Non-GPL Code" means * code that is governed neither by the GPL nor a license * compatible with the GPL. * * You may link the code of this Program with Non-GPL Code and convey * linked combinations including the two, provided that such Non-GPL * Code only links to the code of this Program through those well * defined interfaces identified in the file named EXCEPTION found in * the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline * functions from the Approved Interfaces without causing the resulting * work to be covered by the GPL. Only the copyright holders of this * Program may make changes or additions to the list of Approved * Interfaces. */ #define _GNU_SOURCE #include "sss.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct pin { struct pin *prev; struct pin *next; uint8_t *pt; size_t ptl; FILE *file; pid_t pid; }; static size_t nchldrn(const struct pin *pins, bool response) { size_t n = 0; for (const struct pin *p = pins->next; p != pins; p = p->next) { if (response && p->pt) n++; else if (!response) n++; } return n; } static json_t * compact_field(FILE *file) { json_t *str = NULL; char *buf = NULL; size_t used = 0; size_t size = 0; for (int c = fgetc(file); c != EOF && c != '.' && !isspace(c); c = fgetc(file)) { if (used >= size) { char *tmp = NULL; size += 4096; tmp = realloc(buf, size); if (!tmp) goto error; buf = tmp; } buf[used++] = c; } str = json_stringn(buf ? buf : "", buf ? used : 0); error: free(buf); return str; } static json_t * compact_jwe(FILE *file) { json_auto_t *jwe = NULL; jwe = json_object(); if (!jwe) return NULL; if (json_object_set_new(jwe, "protected", compact_field(file)) < 0) return NULL; if (json_object_set_new(jwe, "encrypted_key", compact_field(file)) < 0) return NULL; if (json_object_set_new(jwe, "iv", compact_field(file)) < 0) return NULL; return json_incref(jwe); } int main(int argc, char *argv[]) { struct pin chldrn = { &chldrn, &chldrn }; json_auto_t *pins = NULL; json_auto_t *hdr = NULL; json_auto_t *jwe = NULL; int ret = EXIT_FAILURE; json_t *p = NULL; json_int_t t = 1; int epoll = -1; size_t pl = 0; if (argc == 2 && strcmp(argv[1], "--summary") == 0) return EXIT_FAILURE; if (isatty(STDIN_FILENO) || argc != 1) goto usage; epoll = epoll_create1(EPOLL_CLOEXEC); if (epoll < 0) return ret; jwe = compact_jwe(stdin); if (!jwe) goto egress; hdr = jose_jwe_hdr(jwe, jwe); if (!hdr) goto egress; if (json_unpack(hdr, "{s:{s:{s:I,s:o,s:O}}}", "clevis", "sss", "t", &t, "p", &p, "jwe", &pins) != 0) goto egress; if (t < 1) goto egress; pl = jose_b64_dec(p, NULL, 0); if (pl == SIZE_MAX) goto egress; for (size_t i = 0; i < json_array_size(pins); i++) { char *args[] = { "clevis", "decrypt", NULL }; const json_t *val = json_array_get(pins, i); struct pin *pin = NULL; if (!json_is_string(val)) goto egress; pin = calloc(1, sizeof(*pin)); if (!pin) goto egress; chldrn.next->prev = pin; pin->next = chldrn.next; pin->prev = &chldrn; chldrn.next = pin; pin->file = call(args, json_string_value(val), json_string_length(val), &pin->pid); if (!pin->file) goto egress; if (epoll_ctl(epoll, EPOLL_CTL_ADD, fileno(pin->file), &(struct epoll_event) { .events = EPOLLIN | EPOLLPRI, .data.fd = fileno(pin->file) }) < 0) goto egress; } json_decref(pins); pins = json_array(); if (!pins) goto egress; for (struct epoll_event e; true; ) { int r = 0; r = epoll_wait(epoll, &e, 1, -1); if (r != 1) break; for (struct pin *pin = chldrn.next; pin != &chldrn; pin = pin->next) { if (!pin->file || e.data.fd != fileno(pin->file)) continue; if (e.events & (EPOLLIN | EPOLLPRI)) { const size_t ptl = pl * 2; pin->pt = malloc(ptl); if (!pin->pt) goto egress; while (!feof(pin->file)) { uint8_t buf[ptl]; size_t rd = 0; rd = fread(buf, 1, sizeof(buf), pin->file); if (ferror(pin->file) || pin->ptl + rd > ptl) { pin->ptl = 0; break; } memcpy(&pin->pt[pin->ptl], buf, rd); pin->ptl += rd; } if (pin->ptl != ptl) { free(pin->pt); pin->pt = NULL; goto egress; } } fclose(pin->file); pin->file = NULL; waitpid(pin->pid, NULL, 0); pin->pid = 0; if (!pin->pt) { pin->next->prev = pin->prev; pin->prev->next = pin->next; free(pin); } break; } if (nchldrn(&chldrn, false) < (size_t) t || nchldrn(&chldrn, true) >= (size_t) t) break; } if (nchldrn(&chldrn, true) >= (size_t) t) { jose_io_auto_t *out = NULL; jose_io_auto_t *dec = NULL; jose_io_auto_t *b64 = NULL; json_auto_t *cek = NULL; const uint8_t *xy[t]; size_t i = 0; memset(xy, 0, t * sizeof(uint8_t)); for (struct pin *pin = chldrn.next; pin != &chldrn; pin = pin->next) { if (pin->pt && i < (size_t) t) xy[i++] = pin->pt; } cek = json_pack("{s:s,s:o}", "kty", "oct", "k", sss_recover(p, t, xy)); if (!cek) goto egress; out = jose_io_file(NULL, stdout); dec = jose_jwe_dec_cek_io(NULL, jwe, cek, out); b64 = jose_b64_dec_io(dec); if (!out || !dec || !b64) goto egress; for (int b = fgetc(stdin); b != EOF && b != '.'; b = fgetc(stdin)) { char c = b; if (!b64->feed(b64, &c, 1)) goto egress; } if (json_object_set_new(jwe, "tag", compact_field(stdin)) < 0) goto egress; if (!b64->done(b64)) goto egress; ret = EXIT_SUCCESS; } egress: while (chldrn.next != &chldrn) { struct pin *pin = chldrn.next; if (pin->file) fclose(pin->file); if (pin->pid > 0) { kill(pin->pid, SIGTERM); waitpid(pin->pid, NULL, 0); } pin->next->prev = pin->prev; pin->prev->next = pin->next; free(pin->pt); free(pin); } close(epoll); return ret; usage: fprintf(stderr, "\n"); fprintf(stderr, "Usage: clevis decrypt sss < JWE > PLAINTEXT\n"); fprintf(stderr, "\n"); return EXIT_FAILURE; } latchset-clevis-6df9b69/src/pins/sss/clevis-encrypt-null000077500000000000000000000024161520110721700234530ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then echo "Configuration is malformed!" >&2 exit 1 fi jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')" jwe='{"protected":{"clevis":{"pin":"null","null":{}}}}' if ! jose fmt -j "$cfg" -g fail -T; then jwe="$(jose fmt -j "$jwe" -Og protected -g clevis -g null -j "$jwk" -Os jwk -UUUUo-)" fi exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) latchset-clevis-6df9b69/src/pins/sss/clevis-encrypt-sss.1.adoc000066400000000000000000000040651520110721700243540ustar00rootroot00000000000000CLEVIS-ENCRYPT-SSS(1) ===================== :doctype: manpage == NAME clevis-encrypt-sss - Encrypts using a Shamir's Secret Sharing policy == SYNOPSIS *clevis encrypt sss* CONFIG [-y] < PT > JWE == OVERVIEW The *clevis encrypt sss* command encrypts using a Shamir's Secret Sharing policy. Its only argument is the JSON configuration object. Shamir's Secret Sharing (SSS) provides a way to mix pins together to create sophisticated unlocking and high availability policies. SSS is a thresholding scheme. It creates a key and divides it into a number of pieces. Each piece is encrypted using another pin (possibly even SSS recursively). Additionally, you define the threshold *t*. If at least *t* pieces can be decrypted, then the encryption key can be recovered and decryption can succeed. For example, let's create a high-availability setup using Tang: $ cfg='{"t":1,"pins":{"tang":[{"url":...},{"url":...}]}}' $ clevis encrypt sss "$cfg" < PT > JWE In this policy, we are declaring that we have a threshold of 1, but that there are multiple key fragments encrypted using different Tang servers. Since our threshold is 1, so long as any of the Tang servers are available, decryption will succeed. As always, decryption is simply: $ clevis decrypt < JWE > PT == CONFIG This command uses the following configuration properties: * *t* (integer) : Number of pins required for decryption (REQUIRED) * *pins* (object) : Pins used for encrypting fragments (REQUIRED) The format of the *pins* property is as follows: {PIN:CFG,...} OR {PIN:[CFG,CFG,...],...} When the list version of the format is used, multiple pins of that type will receive key fragments. == OPTIONS * *-y* : Automatically answer yes for all questions. For the _tang_ pin, it will skip the advertisement trust check, which can be useful in automated deployments: $ cfg='{"t":1,"pins":{"tang":[{"url":...},{"url":...}]}}' $ clevis encrypt sss "$cfg" -y < PT > JWE == SEE ALSO link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)], link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/pins/sss/clevis-encrypt-sss.c000066400000000000000000000217601520110721700235320ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GPLv3 section 7: * * In the following paragraph, "GPL" means the GNU General Public * License, version 3 or any later version, and "Non-GPL Code" means * code that is governed neither by the GPL nor a license * compatible with the GPL. * * You may link the code of this Program with Non-GPL Code and convey * linked combinations including the two, provided that such Non-GPL * Code only links to the code of this Program through those well * defined interfaces identified in the file named EXCEPTION found in * the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline * functions from the Approved Interfaces without causing the resulting * work to be covered by the GPL. Only the copyright holders of this * Program may make changes or additions to the list of Approved * Interfaces. */ #define _GNU_SOURCE #include "sss.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define str_auto_t char __attribute__((cleanup(str_auto))) static void str_auto(char **str) { if (!str || !*str) return; OPENSSL_cleanse(*str, strlen(*str)); free(*str); } static json_int_t npins(json_t *pins) { const char *key = NULL; json_t *val = NULL; json_int_t n = 0; json_object_foreach(pins, key, val) { if (json_is_object(val)) n++; else if (json_is_array(val)) n += json_array_size(val); } return n; } static json_t * encrypt_frag(json_t *sss, const char *pin, const json_t *cfg, int assume_yes) { char *args[] = { "clevis", "encrypt", (char *) pin, NULL, NULL, NULL }; json_auto_t *jwe = json_string(""); str_auto_t *str = NULL; uint8_t *pnt = NULL; FILE *pipe = NULL; size_t pntl = 0; pid_t pid = 0; int status = 0; str = args[3] = json_dumps(cfg, JSON_SORT_KEYS | JSON_COMPACT); if (!str) return NULL; if (assume_yes) { args[4] = "-y"; } pnt = sss_point(sss, &pntl); if (!pnt) return NULL; pipe = call(args, pnt, pntl, &pid); OPENSSL_cleanse(pnt, pntl); free(pnt); if (!pipe) return NULL; while (!feof(pipe)) { char buf[1024] = {}; json_t *tmp = NULL; size_t rd = 0; rd = fread(buf, 1, sizeof(buf), pipe); if (ferror(pipe)) { fclose(pipe); return NULL; } tmp = json_pack("s+%", json_string_value(jwe), buf, rd); if (!tmp) { fclose(pipe); return NULL; } json_decref(jwe); jwe = tmp; } fclose(pipe); waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { return NULL; } return json_incref(jwe); } static json_t * encrypt_frags(json_int_t t, json_t *pins, int assume_yes) { const char *pname = NULL; json_auto_t *sss = NULL; json_t *pcfgs = NULL; json_t *parr = NULL; /* Generate the SSS polynomial. */ sss = sss_generate(32, t); if (!sss) { fprintf(stderr, "Error generating SSS!\n"); return NULL; } if (json_object_set_new(sss, "jwe", parr = json_array()) < 0) return NULL; /* Encrypt each key share with a child pin. */ json_object_foreach(pins, pname, pcfgs) { json_t *pcfg = NULL; size_t i = 0; if (json_is_object(pcfgs)) pcfgs = json_pack("[O]", pcfgs); else if (json_is_array(pcfgs)) pcfgs = json_incref(pcfgs); else return NULL; if (json_object_set_new(pins, pname, pcfgs) < 0) return NULL; json_array_foreach(pcfgs, i, pcfg) { json_auto_t *jwe = NULL; jwe = encrypt_frag(sss, pname, pcfg, assume_yes); if (!jwe) return NULL; if (json_array_append(parr, jwe) < 0) return NULL; } } return json_incref(sss); } int main(int argc, char *argv[]) { const char *SUMMARY = "Encrypts using a Shamir's Secret Sharing policy"; jose_io_auto_t *out = NULL; jose_io_auto_t *b64 = NULL; jose_io_auto_t *enc = NULL; json_auto_t *cfg = NULL; json_auto_t *jwk = NULL; json_auto_t *jwe = NULL; json_auto_t *sss = NULL; const char *key = NULL; const char *prt = NULL; const char *tag = NULL; const char *iv = NULL; json_t *pins = NULL; json_int_t t = 1; int assume_yes = 0; if (argc == 2 && strcmp(argv[1], "--summary") == 0) { fprintf(stdout, "%s\n", SUMMARY); return EXIT_SUCCESS; } if (isatty(STDIN_FILENO) || argc != 2) { if (argc != 3) { goto usage; } if (strcmp(argv[2], "-y") == 0) { assume_yes = 1; } else if (strlen(argv[2]) > 0) { goto usage; } } /* Parse configuration. */ cfg = json_loads(argv[1], 0, NULL); if (!cfg) { fprintf(stderr, "Error parsing config!\n"); return EXIT_FAILURE; } if (json_unpack(cfg, "{s?I,s:o}", "t", &t, "pins", &pins) != 0) { fprintf(stderr, "Config missing 'pins' attribute!\n"); return EXIT_FAILURE; } if (t < 1 || t > npins(pins)) { fprintf(stderr, "Invalid threshold (required: 1 <= %lld <= %lld)!\n", t, npins(pins)); return EXIT_FAILURE; } sss = encrypt_frags(t, pins, assume_yes); if (!sss) return EXIT_FAILURE; /* Perform encryption using the key. */ if (json_unpack(sss, "{s:[s]}", "e", &key) != 0) return EXIT_FAILURE; jwk = json_pack("{s:s,s:s,s:s}", "kty", "oct", "k", key, "alg", "A256GCM"); if (!jwk) return EXIT_FAILURE; if (json_object_del(sss, "e") != 0) return EXIT_FAILURE; jwe = json_pack("{s:{s:s,s:{s:s,s:O}}}", "protected", "alg", "dir", "clevis", "pin", "sss", "sss", sss); if (!jwe) return EXIT_FAILURE; out = jose_io_file(NULL, stdout); b64 = jose_b64_enc_io(out); enc = jose_jwe_enc_cek_io(NULL, jwe, jwk, b64); if (!out || !b64 || !enc) return EXIT_FAILURE; if (json_unpack(jwe, "{s:s,s:s}", "protected", &prt, "iv", &iv) != 0) return EXIT_FAILURE; if (fprintf(stdout, "%s..%s.", prt, iv) < 0) return EXIT_FAILURE; while (!feof(stdin)) { uint8_t rd[1024] = {}; size_t r = 0; r = fread(rd, 1, sizeof(rd), stdin); if (ferror(stdin)) { fprintf(stderr, "Error reading plaintext!\n"); return EXIT_FAILURE; } if (!enc->feed(enc, rd, r)) return EXIT_FAILURE; } if (!enc->done(enc)) return EXIT_FAILURE; if (json_unpack(jwe, "{s:s}", "tag", &tag) != 0) return EXIT_FAILURE; if (fprintf(stdout, ".%s%s", tag, isatty(STDOUT_FILENO) ? "\n" : "") < 0) return EXIT_FAILURE; return EXIT_SUCCESS; usage: fprintf(stderr, "\n"); fprintf(stderr, "Usage: clevis encrypt sss CONFIG [-y] < PLAINTEXT > JWE\n"); fprintf(stderr, "\n"); fprintf(stderr, "%s\n", SUMMARY); fprintf(stderr, "\n"); fprintf(stderr, "This command uses the following configuration properties:\n"); fprintf(stderr, "\n"); fprintf(stderr, " t: Number of pins required for decryption (REQUIRED)\n"); fprintf(stderr, "\n"); fprintf(stderr, " pins: Pins used for encrypting fragments (REQUIRED)\n"); fprintf(stderr, "\n"); fprintf(stderr, "Here is an example configuration for one of two servers:\n"); fprintf(stderr, "\n"); fprintf(stderr, "{\n"); fprintf(stderr, " \"t\": 1,\n"); fprintf(stderr, " \"pins\": {\n"); fprintf(stderr, " \"tang\": [\n"); fprintf(stderr, " { \"url\": \"http://example.com/tang1\" },\n"); fprintf(stderr, " { \"url\": \"http://example.com/tang2\" }\n"); fprintf(stderr, " ]\n"); fprintf(stderr, " }\n"); fprintf(stderr, "}\n"); fprintf(stderr, "\n"); return EXIT_FAILURE; } latchset-clevis-6df9b69/src/pins/sss/meson.build000066400000000000000000000021671520110721700217530ustar00rootroot00000000000000libcrypto = dependency('libcrypto', required: false) if jansson.found() and libcrypto.found() executable('clevis-decrypt-sss', ['clevis-decrypt-sss.c', 'sss.c', 'sss.h'], dependencies: [jansson, libcrypto, jose], install_dir: bindir, install: true, ) executable('clevis-encrypt-sss', ['clevis-encrypt-sss.c', 'sss.c', 'sss.h'], dependencies: [jansson, libcrypto, jose], install_dir: bindir, install: true, ) src = meson.current_source_dir() mans += join_paths(src, 'clevis-encrypt-sss.1') env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), meson.current_build_dir(), '/usr/libexec', libexecdir, src, separator: ':' ) bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-null') bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-null') test('pin-sss', find_program(join_paths(src, 'pin-sss')), env: env) test('pin-null', find_program(join_paths(src, 'pin-null')), env: env) else warning('Will not install sss pin due to missing dependencies!') endif latchset-clevis-6df9b69/src/pins/sss/pin-null000077500000000000000000000003331520110721700212660ustar00rootroot00000000000000#!/bin/bash -x trap 'exit' ERR e="$(echo -n hi | clevis encrypt null '{}')" d="$(echo -n "$e" | clevis decrypt)" test "$d" == "hi" e="$(echo -n hi | clevis encrypt null '{"fail":true}')" ! echo "$e" | clevis decrypt latchset-clevis-6df9b69/src/pins/sss/pin-sss000077500000000000000000000015041520110721700211250ustar00rootroot00000000000000#!/bin/bash -ex e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"null":[{},{}]}}')" d="$(echo "$e" | clevis decrypt)" test "$d" == "hi" e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"null":[{},{"fail":true}]}}')" d="$(echo "$e" | clevis decrypt)" test "$d" == "hi" e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"null":[{"fail":true},{"fail":true}]}}')" ! echo "$e" | clevis decrypt e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"null":[{},{}]}}')" d="$(echo "$e" | clevis decrypt)" test "$d" == "hi" e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"null":[{},{"fail":true}]}}')" ! echo "$e" | clevis decrypt e="$(echo hi | clevis encrypt sss '{"t":2,"pins":{"null":[{"fail":true},{"fail":true}]}}')" ! echo "$e" | clevis decrypt ! e="$(echo hi | clevis encrypt sss '{"t":1,"pins":{"tang":[{"url":"foo bar"}]}}')" latchset-clevis-6df9b69/src/pins/sss/sss.c000066400000000000000000000216711520110721700205660ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GPLv3 section 7: * * In the following paragraph, "GPL" means the GNU General Public * License, version 3 or any later version, and "Non-GPL Code" means * code that is governed neither by the GPL nor a license * compatible with the GPL. * * You may link the code of this Program with Non-GPL Code and convey * linked combinations including the two, provided that such Non-GPL * Code only links to the code of this Program through those well * defined interfaces identified in the file named EXCEPTION found in * the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline * functions from the Approved Interfaces without causing the resulting * work to be covered by the GPL. Only the copyright holders of this * Program may make changes or additions to the list of Approved * Interfaces. */ #define _GNU_SOURCE #include "sss.h" #include #include #include #include #include #include #include #include #include #include #include #define BIGNUM_auto __attribute__((cleanup(BN_cleanup))) BIGNUM #define BN_CTX_auto __attribute__((cleanup(BN_CTX_cleanup))) BN_CTX static BIGNUM * bn_decode(const uint8_t buf[], size_t len) { return BN_bin2bn(buf, len, NULL); } static BIGNUM * bn_decode_json(const json_t *json) { uint8_t *buf = NULL; BIGNUM *bn = NULL; size_t len; len = jose_b64_dec(json, NULL, 0); if (len == SIZE_MAX) return NULL; buf = malloc(len); if (!buf) return NULL; if (jose_b64_dec(json, buf, len) != len) { free(buf); return NULL; } bn = bn_decode(buf, len); free(buf); return bn; } static bool bn_encode(const BIGNUM *bn, uint8_t buf[], size_t len) { int bytes = 0; if (!bn) return false; if (len == 0) len = BN_num_bytes(bn); bytes = BN_num_bytes(bn); if (bytes < 0 || bytes > (int) len) return false; memset(buf, 0, len); return BN_bn2bin(bn, &buf[len - bytes]) > 0; } static json_t * bn_encode_json(const BIGNUM *bn, size_t len) { uint8_t *buf = NULL; json_t *out = NULL; if (!bn) return NULL; if (len == 0) len = BN_num_bytes(bn); if ((int) len < BN_num_bytes(bn)) return NULL; buf = malloc(len); if (!buf) return NULL; if (!bn_encode(bn, buf, len)) { free(buf); return NULL; } out = jose_b64_enc(buf, len); free(buf); return out; } static void BN_CTX_cleanup(BN_CTX **ctx) { if (ctx) BN_CTX_free(*ctx); } static void BN_cleanup(BIGNUM **bnp) { if (bnp) BN_clear_free(*bnp); } json_t * sss_generate(size_t key_bytes, size_t threshold) { BIGNUM_auto *p = NULL; BIGNUM_auto *e = NULL; json_t *sss = NULL; if (key_bytes == 0 || threshold < 1) return NULL; p = BN_new(); e = BN_new(); if (!p || !e) goto error; if (!BN_generate_prime_ex(p, key_bytes * 8, 1, NULL, NULL, NULL)) goto error; sss = json_pack("{s:i,s:[],s:o}", "t", threshold, "e", "p", bn_encode_json(p, key_bytes)); if (!sss) goto error; for (size_t i = 0; i < threshold; i++) { if (BN_rand_range(e, p) <= 0) goto error; if (json_array_append_new(json_object_get(sss, "e"), bn_encode_json(e, key_bytes))) goto error; } return sss; error: json_decref(sss); return NULL; } uint8_t * sss_point(const json_t *sss, size_t *len) { BN_CTX_auto *ctx = NULL; BIGNUM_auto *tmp = NULL; BIGNUM_auto *xx = NULL; BIGNUM_auto *yy = NULL; BIGNUM_auto *pp = NULL; uint8_t *key = NULL; json_t *e = NULL; json_t *p = NULL; json_int_t t = 0; if (json_unpack((json_t *) sss, "{s:I,s:o,s:o}", "t", &t, "p", &p, "e", &e) != 0) return NULL; ctx = BN_CTX_new(); pp = bn_decode_json(p); xx = BN_new(); yy = BN_new(); tmp = BN_new(); if (!ctx || !pp || !xx || !yy || !tmp) return NULL; if (BN_rand_range(xx, pp) <= 0) return NULL; if (BN_set_word(yy, 0) <= 0) return NULL; for (size_t i = 0; i < json_array_size(e); i++) { BIGNUM_auto *ee = NULL; ee = bn_decode_json(json_array_get(e, i)); if (!ee) return NULL; if (BN_cmp(pp, ee) <= 0) return NULL; /* y += e[i] * x^i */ if (BN_set_word(tmp, i) <= 0) return NULL; if (BN_mod_exp(tmp, xx, tmp, pp, ctx) <= 0) return NULL; if (BN_mod_mul(tmp, ee, tmp, pp, ctx) <= 0) return NULL; if (BN_mod_add(yy, yy, tmp, pp, ctx) <= 0) return NULL; } *len = jose_b64_dec(p, NULL, 0); if (*len == SIZE_MAX) return NULL; key = malloc(*len * 2); if (!key) return NULL; if (!bn_encode(xx, key, *len) || !bn_encode(yy, &key[*len], *len)) { memset(key, 0, *len * 2); free(key); return NULL; } *len *= 2; return key; } json_t * sss_recover(const json_t *p, size_t npnts, const uint8_t *pnts[]) { BN_CTX_auto *ctx = BN_CTX_new(); BIGNUM_auto *pp = bn_decode_json(p); BIGNUM_auto *acc = BN_new(); BIGNUM_auto *tmp = BN_new(); BIGNUM_auto *k = BN_new(); size_t len = 0; if (!ctx || !pp || !acc || !tmp || !k) return NULL; if (BN_set_word(k, 0) <= 0) return NULL; len = jose_b64_dec(p, NULL, 0); if (len == SIZE_MAX) return NULL; for (size_t i = 0; i < npnts; i++) { BIGNUM_auto *xo = NULL; /* Outer X */ BIGNUM_auto *yo = NULL; /* Outer Y */ xo = bn_decode(pnts[i], len); yo = bn_decode(&pnts[i][len], len); if (!xo || !yo) return NULL; if (BN_one(acc) <= 0) return NULL; for (size_t j = 0; j < npnts; j++) { BIGNUM_auto *xi = NULL; /* Inner X */ if (i == j) continue; xi = bn_decode(pnts[j], len); if (!xi) return NULL; /* acc *= (0 - xi) / (xo - xi) */ if (BN_set_word(tmp, 0) <= 0) return NULL; if (BN_mod_sub(tmp, tmp, xi, pp, ctx) <= 0) return NULL; if (BN_mod_mul(acc, acc, tmp, pp, ctx) <= 0) return NULL; if (BN_mod_sub(tmp, xo, xi, pp, ctx) <= 0) return NULL; if (BN_mod_inverse(tmp, tmp, pp, ctx) != tmp) return NULL; if (BN_mod_mul(acc, acc, tmp, pp, ctx) <= 0) return NULL; } /* k += acc * y[i] */ if (BN_mod_mul(acc, acc, yo, pp, ctx) <= 0) return NULL; if (BN_mod_add(k, k, acc, pp, ctx) <= 0) return NULL; } return bn_encode_json(k, len); } enum { PIPE_RD = 0, PIPE_WR = 1 }; FILE * call(char *const argv[], const void *buf, size_t len, pid_t *pid) { int dump[2] = { -1, -1 }; int load[2] = { -1, -1 }; FILE *out = NULL; ssize_t wr = 0; *pid = 0; if (pipe2(dump, O_CLOEXEC) < 0) goto error; if (pipe2(load, O_CLOEXEC) < 0) goto error; *pid = fork(); if (*pid < 0) goto error; if (*pid == 0) { if (dup2(dump[PIPE_RD], STDIN_FILENO) < 0 || dup2(load[PIPE_WR], STDOUT_FILENO) < 0) exit(EXIT_FAILURE); execvp(argv[0], argv); exit(EXIT_FAILURE); } for (const uint8_t *tmp = buf; len > 0; tmp += wr, len -= wr) { wr = write(dump[PIPE_WR], tmp, len); if (wr < 0) goto error; } out = fdopen(load[PIPE_RD], "r"); if (!out) goto error; close(dump[PIPE_RD]); close(dump[PIPE_WR]); close(load[PIPE_WR]); return out; error: close(dump[PIPE_RD]); close(dump[PIPE_WR]); close(load[PIPE_RD]); close(load[PIPE_WR]); if (*pid > 0) { kill(*pid, SIGTERM); waitpid(*pid, NULL, 0); *pid = 0; } return NULL; } latchset-clevis-6df9b69/src/pins/sss/sss.h000066400000000000000000000022311520110721700205620ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2015 Red Hat, Inc. * Author: Nathaniel McCallum * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include json_t * sss_generate(size_t key_bytes, size_t threshold); uint8_t * sss_point(const json_t *sss, size_t *len); json_t * sss_recover(const json_t *p, size_t npnts, const uint8_t *pnts[]); FILE * call(char *const argv[], const void *buf, size_t len, pid_t *pid); latchset-clevis-6df9b69/src/pins/tang/000077500000000000000000000000001520110721700177245ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/tang/clevis-decrypt-tang000077500000000000000000000074601520110721700235450ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis decrypt tang < JWE > PLAINTEXT" echo exit 2 fi read -r -d . hdr if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then echo "Error decoding JWE protected header!" >&2 exit 1 fi if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tang" ]; then echo "JWE pin mismatch!" >&2 exit 1 fi if ! clt="$(jose fmt -j- -Og epk -Oo- <<< "$jhd")"; then echo "JWE missing required 'epk' header parameter!" >&2 exit 1 fi if ! kid="$(jose fmt -j- -Og kid -Su- <<< "$jhd")"; then echo "JWE missing required 'kid' header parameter!" >&2 exit 1 fi # Tang advertisement validation. if ! keys="$(jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "${jhd}")"; then echo "JWE missing required 'clevis.tang.adv' header parameter!" >&2 exit 1 fi # Check if the thumbprint we have in `kid' is in the advertised keys. CLEVIS_DEFAULT_THP_ALG=S256 # SHA-256. CLEVIS_DEFAULT_THP_LEN=43 # Length of SHA-256 thumbprint. CLEVIS_ALTERNATIVE_THP_ALGS=S1 # SHA-1. # Issue a warning if we are using a hash that has a shorter length than the # default one. if [ "${#kid}" -lt "${CLEVIS_DEFAULT_THP_LEN}" ]; then echo "WARNING: tang using a deprecated hash for the JWK thumbprints" >&2 fi if ! srv="$(jose jwk thp -i- -f "${kid}" -a "${CLEVIS_DEFAULT_THP_ALG}" \ <<< "${keys}")"; then # `kid' thumprint not in the advertised keys, but it's possible it was # generated using a different algorithm than the default one. # Let us try the alternative supported algorithms to make sure `kid' # really is not part of the advertised keys. for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do srv="$(jose jwk thp -i- -f "$kid" -a "${alg}" <<< "${keys}")" && break done if [ -z "${srv}" ]; then echo "JWE header validation of 'clevis.tang.adv' failed: key thumbprint does not match" >&2 exit 1 fi fi if ! url="$(jose fmt -j- -Og clevis -g tang -g url -Su- <<< "$jhd")"; then echo "JWE missing required 'clevis.tang.url' header parameter!" >&2 exit 1 fi if ! crv="$(jose fmt -j- -Og crv -Su- <<< "$clt")"; then echo "Unable to determine EPK's curve!" >&2 exit 1 fi if ! eph="$(jose jwk gen -i "{\"alg\":\"ECMR\",\"crv\":\"$crv\"}")"; then echo "Error generating ephemeral key!" >&2 exit 1 fi xfr="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$clt$eph")" rec_url="$url/rec/$kid" ct="Content-Type: application/jwk+json" if ! rep="$(curl -sfg -X POST -H "$ct" --data-binary @- "$rec_url" <<< "$xfr")"; then echo "Error communicating with server $url" >&2 exit 1 fi if ! rep="$(jose fmt -j- -Og kty -q EC -EUUg crv -q "$crv" -EUUo- <<< "$rep")"; then echo "Received invalid server reply!" >&2 exit 1 fi tmp="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$eph$srv")" rep="$(jose jwk pub -i- <<< "$rep")" jwk="$(jose jwk exc -l- -r- <<< "$rep$tmp")" (echo -n "$jwk$hdr."; /bin/cat) | jose jwe dec -k- -i- exit $? latchset-clevis-6df9b69/src/pins/tang/clevis-encrypt-tang000077500000000000000000000125061520110721700235540ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # SUMMARY="Encrypts using a Tang binding server policy" if [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis encrypt tang CONFIG [-y] < PLAINTEXT > JWE" echo echo "$SUMMARY" echo echo " -y Use this option for skipping the advertisement" echo " trust check. This can be useful in automated" echo " deployments" echo echo "This command uses the following configuration properties:" echo echo " url: The base URL of the Tang server (REQUIRED)" echo echo " thp: The thumbprint of a trusted signing key" echo echo " adv: A filename containing a trusted advertisement" echo " adv: A trusted advertisement (raw JSON)" echo echo "Obtaining the thumbprint of a trusted signing key is easy. If you" echo "have access to the Tang server's database directory, simply do:" echo echo " $ jose jwk thp -i \$DBDIR/\$SIG.jwk " echo echo "Alternatively, if you have certainty that your network connection" echo "is not compromised (not likely), you can download the advertisement" echo "yourself using:" echo echo " $ curl -f \$URL/adv > adv.jws" echo exit 2 fi if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then echo "Configuration is malformed!" >&2 exit 1 fi CLEVIS_DEFAULT_THP_ALG=S256 # SHA-256. CLEVIS_ALTERNATIVE_THP_ALGS=S1 # SHA-1. trust= [ -n "${2}" ] && [ "${2}" == "-y" ] && trust=yes if ! url="$(jose fmt -j- -Og url -u- <<< "$cfg")"; then echo "Missing the required 'url' property!" >&2 exit 1 fi thp="$(jose fmt -j- -Og thp -Su- <<< "$cfg")" || true ### Get the advertisement if jws="$(jose fmt -j- -g adv -Oo- <<< "$cfg")"; then thp="${thp:-any}" elif jws="$(jose fmt -j- -g adv -Su- <<< "$cfg")"; then if ! [ -f "$jws" ]; then echo "Advertisement file '$jws' not found!" >&2 exit 1 fi if ! jws="$(jose fmt --json="${jws}" -Oo- 2>/dev/null)"; then echo "Advertisement file '$jws' is malformed!" >&2 exit 1 fi thp="${thp:-any}" elif ! jws="$(curl -sfg "$url/adv/$thp")"; then echo "Unable to fetch advertisement: '$url/adv/$thp'!" >&2 exit 1 fi if ! jwks="$(jose fmt --json="${jws}" -Og payload -SyOg keys \ -AUo- 2>/dev/null)"; then echo "Advertisement is malformed!" >&2 exit 1 fi ### Check advertisement validity ver="$(jose jwk use -i- -r -u verify -o- <<< "$jwks")" if ! jose jws ver -i "$jws" -k- -a <<< "$ver"; then echo "Advertisement is missing signatures!" >&2 exit 1 fi ### Check advertisement trust if [ -z "${trust}" ]; then if [ -z "$thp" ]; then echo "The advertisement contains the following signing keys:" >&2 echo >&2 jose jwk thp -i- -a "${CLEVIS_DEFAULT_THP_ALG}" <<< "$ver" >&2 echo >&2 read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty [[ "$ans" =~ ^[yY]$ ]] || exit 1 elif [ "$thp" != "any" ] && \ ! jose jwk thp -i- -f "${thp}" -a "${CLEVIS_DEFAULT_THP_ALG}" \ -o /dev/null <<< "$ver"; then # Thumbprint of trusted JWK did not match the signature. Let's check # alternative thumbprints generated with clevis supported hash # algorithms to be sure. for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do srv="$(jose jwk thp -i- -f "${thp}" -a "${alg}" <<< "${ver}")" \ && break done if [ -z "${srv}" ]; then echo "Trusted JWK '$thp' did not sign the advertisement!" >&2 exit 1 fi fi fi ### Perform encryption if ! enc="$(jose jwk use -i- -r -u deriveKey -o- <<< "$jwks")"; then echo "Key derivation key not available!" >&2 exit 1 fi jose fmt -j "$enc" -Og keys -A || enc="{\"keys\":[$enc]}" if ! jwk="$(jose fmt -j- -Og keys -Af- <<< "$enc")"; then echo "No exchange keys found!" >&2 exit 1 fi jwk="$(jose fmt -j- -Od key_ops -o- <<< "$jwk")" jwk="$(jose fmt -j- -Od alg -o- <<< "$jwk")" kid="$(jose jwk thp -i- -a "${CLEVIS_DEFAULT_THP_ALG}" <<< "$jwk")" jwe='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}' jwe="$(jose fmt -j "$jwe" -g protected -q "$kid" -s kid -UUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -j- -s adv -UUUUo- <<< "$jwks")" exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) latchset-clevis-6df9b69/src/pins/tang/clevis-encrypt-tang.1.adoc000066400000000000000000000046551520110721700246230ustar00rootroot00000000000000CLEVIS-ENCRYPT-TANG(1) ====================== :doctype: manpage == NAME clevis-encrypt-tang - Encrypts using a Tang binding server policy == SYNOPSIS *clevis encrypt tang* CONFIG [-y] < PT > JWE == OVERVIEW The *clevis encrypt tang* command encrypts using a Tang binding server policy. Its only argument is the JSON configuration object. Clevis provides support for the Tang network binding server. Tang provides a stateless, lightweight alternative to escrows. Encrypting data using the Tang pin works like this: $ clevis encrypt tang '{"url":"http://tang.srv"}' < PT > JWE The advertisement contains the following signing keys: _OsIk0T-E2l6qjfdDiwVmidoZjA Do you wish to trust these keys? [ynYN] y To decrypt the data, just pass it to the *clevis decrypt* command: $ clevis decrypt < JWE > PT As you can see above, Tang utilizes a trust-on-first-use workflow. If you already know the thumbprint of a trusted key, you can specify it in the configuration at encryption time: $ cfg='{"url":"http://tang.srv","thp":"_OsIk0T-E2l6qjfdDiwVmidoZjA"}' $ clevis encrypt tang "$cfg" < PT > JWE Obtaining the thumbprint of a trusted signing key is easy. If you have access to the Tang server, simply execute: $ tang-show-keys where is the port that the Tang server is listening on. If *tang-show-keys* is not available, but you have access to the Tang server's database directory, you can execute this instead: $ jose jwk thp -i $DBDIR/$SIG.jwk Tang can also perform entirely offline encryption if you pre-share the server advertisement. You can fetch the advertisement with a simple command (just be careful your network isn't compromised!): $ curl -f $URL/adv > adv.jws Once you have the advertisement file, just provide it: $ clevis encrypt tang '{"url":...,"adv":"adv.jws"}' < PT > JWE == CONFIG This command uses the following configuration properties: * *url* (string) : The base URL of the Tang server (REQUIRED) * *thp* (string) : The thumbprint of a trusted signing key * *adv* (string) : A filename containing a trusted advertisement * *adv* (object) : A trusted advertisement (raw JSON) == OPTIONS * *-y* : Automatically answer yes for all questions. Use this option for skipping the advertisement trust check. This can be useful in automated deployments: $ clevis encrypt tang '{"url":...}' -y < PT > JWE == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/pins/tang/meson.build000066400000000000000000000006701520110721700220710ustar00rootroot00000000000000curl = find_program('curl', required: false) if curl.found() or meson.is_cross_build() bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tang') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tang') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tang.1') if not meson.is_cross_build() subdir('tests') endif else warning('Will not install tang pin due to missing dependencies!') endif latchset-clevis-6df9b69/src/pins/tang/tests/000077500000000000000000000000001520110721700210665ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/tang/tests/default-thp-alg000077500000000000000000000110251520110721700237710ustar00rootroot00000000000000#!/bin/bash set -exo pipefail # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . . tang-common-test-functions TEST=$(basename "${0}") on_exit() { exit_status=$? tang_stop "${TMP}" [ -d "${TMP}" ] && rm -rf "${TMP}" exit "${exit_status}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" tang_run "${TMP}" port=$(tang_get_port "${TMP}") url="http://localhost:${port}" data="just a sample text" # Get the advertisement and extract the keys. adv="$(tang_get_adv "${port}")" jwks="$(jose fmt --json="${adv}" --get payload --b64load --output=-)" enc="$(printf '%s' "${jwks}" | jose jwk use --input=- --required \ --use deriveKey --output=-)" jose fmt --json="${enc}" --get keys --array \ || enc="$(printf '{"keys": [%s]}' "${enc}")" jwk="$(jose fmt --json="${enc}" --get keys --array --foreach=- \ | jose fmt --json=- --delete key_ops --delete alg --output=-)" jwe_t='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}' jwe_t="$(jose fmt --json="${jwe_t}" --get protected --get clevis --get tang --quote "${url}" --set url -UUUUo-)" jwe_t="$(printf '%s' "${jwks}" | jose fmt --json="${jwe_t}" --get protected --get clevis --get tang --json=- --set adv -UUUUo-)" # We currently support SHA-1 (legacy) and SHA-256. CLEVIS_SUPPORTED_THP_ALGS="S1 S256" # Now we will use every hash algorithm supported by jose to create a thumbprint # for `kid', then we do the encoding and verify clevis decrypt can decode it # correctly. for alg in ${CLEVIS_SUPPORTED_THP_ALGS}; do kid="$(printf '%s' "${jwk}" | jose jwk thp -a "${alg}" --input=-)" jwe="$(jose fmt --json="${jwe_t}" --get protected --quote "${kid}" -s kid -UUo-)" encoded=$(printf '%s%s' "${jwk}" "${data}" \ | jose jwe enc --input="${jwe}" --key=- --detached=- --compact) if ! decoded="$(printf '%s' "${encoded}" | clevis decrypt)"; then error "${TEST}: decoding is expected to work (alg = ${alg})" fi if [ "${decoded}" != "${data}" ]; then error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})" fi done # Now let's test encryption providing the thp in the configuration. data="just another test" for alg in ${CLEVIS_SUPPORTED_THP_ALGS}; do thp="$(jose fmt --json="${adv}" -g payload -y -o- \ | jose jwk use -i- -r -u verify -o- \ | jose jwk thp -i- -a "${alg}")" cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")" if ! encoded=$(printf '%s' "${data}" | clevis encrypt tang "${cfg}"); then error "${TEST}: tang encryption should have succeeded when providing the thp (${thp}) with any supported algorithm (${alg})" fi if ! decoded="$(printf '%s' "${encoded}" | clevis decrypt)"; then error "${TEST}: decoding is expected to work (thp alg = ${alg})" fi if [ "${decoded}" != "${data}" ]; then error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})" fi done # Let's also try some unsupported thp hash algorithms. UNSUPPORTED="S224 S384 S512" # SHA-224, SHA-384, SHA-512. for alg in ${UNSUPPORTED}; do thp="$(jose fmt --json="${adv}" -g payload -y -o- \ | jose jwk use -i- -r -u verify -o- \ | jose jwk thp -i- -a "${alg}")" cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")" if echo foo | clevis encrypt tang "${cfg}" >/dev/null; then error "${TEST}: tang encryption should have failed when providing the thp (${thp}) with an unsupported algorithm (${alg})" fi done # Now let's try some bad values for thp. for thp in "" "foo" "invalid"; do cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")" if echo foo | clevis encrypt tang "${cfg}" >/dev/null; then error "${TEST}: tang encryption expected to fail when providing a bad thp" fi done latchset-clevis-6df9b69/src/pins/tang/tests/meson.build000066400000000000000000000032251520110721700232320ustar00rootroot00000000000000kgen = find_program( join_paths(libexecdir, 'tangd-keygen'), join_paths(get_option('prefix'), get_option('libdir'), 'tangd-keygen'), join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd-keygen'), join_paths('/', 'usr', get_option('libdir'), 'tangd-keygen'), join_paths('/', 'usr', get_option('libexecdir'), 'tangd-keygen'), required: false ) tang = find_program( join_paths(libexecdir, 'tangd'), join_paths(get_option('prefix'), get_option('libdir'), 'tangd'), join_paths(get_option('prefix'), get_option('libexecdir'), 'tangd'), join_paths('/', 'usr', get_option('libdir'), 'tangd'), join_paths('/', 'usr', get_option('libexecdir'), 'tangd'), required: false ) socat = find_program('socat', required: false) tang_data = configuration_data() tang_data.set('SOCAT', '') tang_data.set('TANGD_KEYGEN', '') tang_data.set('TANGD', '') if socat.found() tang_data.set('SOCAT', socat.path()) endif if kgen.found() tang_data.set('TANGD_KEYGEN', kgen.path()) endif if tang.found() tang_data.set('TANGD', tang.path()) endif tang_tests_common = configure_file( input: 'tang-common-test-functions.in', output: 'tang-common-test-functions', configuration: tang_data ) env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), join_paths(meson.build_root(), 'src', 'luks', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), separator: ':' ) test('pin-tang', find_program('pin-tang'), env: env) test('tang-validate-adv', find_program('tang-validate-adv'), env: env) test('default-thp-alg', find_program('default-thp-alg'), env: env) latchset-clevis-6df9b69/src/pins/tang/tests/pin-tang000077500000000000000000000030701520110721700225310ustar00rootroot00000000000000#!/bin/bash -xe # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2016 Red Hat, Inc. # Author: Nathaniel McCallum # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tang-common-test-functions on_exit() { exit_status=$? tang_stop "${TMP}" [ -d "$TMP" ] && rm -rf "$TMP" exit "${exit_status}" } trap 'on_exit' EXIT TMP="$(mktemp -d)" tang_run "${TMP}" sig exc port=$(tang_get_port "${TMP}") thp="$(jose jwk thp -i "$TMP/db/sig.jwk")" adv="${TMP}/adv.jws" tang_get_adv "${port}" "${adv}" url="http://localhost:${port}" cfg="$(printf '{"url":"%s","adv":"%s"}' "$url" "$adv")" enc="$(echo -n "hi" | clevis encrypt tang "$cfg")" dec="$(echo -n "$enc" | clevis decrypt)" test "$dec" == "hi" cfg="$(printf '{"url":"%s","thp":"%s"}' "$url" "$thp")" enc="$(echo -n "hi" | clevis encrypt tang "$cfg")" dec="$(echo -n "$enc" | clevis decrypt)" test "$dec" == "hi" tang_stop "${TMP}" ! echo "$enc" | clevis decrypt latchset-clevis-6df9b69/src/pins/tang/tests/tang-common-test-functions.in000066400000000000000000000116101520110721700266170ustar00rootroot00000000000000#!/bin/bash -ex # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tests-common-functions SOCAT="@SOCAT@" TANGD_KEYGEN="@TANGD_KEYGEN@" TANGD="@TANGD@" tang_sanity_check() { [ -n "${SOCAT}" ] && [ -n "${TANGD_KEYGEN}" ] && \ [ -n "${TANGD}" ] && return 0 skip_test "tang is not enabled/supported. Check if you have met all the requirements" } # Creates a tang adv to be used in the tests. tang_create_adv() { local basedir="${1}" local adv="${2:-/dev/stdout}" local SIG="${basedir}/sig.jwk" jose jwk gen --input='{"alg":"ES512"}' --output="${SIG}" local EXC="${basedir}/exc.jwk" jose jwk gen --input='{"alg":"ECMR"}' --output="${EXC}" local TEMPLATE='{"protected":{"cty":"jwk-set+json"}}' jose jwk pub --set --input="${SIG}" --input="${EXC}" \ | jose jws sig --detached=- --signature="${TEMPLATE}" \ --key="${SIG}" --output="${adv}" } # Removes tang rotated keys from the test server. tang_remove_rotated_keys() { tang_sanity_check local basedir="${1}" [ -z "${basedir}" ] && \ error "tang_remove_rotated_keys: please specify 'basedir'" local db="${basedir}/db" mkdir -p "${db}" pushd "${db}" find . -name ".*.jwk" -exec rm -f {} \; popd return 0 } # Creates new keys for the test tang server. tang_new_keys() { tang_sanity_check local basedir="${1}" local rotate="${2:-}" local sig_name="${3:-}" local exc_name="${4:-}" [ -z "${basedir}" ] && error "tang_new_keys: please specify 'basedir'" local db="${basedir}/db" mkdir -p "${db}" if [ -n "${rotate}" ]; then pushd "${db}" local k k=$(find . -name "*.jwk" | wc -l) if [ "${k}" -gt 0 ]; then for k in *.jwk; do mv -f -- "${k}" ".${k}" done fi popd fi "${TANGD_KEYGEN}" "${db}" ${sig_name} ${exc_name} return 0 } # Start a test tang server. tang_run() { tang_sanity_check local basedir="${1}" local sig_name="${2:-}" local exc_name="${3:-}" [ -z "${basedir}" ] && error "tang_run: please specify 'basedir'" if ! tang_new_keys "${basedir}" "" "${sig_name}" "${exc_name}"; then error "Error creating new keys for tang server" fi local KEYS="${basedir}/db" local pid pidfile portfile pidfile="${basedir}/tang.pid" portfile="${basedir}/tang.port" "${SOCAT}" -v -v TCP4-LISTEN:0,fork \ exec:"${TANGD} ${KEYS}" & pid=$! echo "${pid}" > "${pidfile}" process_wait_until_port_ready "${pid}" process_find_port "${pid}" "tang" > "${portfile}" } # Stop tang server. tang_stop() { tang_sanity_check local basedir="${1}" [ -z "${basedir}" ] && error "tang_stop: please specify 'basedir'" local pidfile="${basedir}/tang.pid" [ -f "${pidfile}" ] || return 0 local portfile="${basedir}/tang.port" local pid pid=$(<"${pidfile}") kill -9 "${pid}" 2>/dev/null || : rm -f "${portfile}" rm -f "${pidfile}" } tang_get_port() { local basedir="${1}" [ -z "${basedir}" ] && error "tang_get_port: please specify 'basedir'" local portfile="${basedir}/tang.port" [ -f "${portfile}" ] || error "tang_get_port: tang is not running" cat "${portfile}" } # Get tang advertisement. tang_get_adv() { tang_sanity_check local port="${1}" local adv="${2:-/dev/stdout}" [ -z "${port}" ] && error "tang_get_adv: please specify 'port'" curl -L -o "${adv}" "http://localhost:${port}/adv" } run_test_server() { local basedir="${1}" local response="${2}" [ -z "${SOCAT}" ] && tang_skip "run_test_server: socat is not available" [ -z "${basedir}" ] && tang_error "run_test_server: please specify 'basedir'" [ -z "${response}" ] && tang_error "run_test_server: please specify 'response'" local pid pidfile portfile pidfile="${basedir}/tang.pid" portfile="${basedir}/tang.port" "${SOCAT}" -v -v TCP4-LISTEN:0,bind=localhost "SYSTEM:cat ${response}!!OPEN:/dev/null" & pid=$! echo "${pid}" > "${pidfile}" process_wait_until_port_ready "${pid}" process_find_port "${pid}" "tang" > "${portfile}" } latchset-clevis-6df9b69/src/pins/tang/tests/tang-validate-adv000077500000000000000000000074661520110721700243210ustar00rootroot00000000000000#!/bin/bash -xe # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2020 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tang-common-test-functions on_exit() { local exit_status=$? for d in "${TMP}" "${TMP2}"; do [ ! -d "${d}" ] && continue tang_stop "${d}" rm -rf "${d}" done exit "${exit_status}" } do_test() { local response="${1}" local stderr="${2:-/dev/stderr}" local port run_test_server "${TMP2}" "${response}" port=$(tang_get_port "${TMP2}") cfg="$(printf '{"url":"localhost:%d"}' "${port}")" if ! echo foo | clevis encrypt tang "${cfg}" -y 2>"${stderr}"; then echo "Error (do_test) response: ${response}" >&2 [ -r "${stderr}" ] && cat "${stderr}" >&2 return 1 fi } do_test_with_adv() { local adv="${1}" local stderr="${2:-/dev/stderr}" cfg="$(printf '{"url":"127.1.2.3:1234","adv":"%s"}' "${adv}")" if ! echo foo-adv | clevis encrypt tang "${cfg}" 2>"${stderr}"; then echo "Error (do_test_with_adv) adv: ${adv} response: ${response}" >&2 [ -r "${stderr}" ] && cat "${stderr}" >&2 return 1 fi } validate_output() { local output="${1}" if grep -Fq jose "${output}"; then error "'jose' is not expected to appear in the error output" fi } trap 'on_exit' EXIT TMP="$(mktemp -d)" TMP2="$(mktemp -d)" CASES="${TMP}/cases" mkdir -p "${CASES}" # Let's test server responses. # Case 1 - regular advertisement - PASS. RESP="${CASES}"/good-01 cat << EOF > "${RESP}" HTTP/1.0 200 OK $(tang_create_adv "${TMP}" /dev/stdout) EOF # Case 2 - bad advertisement. RESP="${CASES}"/bad-01 adv='{' cat << EOF > "${RESP}" HTTP/1.0 200 OK ${adv} EOF # Case 3 - returning 404. RESP="${CASES}"/bad-02 cat << EOF > "${RESP}" HTTP/1.0 404 Not Found EOF # case 4 - returning 301. RESP="${CASES}"/bad-03 cat << EOF > "${RESP}" HTTP/1.0 301 Moved Permanently EOF # case 5 - returning 500. RESP="${CASES}"/bad-04 cat << EOF > "${RESP}" HTTP/1.0 500 Internal Server Error EOF for c in "${CASES}"/good-*; do STDERR="${c}".stderr do_test "${c}" "${STDERR}" validate_output "${STDERR}" done # Tests where bind is expected to fail (validate is still expected to succeed). for c in "${CASES}"/bad-*; do STDERR="${c}".stderr ! do_test "${c}" "${STDERR}" validate_output "${STDERR}" done # Now let's do some tests passing "adv" in the configuration. STDERR="${CASES}"/stderr for adv in "[]" "]" "" "{}"; do ! do_test_with_adv "${adv}" "${STDERR}" validate_output "${STDERR}" done # Now let's use existing files as well. tang_run "${TMP}" port=$(tang_get_port "${TMP}") touch "${CASES}"/adv-bad-01 echo '{' > "${CASES}"/adv-bad-02 echo "foobar" > "${CASES}"/adv-bad-03 tang_get_adv "${port}" "${CASES}"/adv-good-01 # Tests where bind is expected to pass. for adv in "${CASES}"/adv-good-*; do STDERR="${adv}".stderr do_test_with_adv "${adv}" "${STDERR}" validate_output "${STDERR}" done # Tests where bind is expected to fail. validate still should pass. for adv in "${CASES}"/adv-bad-*; do STDERR="${adv}".stderr ! do_test_with_adv "${adv}" "${STDERR}" validate_output "${STDERR}" done latchset-clevis-6df9b69/src/pins/template/000077500000000000000000000000001520110721700206065ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/template/README.md000066400000000000000000000053241520110721700220710ustar00rootroot00000000000000 # Writing your own pin The files here should give you an introduction into writing your own pin. ## Overall workflow The `encrypt` script reads a plain text from stdin, encrypts it using `jose jwe enc` which writes the result to stdout, together with some information how to re-create the plain text later. The encryption key itself *must* *not* be included here. The encryption key is provided or created by the pin and stashed away in some way. That is the core logic of a pin. A configuration in the JSON format is provided as the first parameter, it controls the pin's operation. The `decrypt` script reads the encrypted information from stdin, decrypts it using `ose jwe dec` which again writes the result to stdout. The information provided by `encrypt` above is available, this must be sufficient to restore the encryption key. ## How to use this template Copy all the files here (except for this one) into a new subdirectory of `src/pins/`, named as your pin. Replace @pin@ with the name of your pin everywhere, including file names. The `clevis-{en,de}crypt-@pin@` scripts require the most attention. Have a man page in `clevis-encrypt-@pin@.1.adoc`. Adjust `meson.build`. Provide a test in `pin-@pin@`. Adjust dracut configuration in `dracut.module-setup.sh.in`. Adjust initramfs configuration in `initramfs.in`. Optionally add something to `clevis-luks-list`. Finally, add your pin in `../meson.build`. ## Comments An extra form of comments is used to explain concepts. They all should be removed before sending out patches/merge requests. #%# some generic information #!# things worth to know, gotchas #?# some bits that require more understanding ## Nameing your pin and configuration variables The pin name should be short and reflect the purpose. To avoid trouble or extra work, the name should start with a letter, followed by letters, digits, or underscore. Parameter names for the pin configuration should follow the same syntax. These templates assume they can be used as a shell variable. ## Templates variables The templates use `@...@` to mark places that can semi-automatically be adjusted to your needs. Variables are * `@pin@`: The name of this pin, see above * `@PIN@`: The name of this pin, uppercase * `@year@`: Current year * `@name@`: Your name * `@email@`: Your e-mail address * `@mand1@`: The name of a mandatory parameter * `@mand2@`: The name of another mandatory parameter * `@opt1@`: The name of an optional parameter * `@param1@`: The name of a parameter needed for decryption * `@param2@`: Another name If you have more parameters, extend accordingly Any `@@` requires attention in wording. Make sure you've replaced *all* occurances of template variables. Else the build will probably fail. latchset-clevis-6df9b69/src/pins/template/clevis-decrypt-@pin@000077500000000000000000000047571520110721700244720ustar00rootroot00000000000000#%# Creating an decrypting pin #%# #%# Read README.md and clevis-encrypt-@pin@ first, this file aims to #%# to avoid information duplication. #%# Unfortunately, this one uses a bashism (read -d) that is not at #%# all easy to eliminate. #!/bin/bash set -eu # Copyright (c) @year@ @name@ # Author: @name@ <@email@> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # #%# This program takes no options - everything needed to know will be #%# read from stdin. [ $# -eq 1 ] && [ "${1:-}" = "--summary" ] && exit 2 if [ -t 0 ] ; then echo >&2 echo 'Usage: clevis decrypt @pin@ < JWE > PLAINTEXT' >&2 echo >&2 exit 1 fi #%# The input is concatenated using the dot. Read the first element #%# but leave everything else in the buffer. Only read -d can to that. read -d . hdr64 #%# The header is base64-encoded. Decode now and also verify this is valid JSON if ! hdr="$(jose fmt --quote="$hdr64" --string --b64load --object --output=-)" ; then echo 'JWE header corrupt' >&2 exit 1 fi #%# Input validation: The pin must exist by name. if [ "$(jose fmt --json="$hdr" --get clevis --get pin --unquote=-)" != '@pin@' ] ; then echo 'JWE pin mismatch!' >&2 exit 1 fi #%# Load the parameters into shell variables. if ! @param1@="$(jose fmt --json="$hdr" --get clevis --get @pin@ --get @param1@ --unquote=-)" ; then echo 'JWE missing 'clevis.@pin@.@param1@' header parameter!' >&2 exit 1 fi if ! @param2@="$(jose fmt --json="$hdr" --get clevis --get @pin@ --get @param2@ --unquote=-)" ; then echo 'JWE missing 'clevis.@pin@.@param2@' header parameter!' >&2 exit 1 fi #%# Possibly some pre-checks on your parameters are needed. #%# Now everything is set up for your pin's business logic #%# #%# Your job: Somehow bring the key into `jwk`. jwk="$(load_jwk)" #%# Finally, forward everything to `jose jwe dec` which does the #%# decryption job. ( printf '%s' "$jwk$hdr64." ; cat ) | exec jose jwe dec --key=- --input=- latchset-clevis-6df9b69/src/pins/template/clevis-encrypt-@pin@000077500000000000000000000123571520110721700244770ustar00rootroot00000000000000#%# Creating an encrypting pin #%# #%# Read README.md first. #%# #%# The shell interpreter. For portability, it should have as little #%# requirements as possibly. Especially the decrypt should be executable #%# in busybox' ash as well #!/bin/sh #%# Some hardening. Safeguard against coding errors. set -eu #%# Legal stuff. Put your name etc. here. #%# Of course you're not bound to GPL-3+ but it will certainly ease #%# inclusion in upstream clevis if you use that. # Copyright (c) @year@ @name@ # Author: @name@ <@email@> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # #%# A one-line summary. Will be used in the help messages below. SUMMARY='Encrypts using a @pin@ @@ policy' #%# Some option parsing, very simple. #%# Don't touch, it's hardcoded in the `clevis` program. if [ "${1:-}" = '--summary' ] ; then echo "$SUMMARY" exit 0 fi #%# Regular operation assumes output goes to a file. If not, print #%# some usage information and bail out. if [ -t 0 ] ; then #!# Since this script runs in a pipe, *all* operational messages #!# must go to stderr. exec >&2 echo #%# Don't be confused: This script is called from `clevis`, so #%# the usage text has spaces, not dashes. #%# Also, the configuration is in $1, see below. echo 'Usage: clevis encrypt @pin@ CONFIG < PLAINTEXT > JWE' echo echo "$SUMMARY" echo echo 'This command uses the following configuration properties:' echo #%# For the sake of users: Give a good explanation of your pin's #%# parameters. #%# Mandatory parameters should contain the string "REQUIRED" echo ' @mand1@: One parameter @@ (REQUIRED)' echo echo ' @mand2@: Another parameter @@ (REQUIRED)' echo #%# Optional parameters should mention the default value. echo ' @opt1@: An optional parameter @@ (default: @@)' echo #%# Pure visual: Make sure the short descriptions are aligned to the #%# same column. exit 2 fi #%# The CONFIG parameter in $1 has to be valid JSON if ! cfg="$(jose fmt --json="${1:-}" --object --output=- 2>/dev/null)" ; then echo 'Configuration is malformed!' >&2 exit 1 fi #%# Load the values from the configuration into shell variables. #%# Re-using the name is certainly a good idea #%# #%# For mandatory parameters it's like that: if ! @mand1@="$(jose fmt --json="$cfg" --object --get @mand1@ --unquote=-)" ; then echo 'Missing the required @mand1@ property!' >&2 exit 1 fi if ! @mand2@="$(jose fmt --json="$cfg" --object --get @mand2@ --unquote=-)" ; then echo 'Missing the required @mand2@ property!' >&2 exit 1 fi #%# For optional parameters, use: @opt1@="$(jose fmt --json="$cfg" --object --get @opt1@ --unquote=-)" || @opt1@='@@' #%# Possibly validate parameters. If a check can be done at *en*crypt #%# time, it should be done now. #%# Now everything is set up for your pin's business logic. #%# #%# Your jobs, in no particular order: #%# #%# 1. Have the key in `jwk`: #%# If you want to create a new key: jwk="$(jose jwk gen --input='{"alg":"A256GCM"}')" #%# Feel free to use different algorithms for `"alg"`, #%# jose-jwk-gen(1) and jose-alg(1) have more on all this. #%# #%# Or, if you want to use an existing key, just load it into `jwk` #%# from wherever you got it from: jwk="$(somehow_get_the_key)" #%# Remember the result must be a valid jwk object. #%# 2. Store the key somewhere. That's your logic. store_the_jwk "$jwk" #%# It is a good idea to store the entire `$jwk`. If you want to #%# extract the actual key, use #%# jose fmt --json="$jwk" --object --get k --unquote=- #%# ... but you're probably wrong if you want to do that. #%# 3. Assemble all the information you will need to re-create #%# the key later in `jwe`: #%# #%# First create a skeleton that declares the pin, and creates a store. jwe='{"protected":{"clevis":{"pin":"@pin@","@pin@":{}}}}' #%# Then populate that store. Possibly you'll just have to pass #%# the parameters. Leave out those you will not need for decryption. #%# NB: The long form of the `-U` parameter of `jose fmt` is "--unwind" jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get @pin@ --quote "$@mand1@" --set @mand1@ -UUUU --output=-)" jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get @pin@ --quote "$@opt1@" --set @opt1@ -UUUU --output=-)" #%# Almost there! #%# Forward everything to `jose jwe enc` which does the encryption job - #%# including reading the plaintext from stdin which gets replicated #%# using `cat`. ( printf '%s' "$jwe$jwk" ; cat ) | exec jose jwe enc --input=- --key=- --detached=- --compact #%# Anything that follows is executed only if jose failed. #%# #!# When using mktemp or the like, cleaning up has to be done #!# manually. See clevis-{en,de}crypt-tpm2 for an example. latchset-clevis-6df9b69/src/pins/template/clevis-encrypt-@pin@.1.adoc000066400000000000000000000017121520110721700255310ustar00rootroot00000000000000#%# Align the equal signs after substitution CLEVIS-ENCRYPT-@PIN@(1) ======================= :doctype: manpage == NAME clevis-encrypt-@pin@ - Encrypts using a @@ policy == SYNOPSIS *clevis encrypt @pin@* CONFIG < PT > JWE == OVERVIEW The *clevis encrypt @pin@* command encrypts using a @@ policy. Its only argument is the JSON configuration object. #%# And so on ... Encrypting data using the @pin@ pin works like this: $ clevis encrypt @pin@ '{"@mand1@":"@@","@mand2":"@@"}' < PT > JWE To decrypt the data, just pass it to the *clevis decrypt* command: $ clevis decrypt < JWE > PT == CONFIG This command uses the following configuration properties: #%# Keep this in sync with the short help in clevis-encrypt-@pin@ * *@mand1@* (string) : @@ (REQUIRED) * *@mand2@* (string) : @@ (REQUIRED) * *@opt1@* (string) : @@ (default: @@) == BUGS #%# List any flaws and gotchas here. == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/pins/template/dracut.module-setup.sh.in000077500000000000000000000015371520110721700254640ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) @year@ @name@ # Author: @name@ <@email@> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # check() { require_binaries clevis-decrypt-@pin@ || return 1 } depends() { echo clevis return 0 } install() { inst clevis-decrypt-@pin@ } latchset-clevis-6df9b69/src/pins/template/initramfs.in000077500000000000000000000026411520110721700231400ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) @year@ @name@ # Author: @name@ <@email@> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # case $1 in prereqs) exit 0 ;; esac . @initramfstoolsdir@/hook-functions die() { code="$1" msg="$2" echo " (ERROR): $msg" >&2 exit $1 } #%# Possibly you need to find additional binaries... #%# #%# find_binary() { #%# bin_name="$1" #%# resolved=$(command -v ${bin_name}) #%# [ -z "$resolved" ] && die 1 "Unable to find ${bin_name}" #%# echo "$resolved" #%# } if [ -x @bindir@/clevis-decrypt-@pin@ ]; then copy_exec @bindir@/clevis-decrypt-@pin@ || die 1 "Unable to copy @bindir@/clevis-decrypt-@pin@ to initrd image" #%# ...and install them #%# #%# curl_bin=$(find_binary "curl") #%# copy_exec "${curl_bin}" || die 2 "Unable to copy ${curl_bin} to initrd image" fi latchset-clevis-6df9b69/src/pins/template/meson.build000066400000000000000000000040721520110721700227530ustar00rootroot00000000000000#%# This controls build and test #%# #%# Feel free to peek into other tests to get an idea what can be done. #%# #%# Possibly you'll need some extra programs, probe for them. #%# curl is just an example curl = find_program('curl', required: false) #%# dracut and initramfs_tools are needed for early userland dracut = dependency('dracut', required: false) initramfs_tools = find_program('update-initramfs', required: false) if curl.found() #%# BUild the programs bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-@pin@') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-@pin@') #%# and the manpage mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-@pin@.1') #%# Set up a test environment env = environment() env.append('PATH', join_paths(meson.source_root(), 'src'), meson.current_source_dir(), '/usr/libexec', libexecdir, separator: ':' ) #%# Run the test. test('pin-@pin@', find_program('./pin-@pin@'), env: env) else warning('Will not install @pin@ pin due to missing dependencies!') endif #%# dracut support if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/50' + meson.project_name() + '-pin-@pin@' #?# In general, substituation is not needed but it's better to #?# stay flexible. configure_file( input: 'dracut.module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, configuration: data, ) else warning('Will not install dracut module clevis-pin-@pin@ due to missing dependencies!') endif #%# initramfs support if initramfs_tools.found() initramfstools_dir = '/usr/share/initramfs-tools' initramfs_hooks_dir = '/usr/share/initramfs-tools/hooks' initramfs_data = configuration_data() initramfs_data.merge_from(data) initramfs_data.set('initramfstoolsdir', initramfstools_dir) configure_file( input: 'initramfs.in', output: 'clevis-pin-@pin@', install_dir: initramfs_hooks_dir, configuration: initramfs_data, ) else warning('Will not install initramfs module clevis-pin-@pin@ due to missing dependencies!') endif latchset-clevis-6df9b69/src/pins/template/pin-@pin@000077500000000000000000000025701520110721700223120ustar00rootroot00000000000000#%# Of course you should provide some test #%# #!/bin/sh #%# Maximum verbosity is desired set -ex # Copyright (c) @year@ @name@ # Author: @name@ <@email@> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # #%# A simple test will encrypt a text, decrypt, and compare. #%# Some work might be necessary to set up a test bed. #%# #%# Feel free to peek into other tests to get an idea what can be done. #%# #!# Your test might require root privileges but your script might #!# not have them. So probe, and exit mit the magical code 77 to skip #!# that test instead of failing. #%# Create your configuration cfg="$(printf '{"@mand1@":"%s","@mand2@":"%s"}' "@@" "@@")" #%# The input inp='hi' enc="$(printf '%s' "$inp" | clevis encrypt @pin@ "$cfg")" dec="$(printf '%s' "$enc" | clevis decrypt)" test "$dec" = "$inp" latchset-clevis-6df9b69/src/pins/tpm1/000077500000000000000000000000001520110721700176545ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/tpm1/clevis-decrypt-tpm1000077500000000000000000000042571520110721700234260ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis decrypt tpm1 < JWE > PLAINTEXT" echo exit 2 fi tpm_version_bin="$(command -v tpm_version || echo /usr/sbin/tpm_version)" if ! "$tpm_version_bin" >/dev/null 2>&1; then # The tpm_version outputs garbage to stdout on success, so let the # tpm_version output the error again cleanly now echo "The tpm1 pin requires tcsd daemon (trousers) running:" >&2 if [ -x "$tpm_version_bin" ]; then ( "$tpm_version_bin" 2>&1 | tr '\0' ' ' ) >&2 else echo Cannot check, tpm_version from tpm-tools not found >&2 fi exit 1 fi read -r -d . hdr if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then echo "Error decoding JWE protected header!" >&2 exit 1 fi if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tpm1" ]; then echo "JWE pin mismatch!" >&2 exit 1 fi if ! jwk_b64="$(jose fmt -j- -Og clevis -g tpm1 -g jwk -Su- <<< "$jhd")"; then echo "JWE missing required 'jwk' header parameter!" >&2 exit 1 fi if ! jwk_sealed="$(jose b64 dec -i- <<< "$jwk_b64")"; then echo "Decoding jwk from Base64 failed!" >&2 exit 1 fi if ! jwk="$(tpm_unsealdata -i /dev/stdin -z <<< "$jwk_sealed")"; then echo "Unable to unseal jwk!" >&2 exit 1 fi exec jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat) latchset-clevis-6df9b69/src/pins/tpm1/clevis-encrypt-tpm1000077500000000000000000000107511520110721700234340ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # SUMMARY="Encrypts using a TPM1.2 chip binding policy" if [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis encrypt tpm1 CONFIG < PLAINTEXT > JWE" echo echo "$SUMMARY" echo echo "This command uses the following configuration properties:" echo echo " pcr_ids: PCR list used for policy. If not present, no policy is used" echo exit 2 fi validate_pcrs() { local _pcr_bank="${1}" local _pcrs="${2}" local _pcr [ -z "${_pcr_bank}" ] && return 1 [ -z "${_pcrs}" ] && return 0 if [ -z "$TSS_TCSD_PORT" ]; then for _pcr in ${_pcrs//,/ }; do [ -f "/sys/class/tpm/tpm0/pcr-${_pcr_bank}/${_pcr}" ] || return 1 done else local pcr_args="${_pcrs:+-p}${_pcrs//,/ -p}" [ "${_pcr_bank}" = "sha1" ] || return 1 tpm_sealdata ${pcr_args} -z /dev/null 2>&1 || return 1 fi return 0 } tpm_version_bin="$(command -v tpm_version || echo /usr/sbin/tpm_version)" if ! "$tpm_version_bin" >/dev/null 2>&1; then # The tpm_version outputs garbage to stdout on success, so let the # tpm_version output the error again cleanly now echo "The tpm1 pin requires tcsd daemon (trousers) running:" >&2 if [ -x "$tpm_version_bin" ]; then ( "$tpm_version_bin" 2>&1 | tr '\0' ' ' ) >&2 else echo Cannot check, tpm_version from tpm-tools not found >&2 fi exit 1 fi if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then echo "Configuration is malformed!" >&2 exit 1 fi # TPM1.1 and TPM1.2 has only sha1 pcr_bank="sha1" # Trim the spaces from the config, so that we will not have issues parsing # the PCR IDs. pcr_cfg=${cfg//[[:space:]]/} # Issue #103: We support passing pcr_ids using both a single string, as in # "1,3", as well as an actual JSON array, such as ["1","3"]. Let's handle both # cases here. if jose fmt -j- -Og pcr_ids 2>/dev/null <<< "${pcr_cfg}" \ && ! pcr_ids="$(jose fmt -j- -Og pcr_ids -u- 2>/dev/null \ <<< "${pcr_cfg}")"; then # We failed to parse a string, so let's try to parse a JSON array instead. if jose fmt -j- -Og pcr_ids -A 2>/dev/null <<< "${pcr_cfg}"; then # OK, it is an array, so let's get the items and form a string. pcr_ids= for pcr in $(jose fmt -j- -Og pcr_ids -Af- <<< "${pcr_cfg}" \ | tr -d '"'); do pcr_ids=$(printf '%s,%s' "${pcr_ids}" "${pcr}") done # Now let's remove the leading comma. pcr_ids=${pcr_ids/#,/} else # Not to add a policy that was not intended, in this case, no policy # at all, let's report the issue and exit. echo "Parsing the requested PCRs failed!" >&2 exit 1 fi fi if ! validate_pcrs "${pcr_bank}" "${pcr_ids}"; then echo "Unable to validate combination of PCR bank '${pcr_bank}' and PCR IDs '${pcr_ids}'." >&2 exit 1 fi if ! jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')"; then echo "Generating a jwk failed!" >&2 exit 1 fi pcr_args="${pcr_ids:+-p}${pcr_ids//,/ -p}" if ! jwk_sealed=$(tpm_sealdata -i /dev/stdin -o /dev/stdout ${pcr_args} -z <<< "$jwk"); then echo "Unable to seal jwk" >&2 exit 1 fi if ! jwk_b64="$(jose b64 enc -I- <<< "$jwk_sealed")"; then echo "Encoding sealed jwk in Base64 failed!" >&2 exit 1 fi jwe='{"protected":{"clevis":{"pin":"tpm1","tpm1":{}}}}' if [ -n "$pcr_ids" ]; then jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm1 -q "$pcr_ids" -s pcr_ids -UUUUo-)" fi jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm1 -q "$jwk_b64" -s jwk -UUUUo-)" exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) latchset-clevis-6df9b69/src/pins/tpm1/clevis-encrypt-tpm1.1.adoc000066400000000000000000000303551520110721700244770ustar00rootroot00000000000000CLEVIS-ENCRYPT-TPM1(1) ====================== :doctype: manpage == NAME clevis-encrypt-tpm1 - Encrypts using a TPM 1.2 chip binding policy == SYNOPSIS *clevis encrypt tpm1* CONFIG < PT > JWE == OVERVIEW The *clevis encrypt tpm1* command encrypts using a Trusted Platform Module{nbsp}1.2 (TPM{nbsp}1.2) chip. It might work with Trusted Platform Module{nbsp}1.1 too, but it is untested. The tpm1 pin does not support Trusted Platform Module{nbsp}2.0 and higher, please use the tpm2 pin instead. The only argument is the JSON configuration object. When using the tpm1 pin, we create a new, cryptographically-strong, random key. This key is encrypted using the TPM{nbsp}1.2 chip. Then at decryption time, the key is decrypted again using the TPM{nbsp}1.2 chip. $ clevis encrypt tpm1 '{}' < PT > JWE To decrypt the data, simply provide the ciphertext (JWE): $ clevis decrypt < JWE > PT Note that like other pins no configuration is used for decryption, this is due to clevis storing the sealed encryption key in the JWE so clevis can fetch that key from there and unseal it by using the TPM{nbsp}1.2 chip. The pin also supports sealing data to a Platform Configuration Registers (PCR) state. That way the data can only be unsealed if the PCRs hash values match the values used when sealing. For example, to seal the data to the PCR with indexes 0, 4 and 7, use: $ clevis encrypt tpm1 '{"pcr_ids":"0,4,7"}' < PT > JWE The BIOS, boot loader, Grub and Linux kernel incrementally add hashes of the various system states to the registers. The added hashes represent state of different components such as the BIOS (PCR{nbsp}0 and{nbsp}1), option ROMs (PCR{nbsp}2 and{nbsp}3), boot loader (PCR{nbsp}4), EFI Secure Boot state (PCR{nbsp}0 and{nbsp}7), etc. The requirement for exact matching of values ensures that the TPM{nbsp}1.2 chip unseals the data only when the system state measured by the selected registers has not changed. Although the usage of Platform Configuration Registers is standardized, the BIOS implementation might differ slightly, so always test how the particular register value changes when the system is updated, most notably when the Grub boot loaded or Linux kernel is updated. The PCR values can be checked with: $ cat /sys/class/tpm/tpm0/pcrs == CONFIG This command uses the following configuration properties: * *pcr_ids* (string) : Comma separated list of PCR used for policy. If not present, no policy is used == Limitations To avoid prompting for a password during unlocking, the encryption and decryption processes require that the well-known Storage Root Key (SRK) be configured when taking ownership of the TPM{nbsp}1.2 chip. This means you must have either run the `tpm_takeownership` command $ tpm_takeownership --srk-well-known during setup or executed `tpm_changeownerauth` command $ tpm_changeownerauth --srk --set-well-known to configure it. Note that a _well-known_ key is not the same as an empty key. [IMPORTANT] -- If you have changed the SRK to a _well-known_ key, remember to run `update-initramfs` command (on Debian-like systems) $ update-initramfs -u or `dracut` command (on Fedora-like systems) $ dracut -f afterward to recreate initramfs image, because `/var/lib/tpm` is included in the image. This applies to `initramfs-tools` and Dracut in _host-only_ mode. In Dracut's _default_ mode, `/var/lib/tpm` is already configured to allow access to the TPM{nbsp}1.2 chip using a _well-known_ SRK. -- == Unlocking with a Separately-Encrypted `/var` Volume with TPM1 PIN Because TPM1 PIN relies on the `tcsd` daemon from the Trousers project to access the TPM{nbsp}1.2 chip, the daemon must start early in the boot process to unlock the root filesystem automatically. The `/var/lib/tpm` directory contains runtime data for `tcsd` and must be available before the daemon starts. A minimal copy of the required `/var` files is included in the initramfs image prepared by Clevis, so the daemon _should_ be able to start during the _initrd bootup_ phase if everything is configured correctly. After switching to the real root (`/`) filesystem, the _System Manager bootup_ phase starts and `/var` is mounted from the actual target. At this point, Clevis cannot unlock it (`tcsd` would need `/var` to unlock `/var`), so it must already be unlocked. Refer to the instructions below for `initramfs-tools` and Dracut. If the `/var` volume is part of the main LVM volume group (the same as the root `/` filesystem) and is protected by the same LUKS volume, no special configuration is needed. However, if the `/var` volume is encrypted separately (i.e., it uses a different LUKS volume, regardless of whether it has the same password), follow the instructions below to enable automatic unlocking with Clevis. === `initramfs-tools` Initrd Bootup `initramfs-tools` unlocks the root and swap filesystems by copying the corresponding option lines from `/etc/crypttab` into the initramfs. To ensure that `/var` volume options are also included, add the `initramfs` option on Debian-like system to the relevant line in `/etc/crypttab` as shown in the following example: .`/etc/crypttab` ---- … luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,initramfs … ---- This line corresponds to the `crypto_LUKS` volume used by the `/var` volume, as shown by the `lsblk -fp` command: .LVM on LUKS ---- … └─/dev/vda3 crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 └─/dev/mapper/separate-var xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 753,3M 22% /var ---- The above example uses an LVM-on-LUKS encryption scheme, but the same applies to LUKS-on-LVM — just check the `crypto_LUKS` volume UUID. .LUKS on LVM ---- … └─/dev/vda3 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 └─/dev/mapper/separate-var crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 781,5M 19% /var ---- IMPORTANT: After modifying `/etc/crypttab`, you must run `update-initramfs -u` (on Debian-like systems). === Dracut Initrd Bootup Dracut automatically unlocks the root and swap filesystems. The operating system installer ensures that the kernel command line (in `/etc/default/grub`) contains the necessary parameters for Dracut and Systemd. Dracut considers both the kernel command line and the lines copied from `/etc/crypttab` for unlocking. By default, the root and swap lines from `/etc/crypttab` are copied into the initramfs. To ensure the `/var` volume is also unlocked, you must ensure that its options are included and referenced by the kernel command line (as described below). [CAUTION] -- Changing the following options can render the system unbootable, potentially requiring a rescue DVD and expert knowledge to recover. Make a full backup before proceeding! For recovery, you may find these commands helpful: * `cryptsetup open /dev/ ` * `mount /dev/mapper/ /` * `lvm vgscan` * `lvm lvdisplay -o lv_full_name,lv_dm_path` -- To ensure that the `/var` options are included, add either the `x-initrd.attach` option to the corresponding line in /etc/crypttab (to unlock the `/var` volume) or the `x-initrd.mount` option to the corresponding line in `/etc/fstab` (to unlock _and_ mount the `/var` volume). Using both is equivalent to `x-initrd.mount`. .`/etc/crypttab` ---- … luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,x-initrd.attach … ---- .`/etc/fstab` ---- … UUID=767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 /var xfs defaults,x-systemd.device-timeout=0,x-initrd.mount 0 0 … ---- Refer to the `initramfs-tools` section for instructions on finding the correct `/etc/crypttab` line with `lsblk -fp`. The `/etc/fstab` entry is matched by the UUID of the filesystem (see the line with `/var` in the `lsblk -fp` output). IMPORTANT: After changing `/etc/crypttab` and/or `/etc/fstab`, run `dracut -f`. NOTE: If you use `x-initrd.mount`, the volume is mounted during the _initrd bootup_ phase. However, this is not strictly necessary. Systemd's startup order ensures that `/var` is mounted before `tcsd` starts in the _System Manager bootup_ phase, so using `x-initrd.attach` alone is sufficient. Next, ensure that the volumes are found and unlocked. Two kernel command line parameters in `/etc/default/grub` affect this: * `rd.luks.uuid` – Either remove all values or add the UUID of the `crypto_LUKS` volume (optionally prefixed by `luks-`). If this option is present (it can appear multiple times), only the specified volumes are initialized from `/etc/crypttab`. If it is missing, all lines from `/etc/crypttab` are considered. * `rd.lvm.lv` – Either remove all values or add the full LVM volume name for `/var`. If this option is present (it can appear multiple times), only the listed logical volumes are initialized. If it is missing, Dracut automatically detects LVM volumes during boot. NOTE: The `rd.lvm.lv` option matters only in the LUKS-on-LVM case, because the `crypto_LUKS` volume is accessible only after the LVM logical volume is activated. If `rd.lvm.lv` is missing, Dracut will detect LVM volumes automatically. If it is present, make sure to include the `/var` full volume name. For more information, see manual pages of `dracut.cmdline` and `systemd-cryptsetup-generator`. NOTE: Dracut internally uses the same Systemd options, so the same logic applies even if Systemd is not present in the Dracut initrd environment. To find the correct `rd.lvm.lv` value, run: $ lvs -o lv_full_name,lv_dm_path This shows the logical volume's full name and Device Mapper path, which also appears in the `lsblk -fp` output. For example, if it shows `separate/var` (see example below), the `rd.lvm.lv` value would be `rd.lvm.lv=separate/var`: ---- LV DMPath … separate/var /dev/mapper/separate-var … ---- Example of a kernel command line in `/etc/default/grub` with all options present: .`/etc/default/grub` ---- GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/root rd.luks.uuid=luks-21a9c1b8-c202-4985-809a-aba2d6fdab01 rd.lvm.lv=separate/var rd.luks.uuid=luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 quiet" ---- Example of a kernel command line in `/etc/default/grub` when relying on the configuration copied from `/etc/crypttab` and Dracut’s automatic LVM detection: .`/etc/default/grub` ---- GRUB_CMDLINE_LINUX="quiet" ---- IMPORTANT: After changing the kernel command line, update the Grub configuration with `update-grub2` (on Debian-like systems) or `grub2-mkconfig -o /etc/grub2.cfg` (on Fedora-like systems). == Threat model The Clevis security model relies in the fact that an attacker will not be able to access both the encrypted data and the decryption key. For most Clevis pins, the decryption key is not locally stored, so the decryption policy is only satisfied if the decryption key can be remotely accessed. It could for example be stored in a remote server or in a hardware authentication device that has to be plugged into the machine. The tpm1 pin is different in this regard, since a key is wrapped by a{nbsp}TPM{nbsp}1.2 chip that is always present in the machine. This does not mean that there are not use cases for this pin, but it is important to understand the fact that an attacker that has access to both the encrypted data and the local TPM{nbsp}1.2 chip will be able to decrypt the data. The use of specific Platform Configuration Registers along with Secure Boot limits the attack surface because an attacker must reproduce the exact register values that are present during sealing before the TPM{nbsp}1.2 chip will allow the encryption key to be unsealed. Careful selection of registers ensures that any change to the boot process will prevent the TPM{nbsp}1.2 from unsealing the encryption key. == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)], link:man:dracut.cmdline(7)[*dracut.cmdline*(7)], link:man:systemd-cryptsetup-generator(8)[*systemd-cryptsetup-generator*(8)] latchset-clevis-6df9b69/src/pins/tpm1/clevis-tpm1-tcsd-preload.c000066400000000000000000000035401520110721700245450ustar00rootroot00000000000000/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright (c) 2024 Oldřich Jedlička * * Author: Oldřich Jedlička * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #include #include #include #include #define TCSD_NO_PRIVILEGE_DROP_ENV "TCSD_NO_PRIVILEGE_DROP" static int no_privilege_drop(void) { char *no_privilege_drop_env = getenv(TCSD_NO_PRIVILEGE_DROP_ENV); return (no_privilege_drop_env != NULL && no_privilege_drop_env[0] != '\0' && no_privilege_drop_env[0] != '0'); } int setuid(uid_t uid) { static int (*real_setuid)(uid_t) = NULL; if (no_privilege_drop()) { return 0; } else { if (!real_setuid) { real_setuid = dlsym(RTLD_NEXT, "setuid"); } return real_setuid(uid); } } int setgid(gid_t gid) { static int (*real_setgid)(gid_t) = NULL; if (no_privilege_drop()) { return 0; } else { if (!real_setgid) { real_setgid = dlsym(RTLD_NEXT, "setgid"); } return real_setgid(gid); } } static void __attribute ((constructor)) set_line_buffering(void) { setvbuf(stdout, NULL, _IOLBF, 0); } latchset-clevis-6df9b69/src/pins/tpm1/meson.build000066400000000000000000000013141520110721700220150ustar00rootroot00000000000000cmds = ['tpm_sealdata', 'tpm_unsealdata'] all = true foreach cmd : cmds all = all and find_program(cmd, required: get_option('tpm1').enabled()).found() endforeach if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm1') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1.1') libdl_dep = dependency('dl', required: true) libclevis_tpm1_tcsd_preload = shared_library('clevis-tpm1-tcsd-preload', 'clevis-tpm1-tcsd-preload.c', dependencies: libdl_dep, install_dir: libdir, install: true) subdir('tests') else warning('Will not install tpm1 pin due to missing dependencies!') endif latchset-clevis-6df9b69/src/pins/tpm1/tests/000077500000000000000000000000001520110721700210165ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/tpm1/tests/meson.build000066400000000000000000000050721520110721700231640ustar00rootroot00000000000000# Tests env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'pins', 'tpm1'), join_paths(meson.source_root(), 'src', 'pins', 'tpm1', 'tests'), join_paths(meson.build_root(), 'src'), join_paths(meson.build_root(), 'src', 'luks', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'tpm1'), join_paths(meson.build_root(), 'src', 'pins', 'tpm1', 'tests'), separator: ':' ) tpm1_data = configuration_data() tpm1_data.merge_from(data) tpm_version = find_program('tpm_version', '/usr/sbin/tpm_version', required: false) tpm_sealdata = find_program('tpm_sealdata', '/usr/sbin/tpm_sealdata', required: false) tpm_unsealdata = find_program('tpm_unsealdata', '/usr/sbin/tpm_unsealdata', required: false) tpm_takeownership = find_program('tpm_takeownership', '/usr/sbin/tpm_takeownership', required: false) tcsd = find_program('tcsd', '/usr/sbin/tcsd', required: false) swtpm = find_program('swtpm', '/usr/bin/swtpm', required: false) swtpm_setup = find_program('swtpm_setup', '/usr/bin/swtpm_setup', required: false) tpm1_data.set('TPM_VERSION_BIN', tpm_version.found() ? tpm_version.path() : '') tpm1_data.set('TPM_SEALDATA_BIN', tpm_sealdata.found() ? tpm_sealdata.path() : '') tpm1_data.set('TPM_UNSEALDATA_BIN', tpm_unsealdata.found() ? tpm_unsealdata.path() : '') tpm1_data.set('TPM_TAKEOWNERSHIP_BIN', tpm_takeownership.found() ? tpm_takeownership.path() : '') tpm1_data.set('TCSD_BIN', tcsd.found() ? tcsd.path() : '') tpm1_data.set('SWTPM_BIN', swtpm.found() ? swtpm.path() : '') tpm1_data.set('SWTPM_SETUP_BIN', swtpm_setup.found() ? swtpm_setup.path() : '') tpm1_data.set('LIBCLEVIS_TPM1_TCSD_PRELOAD', libclevis_tpm1_tcsd_preload.path()) configure_file( input: 'tpm1-common-test-functions.in', output: 'tpm1-common-test-functions', configuration: tpm1_data, ) patch_data = configuration_data() cc = meson.get_compiler('c') has___xstat = cc.has_header_symbol('sys/stat.h', '__xstat', args: ['-D_GNU_SOURCE']) patch_data.set('HAS___XSTAT', has___xstat) tcsd_patch_src = configure_file( input: 'tcsd-patch.c.in', output: 'tcsd-patch.c', configuration: patch_data ) libdl_dep = dependency('dl', required: true) tcsd_patch = shared_library('tcsd-patch', tcsd_patch_src, dependencies: libdl_dep, install: false) tcsd_patch_path = tcsd_patch.full_path() env.prepend('TCSD_PATCH_LIB', tcsd_patch_path) test('pin-tpm1-hw', find_program('pin-tpm1-hw'), env: env, timeout: 120) test('pin-tpm1-sw', find_program('pin-tpm1-sw'), env: env, timeout: 120) latchset-clevis-6df9b69/src/pins/tpm1/tests/pin-tpm1-hw000077500000000000000000000017301520110721700230260ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tpm1-common-test-functions tpm1_hw_check_preconditions tpm1_working || skip_test "Skipping TPM1 test, TPM is not correctly setup" . pin-tpm1-tests latchset-clevis-6df9b69/src/pins/tpm1/tests/pin-tpm1-sw000077500000000000000000000017721520110721700230470ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tpm1-common-test-functions tpm1_sw_check_preconditions tpm1_start_emulation tpm1_working || skip_test "Unable to setup software emulation of TPM 1, skipping tests" . pin-tpm1-tests latchset-clevis-6df9b69/src/pins/tpm1/tests/pin-tpm1-tests000077500000000000000000000067011520110721700235550ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") validate_pcrs() { local _pcrs="${1}" [ -z "${_pcrs}" ] && return 0 local pcr_args="${pcr_ids:+-p}${pcr_ids//,/ -p}" tpm_sealdata ${pcr_args} -z /dev/null 2>&1 } decode_jwe() { local jwe="${1}" local coded if ! coded=$(jose jwe fmt -i- <<< "${jwe}"); then return 1 fi coded=$(jose fmt -j- -g protected -u- <<< "${coded}" | tr -d '"') jose b64 dec -i- <<< "${coded}" } test_pcr_ids() { local orig="${1}" local cfg="${2}" local expected_pcr_ids="${3}" local enc if ! enc=$(echo -n "${orig}" | clevis encrypt tpm1 "${cfg}"); then echo "${TEST}: encrypt failed for cfg: ${cfg}" >&2 return 1 fi local pcr_ids pcr_ids=$(decode_jwe "${enc}" \ | jose fmt -j- -Og clevis -Og tpm1 -Og pcr_ids -u- 2>/dev/null) local dec dec="$(echo "${enc}" | clevis decrypt)" if [ "${orig}" != "${dec}" ]; then echo "${TEST}: decoded text (${dec}) does not match original one (${orig})" >&2 return 1 fi if [ "${pcr_ids}" != "${expected_pcr_ids}" ]; then echo "${TEST}: pcr_ids (${pcr_ids}) do not match the expected (${expected_pcr_ids}) result." >&2 return 1 fi } test_enc_dec() { local cfg="${1}" output=$(echo Working | clevis encrypt tpm1 "${cfg}" | clevis decrypt) if [ "$output" != "Working" ]; then echo "Output after decrypting doesn't match: ${output} != 'Working'" return 1 fi } test_enc_dec '{}' || exit 1 test_pcr_ids "Hi" '{}' "" || exit 1 test_pcr_ids "Hello" '{}' "" || exit 1 test_pcr_ids "Hi" '{ }' "" || exit 1 # Issue #103: now let's try a few different configs with both strings and # arrays and check if we get the expected pcr_ids. # Let's first make sure this would be a valid configuration. if validate_pcrs "4,16"; then test_pcr_ids "Hi" '{"pcr_ids": "16"}' "16" || exit 1 test_pcr_ids "Hello" '{"pcr_ids": "16"}' "16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": ["16"]}' "16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": "4, 16"}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": "4,16"}' "4,16" || exit 1 test_pcr_ids "Hello" '{"pcr_ids": "4,16"}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": ["4,16"]}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": [4,16]}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": [4, 16]}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": ["4","16"]}' "4,16" || exit 1 ! test_pcr_ids "Hi" '{"pcr_ids": ["4","16"]}' "foo bar" || exit 1 else echo "Skipping tests related to issue#103 because the combination of pcr_bank and PCRs is invalid" >&2 fi latchset-clevis-6df9b69/src/pins/tpm1/tests/tcsd-patch.c.in000066400000000000000000000043071520110721700236250ustar00rootroot00000000000000#define _GNU_SOURCE #undef _FILE_OFFSET_BITS #include #include #include #include #include #include #include #include #include #define TCSD_CONF "tcsd.conf" #mesondefine HAS___XSTAT static void stat_process(const char *statfunc, const char *pathname, struct stat *statbuf) { size_t path_len = strlen(pathname); size_t tcsd_len = strlen(TCSD_CONF); if ((path_len >= tcsd_len) && (strcmp(pathname + path_len - tcsd_len, TCSD_CONF) == 0) && ((path_len == tcsd_len) || (pathname[path_len - tcsd_len - 1] == '/'))) { if ((statbuf->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == (S_IRUSR | S_IWUSR)) { // Compatibility with old tcsd // Get the UID for the user 'tss' struct passwd *pw = getpwnam("tss"); if (pw != NULL) { statbuf->st_uid = pw->pw_uid; fprintf(stderr, "%s(%s) : simulate uid\n", statfunc, pathname); } } else { statbuf->st_uid = 0; } // Get the GID for the group 'tss' struct group *grp = getgrnam("tss"); if (grp != NULL) { statbuf->st_gid = grp->gr_gid; fprintf(stderr, "%s(%s) : simulate gid\n", statfunc, pathname); } } else { fprintf(stderr, "%s(%s) : passthrough\n", statfunc, pathname); } } int stat(const char *pathname, struct stat *statbuf) { static int (*real_stat)(const char *, struct stat *) = NULL; if (!real_stat) { real_stat = dlsym(RTLD_NEXT, "stat"); } // Call the original stat function int result = real_stat(pathname, statbuf); if (result == 0) { stat_process("stat", pathname, statbuf); } return result; } #ifdef HAS___XSTAT int __xstat(int ver, const char *pathname, struct stat *statbuf) { static int (*real___xstat)(int, const char *, struct stat *) = NULL; if (!real___xstat) { real___xstat = dlsym(RTLD_NEXT, "__xstat"); } int result = real___xstat(ver, pathname, statbuf); if (result == 0) { stat_process("__xstat", pathname, statbuf); } return result; } #endif latchset-clevis-6df9b69/src/pins/tpm1/tests/tpm1-common-test-functions.in000066400000000000000000000127441520110721700265100ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2024 Oldřich Jedlička # # Author: Oldřich Jedlička # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tests-common-functions TPM_VERSION_BIN="@TPM_VERSION_BIN@" TPM_SEALDATA_BIN="@TPM_SEALDATA_BIN@" TPM_UNSEALDATA_BIN="@TPM_UNSEALDATA_BIN@" TPM_TAKEOWNERSHIP_BIN="@TPM_TAKEOWNERSHIP_BIN@" TCSD_BIN="@TCSD_BIN@" SWTPM_BIN="@SWTPM_BIN@" SWTPM_SETUP_BIN="@SWTPM_SETUP_BIN@" LIBCLEVIS_TPM1_TCSD_PRELOAD="@LIBCLEVIS_TPM1_TCSD_PRELOAD@" SWTPM_SOCKET_PID= TCSD_PID= export -n TCSD_UN_SOCKET_DEVICE_PATH export -n TSS_TCSD_PORT export -n TSS_TCSD_HOST function on_exit() { popd || error "Unable to change directory" if [ ! -d "$TESTDIR" ] || ! rm -rf "$TESTDIR"; then echo "Delete temporary files failed!" >&2 echo "You need to clean up: $TESTDIR" >&2 exit 1 fi # Cleanup sw emulation [ -n "$TCSD_PID" ] && kill $TCSD_PID >/dev/null 2>&1 if [ -n "$SWTPM_SOCKET_PID" ]; then kill $SWTPM_SOCKET_PID >/dev/null 2>&1 sleep .5 # swtpm does not always terminate gracefully, so kill it kill -9 $SWTPM_SOCKET_PID >/dev/null 2>&1 fi } if ! TESTDIR="$(mktemp -d)"; then echo "Creating a temporary dir for TPM files failed!" >&2 exit 1 fi trap 'on_exit' EXIT pushd "$TESTDIR" || error "Unable to change directory" tpm1_hw_check_preconditions() { [ -x "${TPM_VERSION_BIN}" ] || skip_test "Skipping TPM1 test, tpm_version from tpm-tools not found" [ -x "${TPM_SEALDATA_BIN}" ] || skip_test "Skipping TPM1 test, tpm_sealdata from tpm-tools not found" [ -x "${TPM_UNSEALDATA_BIN}" ] || skip_test "Skipping TPM1 test, tpm_unsealdata from tpm-tools not found" getent group "tss" >/dev/null 2>&1 || skip_test "Skipping TPM1 test, group tss not found" } tpm1_sw_check_preconditions() { tpm1_hw_check_preconditions [ -x "${TCSD_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, tcsd not found" [ -x "${SWTPM_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, swtpm not found" [ -x "${SWTPM_SETUP_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, swtpm_setup not found" [ -f "${TCSD_PATCH_LIB}" ] || skip_test "Skipping TPM1 test with software emulation, libtcsd_patch.so not found" if ! "${SWTPM_BIN}" socket --print-capabilities | jq -e '(.version | test("^0\\.[0-6](\\..*)?$")) or (.features | index("tpm-1.2"))' >/dev/null 2>&1; then skip_test "Skipping TPM1 test with software emulation, no support for TPM 1.2 in swtpm" fi } tpm1_working() { if ! "$TPM_VERSION_BIN" >/dev/null 2>&1; then # The tpm_version outputs garbage to stdout on success, so let the # tpm_version output the error again cleanly now ( "$TPM_VERSION_BIN" 2>&1 | tr '\0' ' ' ) 2>&1 | sed -e 's/^/tpm_version: /' >&2 return 1 fi echo test | "$TPM_SEALDATA_BIN" -z | "$TPM_UNSEALDATA_BIN" -z -i /dev/stdin >/dev/null 2>&1 \ && return 0 if [ ${PIPESTATUS[0]} -gt 0 ]; then "$TPM_SEALDATA_BIN" -z < /dev/null >/dev/null 2>&1 | sed -e 's/^/tpm_sealdata: /' >&2 else "$TPM_SEALDATA_BIN" -z < /dev/null | "$TPM_UNSEALDATA_BIN" -z -i /dev/stdin 2>&1 >/dev/null | sed -e 's/^/tpm_unsealdata: /' >&2 fi return 1 } tpm1_start_emulation() { local socket_wait echo "Starting TPM 1 emulation" >&2 # Setup TPM 1 data "${SWTPM_SETUP_BIN}" --tpm-state "$TESTDIR" --createek --display >&2 || error "Unable to setup TPM 1 emulation" # Start emulation over socket "${SWTPM_BIN}" socket --tpmstate dir="$TESTDIR" --ctrl type=unixio,path="$TESTDIR"/swtpm.sock.ctrl --server type=unixio,path="$TESTDIR"/swtpm.sock --flags startup-clear >&2 & SWTPM_SOCKET_PID=$! socket_wait=1 while [ $socket_wait -le 100 ]; do [ -S "$TESTDIR"/swtpm.sock ] && break socket_wait=$((socket_wait + 1)) sleep 0.1 done [ "$socket_wait" -gt 100 ] && error "Unable to start TPM 1 emulation" # Run tcds cat < "$TESTDIR"/tcsd.conf port = 0 system_ps_file = $TESTDIR/system.data EOM # Deduce correct permissions, or fallback to latest tcsd expectations if [ -f "@sysconfdir@/tcsd.conf" ]; then chmod "$(stat -c "0%a" "@sysconfdir@/tcsd.conf")" "$TESTDIR"/tcsd.conf else chmod 0640 "$TESTDIR"/tcsd.conf fi TCSD_NO_PRIVILEGE_DROP=1 \ LD_PRELOAD="$LIBCLEVIS_TPM1_TCSD_PRELOAD $TCSD_PATCH_LIB" \ TCSD_UN_SOCKET_DEVICE_PATH="$TESTDIR"/swtpm.sock \ "${TCSD_BIN}" -f -e -c "$TESTDIR"/tcsd.conf >&2 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # First try to use the new version of the PIN implementation if command -v clevis-pin-tpm2 >/dev/null; then exec clevis-pin-tpm2 decrypt $@ fi # The owner hierarchy is the one that should be used by the Operating System. auth="o" function on_exit() { if [ ! -d "$TMP" ] || ! rm -rf "$TMP"; then echo "Delete temporary files failed!" >&2 echo "You need to clean up: $TMP" >&2 exit 1 fi } [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis decrypt tpm2 < JWE > PLAINTEXT" echo exit 2 fi TPM2TOOLS_INFO="$(tpm2_createprimary -v)" match='version="(.)\.' [[ $TPM2TOOLS_INFO =~ $match ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}" if [[ $TPM2TOOLS_VERSION -lt 3 ]] || [[ $TPM2TOOLS_VERSION -gt 5 ]]; then echo "The tpm2 pin requires a tpm2-tools version between 3 and 5" >&2 exit 1 fi if [ -z "$TPM2TOOLS_TCTI" ]; then # Old environment variables for tpm2-tools 3.0 export TPM2TOOLS_TCTI_NAME=device export TPM2TOOLS_DEVICE_FILE= for dev in /dev/tpmrm?; do [ -e "$dev" ] || continue TPM2TOOLS_DEVICE_FILE="$dev" break done # New environment variable for tpm2-tools >= 3.1 export TPM2TOOLS_TCTI="$TPM2TOOLS_TCTI_NAME:$TPM2TOOLS_DEVICE_FILE" if [ -z "$TPM2TOOLS_DEVICE_FILE" ]; then echo "A TPM2 device with the in-kernel resource manager is needed!" >&2 exit 1 fi if ! [[ -r "$TPM2TOOLS_DEVICE_FILE" && -w "$TPM2TOOLS_DEVICE_FILE" ]]; then echo "The $TPM2TOOLS_DEVICE_FILE device must be readable and writable!" >&2 exit 1 fi fi read -r -d . hdr if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then echo "Error decoding JWE protected header!" >&2 exit 1 fi if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tpm2" ]; then echo "JWE pin mismatch!" >&2 exit 1 fi if ! hash="$(jose fmt -j- -Og clevis -g tpm2 -g hash -Su- <<< "$jhd")"; then echo "JWE missing required 'hash' header parameter!" >&2 exit 1 fi if ! key="$(jose fmt -j- -Og clevis -g tpm2 -g key -Su- <<< "$jhd")"; then echo "JWE missing required 'key' header parameter!" >&2 exit 1 fi if ! jwk_pub="$(jose fmt -j- -Og clevis -g tpm2 -g jwk_pub -Su- <<< "$jhd")"; then echo "JWE missing required 'jwk_pub' header parameter!" >&2 exit 1 fi if ! jwk_priv="$(jose fmt -j- -Og clevis -g tpm2 -g jwk_priv -Su- <<< "$jhd")"; then echo "JWE missing required 'jwk_priv' header parameter!" >&2 exit 1 fi mkdir -p "${TMPDIR:-/tmp}" if ! TMP="$(mktemp -d)"; then echo "Creating a temporary dir for TPM files failed!" >&2 exit 1 fi trap 'on_exit' EXIT pcr_ids="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_ids -Su- <<< "$jhd")" || true pcr_spec='' if [ -n "$pcr_ids" ]; then pcr_bank="$(jose fmt -j- -Og clevis -g tpm2 -g pcr_bank -Su- <<< "$jhd")" pcr_spec="$pcr_bank:$pcr_ids" fi if ! jose b64 dec -i- -O "$TMP"/jwk.pub <<< "$jwk_pub"; then echo "Decoding jwk.pub from Base64 failed!" >&2 exit 1 fi if ! jose b64 dec -i- -O "$TMP"/jwk.priv <<< "$jwk_priv"; then echo "Decoding jwk.priv from Base64 failed!" >&2 exit 1 fi case "$TPM2TOOLS_VERSION" in 3) tpm2_createprimary -Q -H "$auth" -g "$hash" -G "$key" -C "$TMP"/primary.context || fail=$?;; 4|5) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$TMP"/primary.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Creating TPM2 primary key failed!" >&2 exit 1 fi tpm2_flushcontext -t case "$TPM2TOOLS_VERSION" in 3) tpm2_load -Q -c "$TMP"/primary.context -u "$TMP"/jwk.pub -r "$TMP"/jwk.priv \ -C "$TMP"/load.context || fail=$?;; 4|5) tpm2_load -Q -C "$TMP"/primary.context -u "$TMP"/jwk.pub -r "$TMP"/jwk.priv \ -c "$TMP"/load.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Loading jwk to TPM2 failed!" >&2 exit 1 fi tpm2_flushcontext -t case "$TPM2TOOLS_VERSION" in 3) jwk="$(tpm2_unseal -c "$TMP"/load.context ${pcr_spec:+-L $pcr_spec})" || fail=$?;; 4|5) jwk="$(tpm2_unseal -c "$TMP"/load.context ${pcr_spec:+-p pcr:$pcr_spec})" || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Unsealing jwk from TPM failed!" >&2 exit 1 fi tpm2_flushcontext -t (echo -n "$jwk$hdr."; /bin/cat) | jose jwe dec -k- -i- exit $? latchset-clevis-6df9b69/src/pins/tpm2/clevis-encrypt-tpm2000077500000000000000000000227451520110721700234440ustar00rootroot00000000000000#!/bin/bash -e # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2017 Red Hat, Inc. # Author: Javier Martinez Canillas # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # First try to use the new version of the PIN implementation if command -v clevis-pin-tpm2 >/dev/null; then exec clevis-pin-tpm2 encrypt "$@" fi SUMMARY="Encrypts using a TPM2.0 chip binding policy" # The owner hierarchy is the one that should be used by the Operating System. auth="o" # Algorithm type must be keyedhash for object with user provided sensitive data. alg_create_key="keyedhash" # Attributes for the created TPM2 object with the JWK as sensitive data. obj_attr="fixedtpm|fixedparent|noda|adminwithpolicy" function on_exit() { if [ ! -d "$TMP" ] || ! rm -rf "$TMP"; then echo "Delete temporary files failed!" >&2 echo "You need to clean up: $TMP" >&2 exit 1 fi } if [ "$1" == "--summary" ]; then echo "$SUMMARY" exit 0 fi if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis encrypt tpm2 CONFIG < PLAINTEXT > JWE" echo echo "$SUMMARY" echo echo "This command uses the following configuration properties:" echo echo " hash: Hash algorithm used in the computation of the object name (default: sha256)" echo echo " key: Algorithm type for the generated key (default: ecc)" echo echo " pcr_bank: PCR algorithm bank to use for policy (default: first supported by TPM)" echo echo " pcr_ids: PCR list used for policy. If not present, no policy is used" echo echo " pcr_digest: Binary PCR hashes encoded in base64. If not present, the hash values are looked up" echo exit 2 fi validate_pcrs() { local _tpm2_tools_v="${1}" local _pcr_bank="${2}" local _pcrs="${3}" [ -z "${_pcr_bank}" ] && return 1 [ -z "${_pcrs}" ] && return 0 local _fail= local _pcrs_r= case "${_tpm2_tools_v}" in 3) _pcrs_r="$(tpm2_pcrlist -L "${_pcr_bank}":"${_pcrs}" | grep -v "^${_pcr_bank}")" || _fail=$?;; 4|5) _pcrs_r=$(tpm2_pcrread "${_pcr_bank}":"${_pcrs}" | grep -v " ${_pcr_bank}") || _fail=$?;; *) _fail=1 esac if [ -n "${_fail}" ] || [ -z "${_pcrs_r}" ]; then return 1 fi return 0 } TPM2TOOLS_INFO="$(tpm2_createprimary -v)" match='version="(.)\.' [[ $TPM2TOOLS_INFO =~ $match ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}" if [[ $TPM2TOOLS_VERSION -lt 3 ]] || [[ $TPM2TOOLS_VERSION -gt 5 ]]; then echo "The tpm2 pin requires a tpm2-tools version between 3 and 5" >&2 exit 1 fi if [ -z "$TPM2TOOLS_TCTI" ]; then # Old environment variables for tpm2-tools 3.0 export TPM2TOOLS_TCTI_NAME=device export TPM2TOOLS_DEVICE_FILE= for dev in /dev/tpmrm?; do [ -e "$dev" ] || continue TPM2TOOLS_DEVICE_FILE="$dev" break done # New environment variable for tpm2-tools >= 3.1 export TPM2TOOLS_TCTI="$TPM2TOOLS_TCTI_NAME:$TPM2TOOLS_DEVICE_FILE" if [ -z "$TPM2TOOLS_DEVICE_FILE" ]; then echo "A TPM2 device with the in-kernel resource manager is needed!" >&2 exit 1 fi if ! [[ -r "$TPM2TOOLS_DEVICE_FILE" && -w "$TPM2TOOLS_DEVICE_FILE" ]]; then echo "The $TPM2TOOLS_DEVICE_FILE device must be readable and writable!" >&2 exit 1 fi fi if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then echo "Configuration is malformed!" >&2 exit 1 fi hash="$(jose fmt -j- -Og hash -u- <<< "$cfg")" || hash="sha256" key="$(jose fmt -j- -Og key -u- <<< "$cfg")" || key="ecc" pcr_bank="$(jose fmt -j- -Og pcr_bank -u- <<< "$cfg")" || { if ! pcr_bank=$(tpm2_getcap pcrs | awk '/^[[:space:]]*-[[:space:]]*([^:]+):[[:space:]]*\[[[:space:]]*[^][:space:]]/ \ {found=1; split($0, m, /[-:[:space:]]+/); print m[2]; exit} END {exit !found}'); then echo "Unable to find non-empty PCR algorithm bank, please check output of tpm2_getcap pcrs" >&2 exit 1 fi } # Trim the spaces from the config, so that we will not have issues parsing # the PCR IDs. pcr_cfg=${cfg//[[:space:]]/} # Issue #103: We support passing pcr_ids using both a single string, as in # "1,3", as well as an actual JSON array, such as ["1","3"]. Let's handle both # cases here. if jose fmt -j- -Og pcr_ids 2>/dev/null <<< "${pcr_cfg}" \ && ! pcr_ids="$(jose fmt -j- -Og pcr_ids -u- 2>/dev/null \ <<< "${pcr_cfg}")"; then # We failed to parse a string, so let's try to parse a JSON array instead. if jose fmt -j- -Og pcr_ids -A 2>/dev/null <<< "${pcr_cfg}"; then # OK, it is an array, so let's get the items and form a string. pcr_ids= for pcr in $(jose fmt -j- -Og pcr_ids -Af- <<< "${pcr_cfg}" \ | tr -d '"'); do pcr_ids=$(printf '%s,%s' "${pcr_ids}" "${pcr}") done # Now let's remove the leading comma. pcr_ids=${pcr_ids/#,/} else # Not to add a policy that was not intended, in this case, no policy # at all, let's report the issue and exit. echo "Parsing the requested policy failed!" >&2 exit 1 fi fi if ! validate_pcrs "${TPM2TOOLS_VERSION}" "${pcr_bank}" "${pcr_ids}"; then echo "Unable to validate combination of PCR bank '${pcr_bank}' and PCR IDs '${pcr_ids}'." >&2 exit 1 fi pcr_digest="$(jose fmt -j- -Og pcr_digest -u- <<< "$cfg")" || true if ! jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')"; then echo "Generating a jwk failed!" >&2 exit 1 fi mkdir -p "${TMPDIR:-/tmp}" if ! TMP="$(mktemp -d)"; then echo "Creating a temporary dir for TPM files failed!" >&2 exit 1 fi trap 'on_exit' EXIT case "$TPM2TOOLS_VERSION" in 3) tpm2_createprimary -Q -H "$auth" -g "$hash" -G "$key" -C "$TMP"/primary.context || fail=$?;; 4|5) tpm2_createprimary -Q -C "$auth" -g "$hash" -G "$key" -c "$TMP"/primary.context || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Creating TPM2 primary key failed!" >&2 exit 1 fi tpm2_flushcontext -t policy_options=() if [ -n "$pcr_ids" ]; then if [ -z "$pcr_digest" ]; then case "$TPM2TOOLS_VERSION" in 3) tpm2_pcrlist -Q -L "$pcr_bank":"$pcr_ids" -o "$TMP"/pcr.digest || fail=$?;; 4|5) tpm2_pcrread -Q "$pcr_bank":"$pcr_ids" -o "$TMP"/pcr.digest || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Creating PCR hashes file failed!" >&2 exit 1 fi tpm2_flushcontext -t else if ! jose b64 dec -i- -O "$TMP"/pcr.digest <<< "$pcr_digest"; then echo "Error decoding PCR digest!" >&2 exit 1 fi fi case "$TPM2TOOLS_VERSION" in 3) tpm2_createpolicy -Q -g "$hash" -P -L "$pcr_bank":"$pcr_ids" \ -F "$TMP"/pcr.digest -f "$TMP"/pcr.policy || fail=$?;; 4|5) tpm2_createpolicy -Q -g "$hash" --policy-pcr -l "$pcr_bank":"$pcr_ids" \ -f "$TMP"/pcr.digest -L "$TMP"/pcr.policy || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "create policy fail, please check the environment or parameters!" exit 1 fi tpm2_flushcontext -t tpm2_flushcontext -l policy_options+=(-L "$TMP/pcr.policy") else obj_attr="$obj_attr|userwithauth" fi case "$TPM2TOOLS_VERSION" in 3) tpm2_create -Q -g "$hash" -G "$alg_create_key" -c "$TMP"/primary.context -u "$TMP"/jwk.pub \ -r "$TMP"/jwk.priv -A "$obj_attr" "${policy_options[@]}" -I- <<< "$jwk" || fail=$?;; 4|5) tpm2_create -Q -g "$hash" -C "$TMP"/primary.context -u "$TMP"/jwk.pub \ -r "$TMP"/jwk.priv -a "$obj_attr" "${policy_options[@]}" -i- <<< "$jwk" || fail=$?;; *) fail=1;; esac if [ -n "$fail" ]; then echo "Creating TPM2 object for jwk failed!" >&2 exit 1 fi tpm2_flushcontext -t if ! jwk_pub="$(jose b64 enc -I "$TMP"/jwk.pub)"; then echo "Encoding jwk.pub in Base64 failed!" >&2 exit 1 fi if ! jwk_priv="$(jose b64 enc -I "$TMP"/jwk.priv)"; then echo "Encoding jwk.priv in Base64 failed!" >&2 exit 1 fi jwe='{"protected":{"clevis":{"pin":"tpm2","tpm2":{}}}}' jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$hash" -s hash -UUUUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$key" -s key -UUUUo-)" if [ -n "$pcr_ids" ]; then jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_bank" -s pcr_bank -UUUUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$pcr_ids" -s pcr_ids -UUUUo-)" fi jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_pub" -s jwk_pub -UUUUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm2 -q "$jwk_priv" -s jwk_priv -UUUUo-)" # The on_exit() trap will not be fired after exec, so let's clean up the temp # directory at this point. [ -d "${TMP}" ] && rm -rf "${TMP}" exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) latchset-clevis-6df9b69/src/pins/tpm2/clevis-encrypt-tpm2.1.adoc000066400000000000000000000071201520110721700244730ustar00rootroot00000000000000CLEVIS-ENCRYPT-TPM2(1) ====================== :doctype: manpage == NAME clevis-encrypt-tpm2 - Encrypts using a TPM2.0 chip binding policy == SYNOPSIS *clevis encrypt tpm2* CONFIG < PT > JWE == OVERVIEW The *clevis encrypt tpm2* command encrypts using a Trusted Platform Module 2.0 (TPM2) chip. Its only argument is the JSON configuration object. When using the tpm2 pin, we create a new, cryptographically-strong, random key. This key is encrypted using the TPM2 chip. Then at decryption time, the key is decrypted again using the TPM2 chip. $ clevis encrypt tpm2 '{}' < PT > JWE The pin has reasonable defaults for its configuration, but a different hierarchy, hash, and key algorithms can be chosen if the defaults used are not suitable: $ clevis encrypt tpm2 '{"hash":"sha1","key":"rsa"}' < PT > JWE To decrypt the data, simply provide the ciphertext (JWE): $ clevis decrypt < JWE > PT Note that like other pins no configuration is used for decryption, this is due clevis storing the public and private keys to unseal the TPM2 encrypted object in the JWE so clevis can fetch that information from there. The pin also supports sealing data to a Platform Configuration Registers (PCR) state. That way the data can only be unsealed if the PCRs hashes values match the policy used when sealing. For example, to seal the data to the PCR with index 0 and 1 for the SHA1 bank: $ clevis encrypt tpm2 '{"pcr_bank":"sha1","pcr_ids":"0,1"}' < PT > JWE The PCR digest values are looked up from the current hash values for the PCRs, but a digest can also be provided if the data needs to be sealed with values different to the current ones, by passing the binary hash encoded in base64: $ clevis encrypt tpm2 '{"pcr_ids":"0","pcr_digest":"xy7J5svCtqlfM03d1lE5gdoA8MI"}' < PT > JWE == Threat model The Clevis security model relies in the fact that an attacker will not be able to access both the encrypted data and the decryption key. For most Clevis pins, the decryption key is not locally stored, so the decryption policy is only satisfied if the decryption key can be remotely accessed. It could for example be stored in a remote server or in a hardware authentication device that has to be plugged into the machine. The tpm2 pin is different in this regard, since a key is wrapped by a TPM2 chip that is always present in the machine. This does not mean that there are not use cases for this pin, but it is important to understand the fact that an attacker that has access to both the encrypted data and the local TPM2 chip will be able to decrypt the data. == CONFIG This command uses the following configuration properties: * *hash* (string) : Hash algorithm used in the computation of the object name (default: sha256) It must be one of the following: - *sha1* - *sha256* - *sha384* - *sha512* - *sm3_256* * *key* (string) : Algorithm type for the generated key (default: ecc) It must be one of the following: - *rsa* - *keyedhash* - *ecc* - *symcipher* * *pcr_bank* (string) : PCR algorithm bank to use for policy (default: first supported by TPM) Examples of PCR algorithm banks, support depends on TPM chip: - *sha1* - *sha256* For the full list of algorithms supported by the TPM chip check output of `tpm2_getcap pcrs` and use the algorithm which shows non-empty list of PCR numbers. * *pcr_ids* (string) : Comma separated list of PCR used for policy. If not present, no policy is used * *pcr_digest* (string) : Binary PCR hashes encoded in base64. If not present, the hash values are looked up == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] latchset-clevis-6df9b69/src/pins/tpm2/meson.build000066400000000000000000000011151520110721700220150ustar00rootroot00000000000000cmds = ['tpm2_createprimary', ['tpm2_pcrread', 'tpm2_pcrlist'], 'tpm2_createpolicy', 'tpm2_create', 'tpm2_flushcontext', 'tpm2_load', 'tpm2_unseal'] all = true foreach cmd : cmds all = all and find_program(cmd, required: false).found() endforeach if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm2') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm2') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm2.1') subdir('tests') else warning('Will not install tpm2 pin due to missing dependencies!') endif latchset-clevis-6df9b69/src/pins/tpm2/tests/000077500000000000000000000000001520110721700210175ustar00rootroot00000000000000latchset-clevis-6df9b69/src/pins/tpm2/tests/meson.build000066400000000000000000000030551520110721700231640ustar00rootroot00000000000000# Tests. env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'luks'), join_paths(meson.source_root(), 'src', 'luks', 'tests'), join_paths(meson.source_root(), 'src', 'pins', 'sss'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), join_paths(meson.source_root(), 'src', 'pins', 'tpm2'), join_paths(meson.source_root(), 'src', 'pins', 'tpm2', 'tests'), join_paths(meson.build_root(), 'src'), join_paths(meson.build_root(), 'src', 'luks'), join_paths(meson.build_root(), 'src', 'luks', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'sss'), join_paths(meson.build_root(), 'src', 'pins', 'tang'), join_paths(meson.build_root(), 'src', 'pins', 'tpm2'), join_paths(meson.build_root(), 'src', 'pins', 'tpm2', 'tests'), separator: ':' ) tpm2_data = configuration_data() tpm2_data.merge_from(data) socat = find_program('socat', required: false) swtpm = find_program('swtpm', '/usr/bin/swtpm', required: false) swtpm_setup = find_program('swtpm_setup', '/usr/bin/swtpm_setup', required: false) tpm2_data.set('SOCAT_BIN', socat.found() ? socat.path() : '') tpm2_data.set('SWTPM_BIN', swtpm.found() ? swtpm.path() : '') tpm2_data.set('SWTPM_SETUP_BIN', swtpm_setup.found() ? swtpm_setup.path() : '') configure_file( input: 'tpm2-common-test-functions.in', output: 'tpm2-common-test-functions', configuration: tpm2_data, ) test('pin-tpm2-hw', find_program('pin-tpm2-hw'), env: env, timeout: 120) test('pin-tpm2-sw', find_program('pin-tpm2-sw'), env: env, timeout: 120) latchset-clevis-6df9b69/src/pins/tpm2/tests/pin-tpm2-hw000077500000000000000000000016361520110721700230350ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tpm2-common-test-functions tpm2_hw_available || skip_test tpm2_version || skip_test . pin-tpm2-tests latchset-clevis-6df9b69/src/pins/tpm2/tests/pin-tpm2-sw000077500000000000000000000016601520110721700230450ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tpm2-common-test-functions tpm2_sw_check_preconditions tpm2_version || skip_test tpm2_start_emulation . pin-tpm2-tests latchset-clevis-6df9b69/src/pins/tpm2/tests/pin-tpm2-tests000077500000000000000000000125211520110721700235540ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # TEST=$(basename "${0}") validate_pcrs() { local _pcr_bank="${1}" local _pcrs="${2}" [ -z "${_pcr_bank}" ] && return 1 [ -z "${_pcrs}" ] && return 0 local _fail= local _pcrs_r= case "${TPM2TOOLS_VERSION}" in 3) _pcrs_r="$(tpm2_pcrlist -L "${_pcr_bank}":"${_pcrs}" | grep -v "^${_pcr_bank}")" || _fail=$?;; 4|5) _pcrs_r=$(tpm2_pcrread "${_pcr_bank}":"${_pcrs}" | grep -v " ${_pcr_bank}") || _fail=$?;; *) _fail=1 esac if [ -n "${_fail}" ] || [ -z "${_pcrs_r}" ]; then return 1 fi return 0 } decode_jwe() { local jwe="${1}" local coded if ! coded=$(jose jwe fmt -i- <<< "${jwe}"); then return 1 fi coded=$(jose fmt -j- -g protected -u- <<< "${coded}" | tr -d '"') jose b64 dec -i- <<< "${coded}" } test_pcr_ids() { local orig="${1}" local cfg="${2}" local expected_pcr_ids="${3}" local enc if ! enc=$(echo -n "${orig}" | clevis encrypt tpm2 "${cfg}"); then echo "${TEST}: encrypt failed for cfg: ${cfg}" >&2 return 1 fi local pcr_ids pcr_ids=$(decode_jwe "${enc}" \ | jose fmt -j- -Og clevis -Og tpm2 -Og pcr_ids -u- 2>/dev/null) local dec dec="$(echo "${enc}" | clevis decrypt)" if [ "${orig}" != "${dec}" ]; then echo "${TEST}: decoded text (${dec}) does not match original one (${orig})" >&2 return 1 fi if [ "${pcr_ids}" != "${expected_pcr_ids}" ]; then echo "${TEST}: pcr_ids (${pcr_ids}) do not match the expected (${expected_pcr_ids}) result." >&2 return 1 fi } test_enc_dec() { local cfg="${1}" output=$(echo Working | clevis encrypt tpm2 "${cfg}" | clevis decrypt) if [ "$output" != "Working" ]; then echo "Output after decrypting doesn't match: ${output} != 'Working'" return 1 fi } test_enc_dec '{}' || exit 1 test_pcr_ids "Hi" '{}' "" || exit 1 test_pcr_ids "Hello" '{}' "" || exit 1 test_pcr_ids "Hi" '{ }' "" || exit 1 test_pcr_ids "Hi" '{"key": "ecc"}' "" || exit 1 test_pcr_ids "Hello" '{"key": "ecc"}' "" || exit 1 # Issue #103: now let's try a few different configs with both strings and # arrays and check if we get the expected pcr_ids. # Let's first make sure this would be a valid configuration. _default_pcr_bank=$(tpm2_getcap pcrs | awk '/^[[:space:]]*-[[:space:]]*([^:]+):[[:space:]]*\[[[:space:]]*[^][:space:]]/ \ {split($0, m, /[-:[:space:]]+/); print m[2]; exit}') if [ -n "$_default_pcr_bank" ] && validate_pcrs "${_default_pcr_bank}" "4,16"; then test_pcr_ids "Hi" '{"pcr_ids": "16"}' "16" || exit 1 test_pcr_ids "Hello" '{"pcr_ids": "16"}' "16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": ["16"]}' "16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": "4, 16"}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": "4,16"}' "4,16" || exit 1 test_pcr_ids "Hello" '{"pcr_ids": "4,16"}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": ["4,16"]}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": [4,16]}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": [4, 16]}' "4,16" || exit 1 test_pcr_ids "Hi" '{"pcr_ids": ["4","16"]}' "4,16" || exit 1 ! test_pcr_ids "Hi" '{"pcr_ids": ["4","16"]}' "foo bar" || exit 1 else echo "Skipping tests related to issue#103 because the combination of pcr_bank and PCRs is invalid" >&2 fi # Test with policies if we have the PIN rewrite available if ! command -v clevis-pin-tpm2 >/dev/null 2>&1; then echo "No PIN rewrite available" exit 0 fi if ! command -v clevis-pin-tpm2-signtool >/dev/null 2>&1; then echo "No policy signtool available" exit 0 fi clevis-pin-tpm2-signtool >policy_working.json << EOP --- - policy_ref: steps: - PCRs: hash_algorithm: sha256 selection: - pcr_id: 21 value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - pcr_id: 22 value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" EOP clevis-pin-tpm2-signtool >policy_broken.json << EOP --- - policy_ref: steps: - PCRs: hash_algorithm: sha256 selection: - pcr_id: 21 value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" - pcr_id: 22 value: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" EOP test_enc_dec '{"policy_pubkey_path":"./publickey.json", "policy_ref": "", "policy_path": "./policy_working.json"}' || exit 1 ! test_enc_dec '{"policy_pubkey_path":"./publickey.json", "policy_ref": "", "policy_path": "./policy_broken.json"}' || exit 1 latchset-clevis-6df9b69/src/pins/tpm2/tests/tpm2-common-test-functions.in000066400000000000000000000110231520110721700264770ustar00rootroot00000000000000#!/bin/bash -x # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # . tests-common-functions SOCAT_BIN="@SOCAT_BIN@" SWTPM_BIN="@SWTPM_BIN@" SWTPM_SETUP_BIN="@SWTPM_SETUP_BIN@" SWTPM_SOCKET_PID= function on_exit() { popd || error "Unable to change directory" if [ ! -d "$TESTDIR" ] || ! rm -rf "$TESTDIR"; then echo "Delete temporary files failed!" >&2 echo "You need to clean up: $TESTDIR" >&2 exit 1 fi # Cleanup sw emulation if [ -n "$SWTPM_SOCKET_PID" ]; then kill $SWTPM_SOCKET_PID >/dev/null 2>&1 sleep .5 # swtpm does not always terminate gracefully, so kill it kill -9 $SWTPM_SOCKET_PID >/dev/null 2>&1 fi } if ! TESTDIR="$(mktemp -d)"; then echo "Creating a temporary dir for TPM files failed!" >&2 exit 1 fi trap 'on_exit' EXIT pushd "$TESTDIR" || error "Unable to change directory" tpm2_hw_available() { # Old environment variables for tpm2-tools 3.0 export TPM2TOOLS_TCTI_NAME=device export TPM2TOOLS_DEVICE_FILE= for dev in /dev/tpmrm?; do [ -e "${dev}" ] || continue TPM2TOOLS_DEVICE_FILE="${dev}" break done # New environment variable for tpm2-tools >= 3.1 export TPM2TOOLS_TCTI="${TPM2TOOLS_TCTI_NAME}:${TPM2TOOLS_DEVICE_FILE}" if [ -z "${TPM2TOOLS_DEVICE_FILE}" ]; then echo "A TPM2 device with the in-kernel resource manager is needed!" >&2 return 1 fi if ! [[ -r "${TPM2TOOLS_DEVICE_FILE}" \ && -w "${TPM2TOOLS_DEVICE_FILE}" ]]; then echo "The ${TPM2TOOLS_DEVICE_FILE} device must be readable and writable!" >&2 return 1 fi return 0 } tpm2_version() { local _tpm2tools_info local _match='version="(.)\.' _tpm2tools_info="$(tpm2_createprimary -v)" [[ ${_tpm2tools_info} =~ ${_match} ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}" if [[ $TPM2TOOLS_VERSION -lt 3 ]] || [[ $TPM2TOOLS_VERSION -gt 5 ]]; then echo "The tpm2 pin requires a tpm2-tools version between 3 and 5" >&2 return 1 fi export TPM2TOOLS_VERSION } tpm2_sw_check_preconditions() { [ -x "${SOCAT_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, socat not found" [ -x "${SWTPM_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, swtpm not found" [ -x "${SWTPM_SETUP_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, swtpm_setup not found" if ! "${SWTPM_BIN}" socket --print-capabilities | jq -e '(.version | test("^0\\.[0-6](\\..*)?$")) or (.features | index("tpm-2.0"))' >/dev/null 2>&1; then skip_test "Skipping TPM2 test with software emulation, no support for TPM 2.0 in swtpm" fi } tpm2_start_emulation() { local socket_wait local server_sock local control_sock echo "Starting TPM 2 emulation" >&2 # Setup TPM 2 data "${SWTPM_SETUP_BIN}" --tpm-state "$TESTDIR" --tpm2 --create-platform-cert --lock-nvram --display >&2 || error "Unable to setup TPM 2 emulation" # Start emulation over socket server_sock="$TESTDIR"/swtpm.sock control_sock="$TESTDIR"/swtpm.sock.ctrl "${SWTPM_BIN}" socket --tpmstate dir="$TESTDIR" --tpm2 --ctrl type=unixio,path="$control_sock" --server type=unixio,path="$server_sock" --flags startup-clear >&2 & SWTPM_SOCKET_PID=$! socket_wait=1 while [ $socket_wait -le 100 ]; do [ -S "$server_sock" ] && break socket_wait=$((socket_wait + 1)) sleep 0.1 done [ "$socket_wait" -gt 100 ] && error "Unable to start TPM 2 emulation" # Use swtpm in tpm2-tools export TPM2TOOLS_TCTI="swtpm:path=$server_sock" # Test if socket communication is supported if ! tpm2_getcap pcrs >/dev/null 2>&1; then # Old libtss2 compatibility - use socat export TPM2TOOLS_TCTI="cmd:\"$SOCAT_BIN\" - \"UNIX-CONNECT:$server_sock\"" fi } latchset-clevis-6df9b69/src/tests-common-functions000066400000000000000000000037401520110721700224070ustar00rootroot00000000000000#!/bin/bash -ex # vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # # Copyright (c) 2019 Red Hat, Inc. # Author: Sergio Correia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # error() { echo "$@" >&2 exit 1 } skip_test() { local message="$*" [ -n "$message" ] && echo "$message" >&2 exit 77 } # Find listening port of a process process_find_port() { local pid="${1}" [ -z "${pid}" ] && \ error "process_find_port: please specify 'pid'" local port port=$(lsof -Pan -p "${pid}" -iTCP -sTCP:LISTEN -Fn | grep '^n.*:' | head -n1 | cut -d: -f2) [ -n "${port}" ] && echo "${port}" } # Wait for the server to be operational. process_wait_until_port_ready() { local pid="${1}" local name="${2}" [ -z "${pid}" ] && \ error "process_wait_until_port_ready: please specify 'pid'" local max_timeout_in_s=5 local start elapsed start="${SECONDS}" while ! process_find_port "${pid}" >/dev/null; do elapsed=$((SECONDS - start)) if ! ps -A -o pid | awk -v pid="${pid}" '$1==pid {found=1} END {exit !found}'; then error "Failed waiting, ${name:-process} terminated" elif [ "${elapsed}" -gt "${max_timeout_in_s}" ]; then error "Timeout (${max_timeout_in_s}s) waiting for ${name:-process}" fi sleep 0.1 echo -n . >&2 done }