From 087331cd549c18403f4d3dd8023fe611c4bed982 Mon Sep 17 00:00:00 2001 From: Packit Service Date: Feb 02 2021 22:18:08 +0000 Subject: ima-evm-utils-1.3.2 base --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c579199 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +*.swp +*~ + +# Generated by autotools +.libs +m4 +.deps +aclocal.m4 +autom4te.cache +config.guess +config.log +config.status +config.sub +configure +depcomp +install-sh +Makefile.in +Makefile +!tests/data/Makefile +missing +compile +libtool +ltmain.sh +test-driver + +# Compiled executables +*.o +*.a +*.lo +*.la +src/evmctl +tests/openclose +config.h +config.h.in +stamp-h1 +*.spec + +# But don't ignore the symlinks with the same names in this directory +!tests/valgrind/* + +# cscope/tags +tags +TAGS +cscope.* +ncscope.* + +# Generated documentation +*.1 +*.8 +*.5 +manpage.links +manpage.refs + +# quilt's files +patches +series + +# test output + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9bea5d1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,78 @@ +dist: bionic +language: C +services: + - docker + +matrix: + include: + # 32 bit build + - os: linux + env: DISTRO=debian:stable VARIANT=i386 ARCH=i386 TSS=tpm2-tss + compiler: gcc + + # cross compilation builds + - os: linux + env: DISTRO=debian:stable VARIANT=cross-compile ARCH=ppc64el TSS=ibmtss + compiler: powerpc64le-linux-gnu-gcc + + - os: linux + env: DISTRO=debian:stable VARIANT=cross-compile ARCH=arm64 TSS=tpm2-tss + compiler: aarch64-linux-gnu-gcc + + - os: linux + env: DISTRO=debian:stable VARIANT=cross-compile ARCH=s390x TSS=ibmtss + compiler: s390x-linux-gnu-gcc + + # musl + - os: linux + env: DISTRO=alpine:latest TSS=tpm2-tss + compiler: gcc + + # glibc (gcc/clang) + - os: linux + env: DISTRO=opensuse/tumbleweed TSS=ibmtss + compiler: clang + + - os: linux + env: DISTRO=opensuse/leap TSS=tpm2-tss + compiler: gcc + + - os: linux + env: DISTRO=ubuntu:eoan TSS=ibmtss + compiler: gcc + + - os: linux + env: DISTRO=ubuntu:xenial TSS=tpm2-tss + compiler: clang + + - os: linux + env: DISTRO=fedora:latest TSS=ibmtss + compiler: clang + + - os: linux + env: DISTRO=centos:7 TSS=tpm2-tss + compiler: gcc + + - os: linux + env: DISTRO=centos:latest TSS=tpm2-tss + compiler: clang + + - os: linux + env: DISTRO=debian:testing TSS=tpm2-tss + compiler: clang + + - os: linux + env: DISTRO=debian:stable TSS=ibmtss + compiler: gcc + +before_install: + - df -hT + - DIR="/usr/src/ima-evm-utils" + - printf "FROM $DISTRO\nRUN mkdir -p $DIR\nWORKDIR $DIR\nCOPY . $DIR\n" > Dockerfile + - cat Dockerfile + - docker build -t ima-evm-utils . + +script: + - INSTALL="${DISTRO%%:*}" + - INSTALL="${INSTALL%%/*}" + - docker run -t ima-evm-utils /bin/sh -c "cd travis && if [ \"$VARIANT\" ]; then ARCH=\"$ARCH\" ./$INSTALL.$VARIANT.sh; fi && ARCH=\"$ARCH\" CC=\"$CC\" TSS=\"$TSS\" ./$INSTALL.sh && if [ ! \"$VARIANT\" ]; then which tpm_server || which swtpm || ../tests/install-swtpm.sh; fi && CC=\"$CC\" VARIANT=\"$VARIANT\" ../build.sh" diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9ffa1a1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +Dmitry Kasatkin + +CONTRIBUTORS: +Vivek Goyal +Mimi Zohar + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..007e939 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..17fd478 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,43 @@ +SUBDIRS = src tests +if MANPAGE_DOCBOOK_XSL +dist_man_MANS = evmctl.1 +endif + +doc_DATA = examples/ima-genkey-self.sh examples/ima-genkey.sh examples/ima-gen-local-ca.sh +EXTRA_DIST = autogen.sh $(doc_DATA) + +CLEANFILES = *.html *.xsl + +ACLOCAL_AMFLAGS = -I m4 + +SRCS = $(HOME)/rpmbuild/SOURCES +SPEC = $(PACKAGE_NAME).spec + +pkgname = $(PACKAGE_NAME)-$(PACKAGE_VERSION) +tarname = $(pkgname).tar.gz + +$(tarname): + git archive --format=tar --prefix=$(pkgname)/ v$(PACKAGE_VERSION) $(FILES) | gzip >$@ + +tar: $(tarname) + +rpm: $(tarname) + cp $(tarname) $(SRCS)/ + rpmbuild -ba --nodeps $(SPEC) + +if MANPAGE_DOCBOOK_XSL +evmctl.1.html: README + @asciidoc -o $@ $< + +evmctl.1: + asciidoc -d manpage -b docbook -o evmctl.1.xsl README + xsltproc --nonet -o $@ $(MANPAGE_DOCBOOK_XSL) evmctl.1.xsl + rm -f evmctl.1.xsl + +rmman: + rm -f evmctl.1 + +doc: evmctl.1.html rmman evmctl.1 +endif + +.PHONY: $(tarname) diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..1530eb3 --- /dev/null +++ b/NEWS @@ -0,0 +1,202 @@ +2020-10-28 Mimi Zohar + + version 1.3.2: + * Bugfixes: importing keys + * NEW: Docker based travis distro testing + * Travis bugfixes, code cleanup, software version update, + and script removal + * Initial travis testing + +2020-08-11 Mimi Zohar + + version 1.3.1: + * "--pcrs" support for per crypto algorithm + * Drop/rename "ima_measurement" options + * Moved this summary from "Changelog" to "NEWS", removing + requirement for GNU empty files + * Distro build fixes + +2020-07-21 Mimi Zohar + + version 1.3 new features: + * NEW ima-evm-utils regression test infrastructure with two initial + tests: + - ima_hash.test: calculate/verify different crypto hash algorithms + - sign_verify.test: EVM and IMA sign/verify signature tests + * TPM 2.0 support + - Calculate the new per TPM 2.0 bank template data digest + - Support original padding the SHA1 template data digest + - Compare ALL the re-calculated TPM 2.0 bank PCRs against the + TPM 2.0 bank PCR values + - Calculate the per TPM bank "boot_aggregate" values, including + PCRs 8 & 9 in calculation + - Support reading the per TPM 2.0 Bank PCRs using Intel's TSS + - boot_aggregate.test: compare the calculated "boot_aggregate" + values with the "boot_aggregate" value included in the IMA + measurement. + * TPM 1.2 support + - Additionally support reading the TPM 1.2 PCRs from a supplied file + ("--pcrs" option) + * Based on original IMA LTP and standalone version support + - Calculate the TPM 1.2 "boot_aggregate" based on the exported + TPM 1.2 BIOS event log. + - In addition to verifying the IMA measurement list against the + the TPM PCRs, verify the IMA template data digest against the + template data. (Based on LTP "--verify" option.) + - Ignore file measurement violations while verifying the IMA + measurment list. (Based on LTP "--validate" option.) + - Verify the file data signature included in the measurement list + based on the file hash also included in the measurement list + (--verify-sig) + - Support original "ima" template (mixed templates not supported) + * Support "sm3" crypto name + + Bug fixes and code cleanup: + * Don't exit with -1 on failure, exit with 125 + * On signature verification failure, include pathname. + * Provide minimal hash_info.h file in case one doesn't exist, needed + by the ima-evm-utils regression tests. + * On systems with TPM 1.2, skip "boot_aggregate.test" using sample logs + * Fix hash_algo type comparison mismatch + * Simplify/clean up code + * Address compiler complaints and failures + * Fix memory allocations and leaks + * Sanity check provided input files are regular files + * Revert making "tsspcrread" a compile build time decision. + * Limit additional messages based on log level (-v) + +2019-07-30 Mimi Zohar + + version 1.2.1 Bug fixes: + * When verifying multiple file signatures, return correct status + * Don't automatically use keys from x509 certs if user supplied "--rsa" + * Fix verifying DIGSIG_VERSION_1 signatures + * autoconf, openssl fixes + + +2019-07-24 Mimi Zohar + + version 1.2 new features: + * Generate EVM signatures based on the specified hash algorithm + * include "security.apparmor" in EVM signature + * Add support for writing & verifying "user.xxxx" xattrs for testing + * Support Strebog/Gost hash functions + * Add OpenSSL engine support + * Use of EVP_PKEY OpenSSL API to generate/verify v2 signatures + * Support verifying multiple signatures at once + * Support new template "buf" field and warn about other unknown fields + * Improve OpenSSL error reporting + * Support reading TPM 2.0 PCRs using tsspcrread + + Bug fixes and code cleanup: + * Update manpage stylesheet detection + * Fix xattr.h include file + * On error when reading TPM PCRs, don't log gargabe + * Properly return keyid string to calc_keyid_v1/v2 callers, caused by + limiting keyid output to verbose mode + * Fix hash buffer overflow caused by EVM support for larger hashes, + defined MAX_DIGEST_SIZE and MAX_SIGNATURE_SIZE, and added "asserts". + * Linked with libcrypto instead of OpenSSL + * Updated Autotools, replacing INCLUDES with AM_CPPFLAGS + * Include new "hash-info.gen" in tar + * Log the hash algorithm, not just the hash value + * Fixed memory leaks in: EV_MD_CTX, init_public_keys + * Fixed other warnings/bugs discovered by clang, coverity + * Remove indirect calls in verify_hash() to improve code readability + * Don't fallback to using sha1 + * Namespace some too generic object names + * Make functions/arrays static if possible + + +2018-01-28 Mimi Zohar + + version 1.1 + * Support the new openssl 1.1 api + * Support for validating multiple pcrs + * Verify the measurement list signature based on the list digest + * Verify the "ima-sig" measurement list using multiple keys + * Fixed parsing the measurement template data field length + * Portable & immutable EVM signatures (new format) + * Multiple fixes that have been lingering in the next branch. Some + are for experimental features that are not yet supported in the + kernel. + +2014-07-30 Dmitry Kasatkin + + version 1.0 + * Recursive hashing + * Immutable EVM signatures (experimental) + * Command 'ima_clear' to remove xattrs + * Support for passing password to the library + * Support for asking password safely from the user + +2014-09-23 Dmitry Kasatkin + + version 0.9 + * Updated README + * man page generated and added to the package + * Use additional SMACK xattrs for EVM signature generation + * Signing functions moved to libimaevm for external use (RPM) + * Fixed setting of correct hash header + +2014-05-05 Dmitry Kasatkin + + version 0.8 + * Symbilic names for keyrings + * Hash list signing + * License text fix for using OpenSSL + * Help output fix + +2014-02-17 Dmitry Kasatkin + + version 0.7 + * Fix symbolic links related bugs + * Provide recursive fixing + * Provide recursive signing + * Move IMA verification to the library (first for LTP use) + * Support for target architecture data size + * Remove obsolete module signing code + * Code cleanup + +2013-08-28 Dmitry Kasatkin + + version 0.6 + * support for asymmetric crypto keys and new signature format (v2) + * fixes to set correct hash algo for digital signature v1 + * uuid support for EVM + * signature verification support + * test scripts removed + * README updates + +2012-05-18 Dmitry Kasatkin + + version 0.3 + * llistxattr returns 0 if there are no xattrs and it is valid + * Added entry type to directory hash calculation + * inline block variable renamed + * Remove forced tag creation + * Use libexec for programs and scripts + * Some files updated + * Do not search for algorithm as it is known + * Refactored to remove redundant hash initialization code + * Added hash calculation for special files + +2012-04-05 Dmitry Kasatkin + + version 0.2 + * added RPM & TAR building makefile rules + * renamed evm-utils to ima-evm-utils + * added command options description + * updated error handling + * refactored redundant code + +2012-04-02 Dmitry Kasatkin + + version 0.1.0 + * Fully functional version for lastest 3.x kernels + +2011-08-24 Dmitry Kasatkin + + version 0.1 + * Initial public version. + diff --git a/README b/README new file mode 100644 index 0000000..321045d --- /dev/null +++ b/README @@ -0,0 +1,446 @@ +EVMCTL(1) +========= + +NAME +---- + +evmctl - IMA/EVM signing utility + + +SYNOPSIS +-------- + +evmctl [options] [OPTIONS] + + +DESCRIPTION +----------- + +The evmctl utility can be used for producing and verifying digital signatures, +which are used by Linux kernel integrity subsystem (IMA/EVM). It can be also +used to import keys into the kernel keyring. + +COMMANDS +-------- + + --version + help + import [--rsa] pubkey keyring + sign [-r] [--imahash | --imasig ] [--portable] [--key key] [--pass password] file + verify file + ima_boot_aggregate [--pcrs hash-algorithm,file] [TPM 1.2 BIOS event log] + ima_sign [--sigfile] [--key key] [--pass password] file + ima_verify file + ima_hash file + ima_measurement [--ignore-violations] [--verify-sig [--key "key1, key2, ..."]] [--pcrs [hash-algorithm,]file [--pcrs hash-algorithm,file] ...] file + ima_fix [-t fdsxm] path + sign_hash [--key key] [--pass password] + hmac [--imahash | --imasig ] file + + +OPTIONS +------- + + -a, --hashalgo sha1 (default), sha224, sha256, sha384, sha512 + -s, --imasig make IMA signature + -d, --imahash make IMA hash + -f, --sigfile store IMA signature in .sig file instead of xattr + --xattr-user store xattrs in user namespace (for testing purposes) + --rsa use RSA key type and signing scheme v1 + -k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem) + -o, --portable generate portable EVM signatures + -p, --pass password for encrypted signing key + -r, --recursive recurse into directories (sign) + -t, --type file types to fix 'fdsxm' (f: file, d: directory, s: block/char/symlink) + x - skip fixing if both ima and evm xattrs exist (use with caution) + m - stay on the same filesystem (like 'find -xdev') + -n print result to stdout instead of setting xattr + -u, --uuid use custom FS UUID for EVM (unspecified: from FS, empty: do not use) + --smack use extra SMACK xattrs for EVM + --m32 force EVM hmac/signature for 32 bit target system + --m64 force EVM hmac/signature for 64 bit target system + --engine e preload OpenSSL engine e (such as: gost) + --pcrs file containing TPM pcrs, one per hash-algorithm/bank + --ignore-violations ignore ToMToU measurement violations + --verify-sig verify the file signature based on the file hash, both + stored in the template data. + -v increase verbosity level + -h, --help display this help and exit + + +INTRODUCTION +------------ + +Linux kernel integrity subsystem is comprised of a number of different components +including the Integrity Measurement Architecture (IMA), Extended Verification Module +(EVM), IMA-appraisal extension, digital signature verification extension and audit +measurement log support. + +The evmctl utility is used for producing and verifying digital signatures, which +are used by the Linux kernel integrity subsystem. It is also used for importing keys +into the kernel keyring. + +Linux integrity subsystem allows to use IMA and EVM signatures. EVM signature +protects file metadata, such as file attributes and extended attributes. IMA +signature protects file content. + +For more detailed information about integrity subsystem it is recommended to follow +resources in RESOURCES section. + + +EVM HMAC and signature metadata +------------------------------- + +EVM protects file metadata by including following attributes into HMAC and signature +calculation: inode number, inode generation, UID, GID, file mode, security.selinux, +security.SMACK64, security.ima, security.capability. + +EVM HMAC and signature in may also include additional file and file system attributes. +Currently supported additional attributes are filesystem UUID and extra SMACK +extended attributes. + +Kernel configuration option CONFIG_EVM_ATTR_FSUUID controls whether to include +filesystem UUID into HMAC and enabled by default. Therefore evmctl also includes +fsuuid by default. Providing '--uuid' option without parameter allows to disable +usage of fs uuid. Providing '--uuid=UUID' option with parameter allows to use +custom UUID. Providing the '--portable' option will disable usage of the fs uuid +and also the inode number and generation. + +Kernel configuration option CONFIG_EVM_EXTRA_SMACK_XATTRS controls whether to +include additional SMACK extended attributes into HMAC. They are following: +security.SMACK64EXEC, security.SMACK64TRANSMUTE and security.SMACK64MMAP. +evmctl '--smack' options enables that. + + +Key and signature formats +------------------------- + +Linux integrity subsystem supports two type of signature and respectively two +key formats. + +First key format (v1) is pure RSA key encoded in PEM a format and uses own signature +format. It is now non-default format and requires to provide evmctl '--rsa' option +for signing and importing the key. + +Second key format uses X509 DER encoded public key certificates and uses asymmetric key support +in the kernel (since kernel 3.9). CONFIG_INTEGRITY_ASYMMETRIC_KEYS must be enabled (default). + + +Integrity keyrings +---------------- + +Integrity subsystem uses dedicated IMA/EVM keyrings to search for signature verification +keys - '_ima' and '_evm' respectively. + +Since 3.13 IMA allows to declare IMA keyring as trusted. It allows only to load keys, +signed by a key from the system keyring (.system). It means self-signed keys are not +allowed. This is a default behavior unless CONFIG_IMA_TRUSTED_KEYRING is undefined. +IMA trusted keyring is has different name '.ima'. Trusted keyring requires X509 +public key certificates. Old version RSA public keys are not compatible with trusted +keyring. + + +Generate EVM encrypted keys +--------------------------- + +EVM encrypted key is used for EVM HMAC calculation: + + # create and save the key kernel master key (user type) + # LMK is used to encrypt encrypted keys + keyctl add user kmk "`dd if=/dev/urandom bs=1 count=32 2>/dev/null`" @u + keyctl pipe `keyctl search @u user kmk` > /etc/keys/kmk + + # create the EVM encrypted key + keyctl add encrypted evm-key "new user:kmk 64" @u + keyctl pipe `keyctl search @u encrypted evm-key` >/etc/keys/evm-key + + +Generate EVM trusted keys (TPM based) +------------------------------------- + +Trusted EVM keys are keys which a generate with the help of TPM. +They are not related to integrity trusted keys. + + # create and save the key kernel master key (user type) + keyctl add trusted kmk "new 32" @u + keyctl pipe `keyctl search @u trusted kmk` >kmk + + # create the EVM trusted key + keyctl add encrypted evm-key "new trusted:kmk 32" @u + keyctl pipe `keyctl search @u encrypted evm-key` >evm-key + + +Generate signing and verification keys +-------------------------------------- + +Generate private key in plain text format: + + openssl genrsa -out privkey_evm.pem 1024 + +Generate encrypted private key: + + openssl genrsa -des3 -out privkey_evm.pem 1024 + +Make encrypted private key from unencrypted: + + openssl rsa -in /etc/keys/privkey_evm.pem -out privkey_evm_enc.pem -des3 + +Generate self-signed X509 public key certificate and private key for using kernel +asymmetric keys support: + + openssl req -new -nodes -utf8 -sha1 -days 36500 -batch \ + -x509 -config x509_evm.genkey \ + -outform DER -out x509_evm.der -keyout privkey_evm.pem + +Configuration file x509_evm.genkey: + + # Begining of the file + [ req ] + default_bits = 1024 + distinguished_name = req_distinguished_name + prompt = no + string_mask = utf8only + x509_extensions = myexts + + [ req_distinguished_name ] + O = Magrathea + CN = Glacier signing key + emailAddress = slartibartfast@magrathea.h2g2 + + [ myexts ] + basicConstraints=critical,CA:FALSE + keyUsage=digitalSignature + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid + # EOF + + +Generate public key for using RSA key format: + + openssl rsa -pubout -in privkey_evm.pem -out pubkey_evm.pem + + +Copy keys to /etc/keys: + + cp pubkey_evm.pem /etc/keys + scp pubkey_evm.pem target:/etc/keys + or + cp x509_evm.pem /etc/keys + scp x509_evm.pem target:/etc/keys + + +Generate trusted keys +--------------------- + +Generation of trusted keys is a bit more complicated process and involves +following steps: + +* Creation of local IMA certification authority (CA). + It consist of private and public key certificate which are used + to sign and verify other keys. +* Build Linux kernel with embedded local IMA CA X509 certificate. + It is used to verify other keys added to the '.ima' trusted keyring +* Generate IMA private signing key and verification public key certificate, + which is signed using local IMA CA private key. + +Configuration file ima-local-ca.genkey: + + # Begining of the file + [ req ] + default_bits = 2048 + distinguished_name = req_distinguished_name + prompt = no + string_mask = utf8only + x509_extensions = v3_ca + + [ req_distinguished_name ] + O = IMA-CA + CN = IMA/EVM certificate signing key + emailAddress = ca@ima-ca + + [ v3_ca ] + basicConstraints=CA:TRUE + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid:always,issuer + # keyUsage = cRLSign, keyCertSign + # EOF + +Generate private key and X509 public key certificate: + + openssl req -new -x509 -utf8 -sha1 -days 3650 -batch -config $GENKEY \ + -outform DER -out ima-local-ca.x509 -keyout ima-local-ca.priv + +Produce X509 in DER format for using while building the kernel: + + openssl x509 -inform DER -in ima-local-ca.x509 -out ima-local-ca.pem + +Configuration file ima.genkey: + + # Begining of the file + [ req ] + default_bits = 1024 + distinguished_name = req_distinguished_name + prompt = no + string_mask = utf8only + x509_extensions = v3_usr + + [ req_distinguished_name ] + O = `hostname` + CN = `whoami` signing key + emailAddress = `whoami`@`hostname` + + [ v3_usr ] + basicConstraints=critical,CA:FALSE + #basicConstraints=CA:FALSE + keyUsage=digitalSignature + #keyUsage = nonRepudiation, digitalSignature, keyEncipherment + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid + #authorityKeyIdentifier=keyid,issuer + # EOF + + +Generate private key and X509 public key certificate signing request: + + openssl req -new -nodes -utf8 -sha1 -days 365 -batch -config $GENKEY \ + -out csr_ima.pem -keyout privkey_ima.pem + +Sign X509 public key certificate signing request with local IMA CA private key: + + openssl x509 -req -in csr_ima.pem -days 365 -extfile $GENKEY -extensions v3_usr \ + -CA ima-local-ca.pem -CAkey ima-local-ca.priv -CAcreateserial \ + -outform DER -out x509_ima.der + + +Sign file data and metadata +--------------------------- + +Default key locations: + + Private RSA key: /etc/keys/privkey_evm.pem + Public RSA key: /etc/keys/pubkey_evm.pem + X509 certificate: /etc/keys/x509_evm.der + +Options to remember: '-k', '-r', '--rsa', '--uuid', '--smack'. + +Sign file with EVM signature and calculate hash value for IMA: + + evmctl sign --imahash test.txt + +Sign file with both IMA and EVM signatures: + + evmctl sign --imasig test.txt: + +Sign file with IMA signature: + + evmctl ima_sign test.txt + +Sign recursively whole filesystem: + + evmctl -r sign --imahash / + +Fix recursively whole filesystem: + + evmctl -r ima_fix / + +Sign filesystem selectively using 'find' command: + + find / \( -fstype rootfs -o -fstype ext4 \) -exec evmctl sign --imahash '{}' \; + +Fix filesystem selectively using 'find' command: + + find / \( -fstype rootfs -o -fstype ext4 \) -exec sh -c "< '{}'" \; + + +Initialize IMA/EVM at early boot +-------------------------------- + +IMA/EVM initialization should be normally done from initial RAM file system +before mounting root filesystem. + +Here is Ubuntu initramfs example script (/etc/initramfs-tools/scripts/local-top/ima.sh) + + # mount securityfs if not mounted + SECFS=/sys/kernel/security + grep -q $SECFS /proc/mounts || mount -n -t securityfs securityfs $SECFS + + # search for IMA trusted keyring, then for untrusted + ima_id="`awk '/\.ima/ { printf "%d", "0x"$1; }' /proc/keys`" + if [ -z "$ima_id" ]; then + ima_id=`keyctl search @u keyring _ima 2>/dev/null` + if [ -z "$ima_id" ]; then + ima_id=`keyctl newring _ima @u` + fi + fi + # import IMA X509 certificate + evmctl import /etc/keys/x509_ima.der $ima_id + + # search for EVM keyring + evm_id=`keyctl search @u keyring _evm 2>/dev/null` + if [ -z "$evm_id" ]; then + evm_id=`keyctl newring _evm @u` + fi + # import EVM X509 certificate + evmctl import /etc/keys/x509_evm.der $evm_id + + # a) import EVM encrypted key + cat /etc/keys/kmk | keyctl padd user kmk @u + keyctl add encrypted evm-key "load `cat /etc/keys/evm-key`" @u + # OR + # b) import EVM trusted key + keyctl add trusted kmk "load `cat /etc/keys/kmk`" @u + keyctl add encrypted evm-key "load `cat /etc/keys/evm-key`" @u + + # enable EVM + echo "1" > /sys/kernel/security/evm + +Optionally it is possible also to forbid adding, removing of new public keys +and certificates into keyrings and revoking keys using 'keyctl setperm' command: + + # protect EVM keyring + keyctl setperm $evm_id 0x0b0b0000 + # protect IMA keyring + keyctl setperm $ima_id 0x0b0b0000 + # protecting IMA key from revoking (against DoS) + ima_key=`evmctl import /etc/keys/x509_ima.der $ima_id` + keyctl setperm $ima_key 0x0b0b0000 + + +When using plain RSA public keys in PEM format, use 'evmctl import --rsa' for importing keys: + + evmctl import --rsa /etc/keys/pubkey_evm.pem $evm_id + +Latest version of keyctl allows to import X509 public key certificates: + + cat /etc/keys/x509_ima.der | keyctl padd asymmetric '' $ima_id + + +FILES +----- + +Examples of scripts to generate X509 public key certificates: + + /usr/share/doc/ima-evm-utils/ima-genkey-self.sh + /usr/share/doc/ima-evm-utils/ima-genkey.sh + /usr/share/doc/ima-evm-utils/ima-gen-local-ca.sh + + +AUTHOR +------ + +Written by Dmitry Kasatkin, and others. + + +RESOURCES +--------- + + http://sourceforge.net/p/linux-ima/wiki/Home + http://sourceforge.net/p/linux-ima/ima-evm-utils + + +COPYING +------- + +Copyright \(C) 2012 - 2014 Linux Integrity Project. Free use of this software is granted under +the terms of the GNU Public License (GPL). + diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..dd430d4 --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,14 @@ + +AC_DEFUN([PKG_ARG_ENABLE], + [ + AC_MSG_CHECKING(whether to enable $1) + AC_ARG_ENABLE([$1], AC_HELP_STRING([--enable-$1], [enable $1 (default is $2)]), + [pkg_cv_enable_$1=$enableval], + [AC_CACHE_VAL([pkg_cv_enable_$1], [pkg_cv_enable_$1=$2])]) + if test $pkg_cv_enable_$1 = yes; then + AC_DEFINE([$3],, [$4]) + fi + AC_MSG_RESULT([$pkg_cv_enable_$1]) + AM_CONDITIONAL($3, test $pkg_cv_enable_$1 = yes) +]) + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..902f2bc --- /dev/null +++ b/autogen.sh @@ -0,0 +1,4 @@ +#! /bin/sh +set -e + +autoreconf -i diff --git a/build-static.sh b/build-static.sh new file mode 100755 index 0000000..d6d420b --- /dev/null +++ b/build-static.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +gcc -static -o evmctl.static -include config.h src/evmctl.c src/libimaevm.c -lcrypto -lkeyutils -ldl + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b922fa6 --- /dev/null +++ b/build.sh @@ -0,0 +1,97 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel + +set -e + +CC="${CC:-gcc}" +CFLAGS="${CFLAGS:--Wformat -Werror=format-security -Werror=implicit-function-declaration -Werror=return-type -fno-common}" +PREFIX="${PREFIX:-$HOME/ima-evm-utils-install}" + +export LD_LIBRARY_PATH="$PREFIX/lib64:$PREFIX/lib:/usr/local/lib64:/usr/local/lib" +export PATH="$PREFIX/bin:/usr/local/bin:$PATH" + +title() +{ + echo "===== $1 =====" +} + +log_exit() +{ + local ret="${3:-$?}" + local log="$1" + local msg="$2" + local prefix + + echo "=== $log ===" + [ $ret -eq 0 ] || prefix="FAIL: " + cat $log + echo + echo "$prefix$msg, see output of $log above" + exit $ret +} + +cd `dirname $0` + +case "$VARIANT" in + i386) + echo "32-bit compilation" + export CFLAGS="-m32 $CFLAGS" LDFLAGS="-m32 $LDFLAGS" + export PKG_CONFIG_LIBDIR=/usr/lib/i386-linux-gnu/pkgconfig + ;; + cross-compile) + host="${CC%-gcc}" + export CROSS_COMPILE="${host}-" + host="--host=$host" + echo "cross compilation: $host" + echo "CROSS_COMPILE: '$CROSS_COMPILE'" + ;; + *) + if [ "$VARIANT" ]; then + echo "Wrong VARIANT: '$VARIANT'" >&2 + exit 1 + fi + echo "native build" + ;; +esac + +title "compiler version" +$CC --version +echo "CFLAGS: '$CFLAGS'" +echo "LDFLAGS: '$LDFLAGS'" +echo "PREFIX: '$PREFIX'" + +title "configure" +./autogen.sh +./configure --prefix=$PREFIX $host || log_exit config.log "configure failed" + +title "make" +make -j$(nproc) +make install + +title "test" +if [ "$VARIANT" = "cross-compile" ]; then + echo "skip make check on cross compilation" + exit 0 +fi + +ret=0 +VERBOSE=1 make check || ret=$? + +title "logs" +if [ $ret -eq 0 ]; then + tail -3 tests/ima_hash.log + tail -3 tests/sign_verify.log + tail -20 tests/boot_aggregate.log + exit 0 +fi + +cat tests/test-suite.log + +if [ $ret -eq 77 ]; then + msg="WARN: some tests skipped" + ret=0 +else + msg="FAIL: tests exited: $ret" +fi + +log_exit tests/test-suite.log "$msg" $ret diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e1ed100 --- /dev/null +++ b/configure.ac @@ -0,0 +1,83 @@ +# autoconf script + +AC_PREREQ([2.65]) +AC_INIT(ima-evm-utils, 1.3.2, zohar@linux.ibm.com) +AM_INIT_AUTOMAKE([foreign]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_CANONICAL_HOST +AC_USE_SYSTEM_EXTENSIONS + +# Checks for programs. +AC_PROG_CC +AM_PROG_CC_C_O +#AC_PROG_CXX +#AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LIBTOOL +#AC_PROG_LN_S +LT_INIT + +# FIXME: Replace `main' with a function in `-lpthread': +#AC_CHECK_LIB([pthread], [main]) + +# Checks for header files. +AC_HEADER_STDC + +PKG_CHECK_MODULES(LIBCRYPTO, [libcrypto >= 0.9.8 ]) +AC_SUBST(KERNEL_HEADERS) +AC_CHECK_HEADER(unistd.h) +AC_CHECK_HEADERS(openssl/conf.h) + +AC_CHECK_LIB([tss2-esys], [Esys_Free]) +AC_CHECK_LIB([tss2-rc], [Tss2_RC_Decode]) +AM_CONDITIONAL([USE_PCRTSS], [test "x$ac_cv_lib_tss2_esys_Esys_Free" = "xyes"]) + +AC_CHECK_HEADERS(sys/xattr.h, , [AC_MSG_ERROR([sys/xattr.h header not found. You need the c-library development package.])]) +AC_CHECK_HEADERS(keyutils.h, , [AC_MSG_ERROR([keyutils.h header not found. You need the libkeyutils development package.])]) + +AC_ARG_WITH(kernel_headers, [AS_HELP_STRING([--with-kernel-headers=PATH], + [specifies the Linux kernel-headers package location or kernel root directory you want to use])], + [KERNEL_HEADERS="$withval"], + [KERNEL_HEADERS=/lib/modules/$(uname -r)/source]) + +AC_ARG_ENABLE([openssl_conf], + [AS_HELP_STRING([--disable-openssl-conf], [disable loading of openssl config by evmctl])], + [if test "$enable_openssl_conf" = "no"; then + AC_DEFINE(DISABLE_OPENSSL_CONF, 1, [Define to disable loading of openssl config by evmctl.]) + fi], [enable_openssl_conf=yes]) + +#debug support - yes for a while +PKG_ARG_ENABLE(debug, "yes", DEBUG, [Enable Debug support]) +if test $pkg_cv_enable_debug = yes; then + CFLAGS="$CFLAGS -g -O1 -Wall -Wstrict-prototypes -pipe" +else + CFLAGS="$CFLAGS -Wall -Wstrict-prototypes -pipe -fomit-frame-pointer" +fi + +EVMCTL_MANPAGE_DOCBOOK_XSL + +# for gcov +#CFLAGS="$CFLAGS -Wall -fprofile-arcs -ftest-coverage" +#CXXFLAGS="$CXXFLAGS -Wall -fprofile-arcs -ftest-coverage" +#LDFLAGS="$LDFLAGS -fprofile-arcs" +#DISTCLEANFILES="*.gcno *.gcda" + +AC_CONFIG_FILES([Makefile + src/Makefile + tests/Makefile + packaging/ima-evm-utils.spec + ]) +AC_OUTPUT + +# Give some feedback +echo +echo +echo "Configuration:" +echo " debug: $pkg_cv_enable_debug" +echo " openssl-conf: $enable_openssl_conf" +echo " tss2-esys: $ac_cv_lib_tss2_esys_Esys_Free" +echo " tss2-rc-decode: $ac_cv_lib_tss2_rc_Tss2_RC_Decode" +echo " doc: $have_doc" +echo diff --git a/examples/ima-gen-local-ca.sh b/examples/ima-gen-local-ca.sh new file mode 100755 index 0000000..1f24949 --- /dev/null +++ b/examples/ima-gen-local-ca.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +GENKEY=ima-local-ca.genkey + +cat << __EOF__ >$GENKEY +[ req ] +default_bits = 2048 +distinguished_name = req_distinguished_name +prompt = no +string_mask = utf8only +x509_extensions = v3_ca + +[ req_distinguished_name ] +O = IMA-CA +CN = IMA/EVM certificate signing key +emailAddress = ca@ima-ca + +[ v3_ca ] +basicConstraints=CA:TRUE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +# keyUsage = cRLSign, keyCertSign +__EOF__ + +openssl req -new -x509 -utf8 -sha1 -days 3650 -batch -config $GENKEY \ + -outform DER -out ima-local-ca.x509 -keyout ima-local-ca.priv + +openssl x509 -inform DER -in ima-local-ca.x509 -out ima-local-ca.pem + diff --git a/examples/ima-genkey-self.sh b/examples/ima-genkey-self.sh new file mode 100755 index 0000000..e293b94 --- /dev/null +++ b/examples/ima-genkey-self.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +GENKEY=x509_evm.genkey + +cat << __EOF__ >$GENKEY +[ req ] +default_bits = 1024 +distinguished_name = req_distinguished_name +prompt = no +string_mask = utf8only +x509_extensions = myexts + +[ req_distinguished_name ] +O = `hostname` +CN = `whoami` signing key +emailAddress = `whoami`@`hostname` + +[ myexts ] +basicConstraints=critical,CA:FALSE +keyUsage=digitalSignature +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid +__EOF__ + +openssl req -x509 -new -nodes -utf8 -sha1 -days 3650 -batch -config $GENKEY \ + -outform DER -out x509_evm.der -keyout privkey_evm.pem + +openssl rsa -pubout -in privkey_evm.pem -out pubkey_evm.pem + diff --git a/examples/ima-genkey.sh b/examples/ima-genkey.sh new file mode 100755 index 0000000..b08778f --- /dev/null +++ b/examples/ima-genkey.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +GENKEY=ima.genkey + +cat << __EOF__ >$GENKEY +[ req ] +default_bits = 1024 +distinguished_name = req_distinguished_name +prompt = no +string_mask = utf8only +x509_extensions = v3_usr + +[ req_distinguished_name ] +O = `hostname` +CN = `whoami` signing key +emailAddress = `whoami`@`hostname` + +[ v3_usr ] +basicConstraints=critical,CA:FALSE +#basicConstraints=CA:FALSE +keyUsage=digitalSignature +#keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid +#authorityKeyIdentifier=keyid,issuer +__EOF__ + +openssl req -new -nodes -utf8 -sha1 -days 365 -batch -config $GENKEY \ + -out csr_ima.pem -keyout privkey_ima.pem +openssl x509 -req -in csr_ima.pem -days 365 -extfile $GENKEY -extensions v3_usr \ + -CA ima-local-ca.pem -CAkey ima-local-ca.priv -CAcreateserial \ + -outform DER -out x509_ima.der + diff --git a/m4/manpage-docbook-xsl.m4 b/m4/manpage-docbook-xsl.m4 new file mode 100644 index 0000000..25c8ce5 --- /dev/null +++ b/m4/manpage-docbook-xsl.m4 @@ -0,0 +1,48 @@ +dnl Copyright (c) 2018-2020 Petr Vorel +dnl Find docbook manpage stylesheet + +AC_DEFUN([EVMCTL_MANPAGE_DOCBOOK_XSL], [ + DOCBOOK_XSL_URI="http://docbook.sourceforge.net/release/xsl/current" + DOCBOOK_XSL_PATH="manpages/docbook.xsl" + + AC_PATH_PROGS(XMLCATALOG, xmlcatalog) + AC_ARG_WITH([xml-catalog], + AC_HELP_STRING([--with-xml-catalog=CATALOG], + [path to xml catalog to use]),, + [with_xml_catalog=/etc/xml/catalog]) + XML_CATALOG_FILE="$with_xml_catalog" + AC_SUBST([XML_CATALOG_FILE]) + + if test "x${XMLCATALOG}" = "x"; then + AC_MSG_WARN([xmlcatalog not found, cannot search for $DOCBOOK_XSL_PATH]) + else + AC_MSG_CHECKING([for XML catalog ($XML_CATALOG_FILE)]) + if test -f "$XML_CATALOG_FILE"; then + have_xmlcatalog_file=yes + AC_MSG_RESULT([found]) + else + AC_MSG_RESULT([not found, cannot search for $DOCBOOK_XSL_PATH]) + fi + fi + + if test "x${XMLCATALOG}" != "x" -a "x$have_xmlcatalog_file" = "xyes"; then + MANPAGE_DOCBOOK_XSL=$(${XMLCATALOG} ${XML_CATALOG_FILE} ${DOCBOOK_XSL_URI}/${DOCBOOK_XSL_PATH} | sed 's|^file:/\+|/|') + fi + + if test "x${MANPAGE_DOCBOOK_XSL}" = "x"; then + MANPAGE_DOCBOOK_XSL="/usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl" + AC_MSG_WARN([trying a default path for $DOCBOOK_XSL_PATH]) + fi + + if test -f "$MANPAGE_DOCBOOK_XSL"; then + have_doc=yes + AC_MSG_NOTICE([using $MANPAGE_DOCBOOK_XSL for generating doc]) + else + AC_MSG_WARN([$DOCBOOK_XSL_PATH not found, generating doc will be skipped]) + MANPAGE_DOCBOOK_XSL= + have_doc=no + fi + AM_CONDITIONAL(MANPAGE_DOCBOOK_XSL, test "x$have_doc" = xyes) + + AC_SUBST(MANPAGE_DOCBOOK_XSL) +]) diff --git a/packaging/ima-evm-utils.spec b/packaging/ima-evm-utils.spec new file mode 100644 index 0000000..c342cc0 --- /dev/null +++ b/packaging/ima-evm-utils.spec @@ -0,0 +1,52 @@ +Name: ima-evm-utils +Version: 1.3.2 +Release: 1%{?dist} +Summary: ima-evm-utils - IMA/EVM control utility +Group: System/Libraries +License: GPLv2 +#URL: +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: openssl-devel +BuildRequires: keyutils-libs-devel + +%description +This package provide IMA/EVM control utility + +%prep +%setup -q + +%build +./autogen.sh +%configure --prefix=/usr +make + +%install +rm -rf %{buildroot} +make DESTDIR=%{buildroot} install + +%clean +rm -rf %{buildroot} + +%post +/sbin/ldconfig +exit 0 + +%preun -p /sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%defattr(-,root,root,-) +%{_bindir}/* +%{_libdir}/libimaevm.* +%{_includedir}/* + +%changelog +* Thu Apr 05 2012 Dmitry Kasatkin +- Initial RPM spec file + diff --git a/packaging/ima-evm-utils.spec.in b/packaging/ima-evm-utils.spec.in new file mode 100644 index 0000000..65c32f9 --- /dev/null +++ b/packaging/ima-evm-utils.spec.in @@ -0,0 +1,52 @@ +Name: @PACKAGE_NAME@ +Version: @PACKAGE_VERSION@ +Release: 1%{?dist} +Summary: @PACKAGE_NAME@ - IMA/EVM control utility +Group: System/Libraries +License: GPLv2 +#URL: +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: openssl-devel +BuildRequires: keyutils-libs-devel + +%description +This package provide IMA/EVM control utility + +%prep +%setup -q + +%build +./autogen.sh +%configure --prefix=/usr +make + +%install +rm -rf %{buildroot} +make DESTDIR=%{buildroot} install + +%clean +rm -rf %{buildroot} + +%post +/sbin/ldconfig +exit 0 + +%preun -p /sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%defattr(-,root,root,-) +%{_bindir}/* +%{_libdir}/libimaevm.* +%{_includedir}/* + +%changelog +* Thu Apr 05 2012 Dmitry Kasatkin +- Initial RPM spec file + diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..38e8e3c --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +hash_info.h diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..d6c779f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,34 @@ +lib_LTLIBRARIES = libimaevm.la + +libimaevm_la_SOURCES = libimaevm.c +libimaevm_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCRYPTO_CFLAGS) +# current[:revision[:age]] +# result: [current-age].age.revision +libimaevm_la_LDFLAGS = -version-info 2:0:0 +libimaevm_la_LIBADD = $(LIBCRYPTO_LIBS) + +include_HEADERS = imaevm.h + +nodist_libimaevm_la_SOURCES = hash_info.h +BUILT_SOURCES = hash_info.h +EXTRA_DIST = hash_info.gen +hash_info.h: Makefile + $(srcdir)/hash_info.gen $(KERNEL_HEADERS) >$@ + +bin_PROGRAMS = evmctl + +evmctl_SOURCES = evmctl.c utils.c +evmctl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCRYPTO_CFLAGS) +evmctl_LDFLAGS = $(LDFLAGS_READLINE) +evmctl_LDADD = $(LIBCRYPTO_LIBS) -lkeyutils libimaevm.la + +if USE_PCRTSS +evmctl_SOURCES += pcr_tss.c +else +evmctl_SOURCES += pcr_tsspcrread.c +endif + +AM_CPPFLAGS = -I$(top_srcdir) -include config.h + +CLEANFILES = hash_info.h tmp_hash_info.h +DISTCLEANFILES = @DISTCLEANFILES@ diff --git a/src/evmctl.c b/src/evmctl.c new file mode 100644 index 0000000..1815f55 --- /dev/null +++ b/src/evmctl.c @@ -0,0 +1,2755 @@ +/* + * ima-evm-utils - IMA/EVM support utilities + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011,2012,2013 Intel Corporation + * Copyright (C) 2013,2014 Samsung Electronics + * + * Authors: + * Dmitry Kasatkin + * + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 . + * + * 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 program with the OpenSSL library. You + * must comply with the GNU General Public License in all respects + * for all of the code used other than as permitted herein. 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 in the license file. + * + * File: evmctl.c + * IMA/EVM control program + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "hash_info.h" +#include "pcr.h" +#include "utils.h" + +#ifndef XATTR_APPAARMOR_SUFFIX +#define XATTR_APPARMOR_SUFFIX "apparmor" +#define XATTR_NAME_APPARMOR XATTR_SECURITY_PREFIX XATTR_APPARMOR_SUFFIX +#endif + +#define USE_FPRINTF + +#include "imaevm.h" + +static char *evm_default_xattrs[] = { + XATTR_NAME_SELINUX, + XATTR_NAME_SMACK, + XATTR_NAME_APPARMOR, + XATTR_NAME_IMA, + XATTR_NAME_CAPS, + NULL +}; + +static char *evm_extra_smack_xattrs[] = { + XATTR_NAME_SELINUX, + XATTR_NAME_SMACK, + XATTR_NAME_SMACKEXEC, + XATTR_NAME_SMACKTRANSMUTE, + XATTR_NAME_SMACKMMAP, + XATTR_NAME_APPARMOR, + XATTR_NAME_IMA, + XATTR_NAME_CAPS, + NULL +}; + +static char **evm_config_xattrnames = evm_default_xattrs; + +struct command { + char *name; + int (*func)(struct command *cmd); + int cmd; + char *arg; + char *msg; /* extra info message */ +}; + +static int g_argc; +static char **g_argv; +static int xattr = 1; +static bool check_xattr; +static int sigdump; +static int digest; +static int digsig; +static int sigfile; +static char *uuid_str; +static char *ino_str; +static char *uid_str; +static char *gid_str; +static char *mode_str; +static char *generation_str; +static char *caps_str; +static char *ima_str; +static char *selinux_str; +static char *search_type; +static int verify_list_sig; +static int recursive; +static int msize; +static dev_t fs_dev; +static bool evm_immutable; +static bool evm_portable; + +#define HMAC_FLAG_NO_UUID 0x0001 +#define HMAC_FLAG_CAPS_SET 0x0002 + +static unsigned long hmac_flags; + +typedef int (*find_cb_t)(const char *path); +static int find(const char *path, int dts, find_cb_t func); + +#define REG_MASK (1 << DT_REG) +#define DIR_MASK (1 << DT_DIR) +#define LNK_MASK (1 << DT_LNK) +#define CHR_MASK (1 << DT_CHR) +#define BLK_MASK (1 << DT_BLK) + +struct command cmds[]; +static void print_usage(struct command *cmd); + +static const char *xattr_ima = "security.ima"; +static const char *xattr_evm = "security.evm"; + +struct tpm_bank_info { + int digest_size; + int supported; + const char *algo_name; + uint8_t digest[MAX_DIGEST_SIZE]; + uint8_t pcr[NUM_PCRS][MAX_DIGEST_SIZE]; +}; + +/* One --pcrs file per hash-algorithm/bank */ +#define MAX_PCRFILE 2 +static char *pcrfile[MAX_PCRFILE]; +static unsigned npcrfile; + +static int bin2file(const char *file, const char *ext, const unsigned char *data, int len) +{ + FILE *fp; + char name[strlen(file) + (ext ? strlen(ext) : 0) + 2]; + int err; + + if (ext) + sprintf(name, "%s.%s", file, ext); + else + sprintf(name, "%s", file); + + log_info("Writing to %s\n", name); + + fp = fopen(name, "w"); + if (!fp) { + log_err("Failed to open: %s\n", name); + return -1; + } + err = fwrite(data, len, 1, fp); + fclose(fp); + return err; +} + +static unsigned char *file2bin(const char *file, const char *ext, int *size) +{ + FILE *fp; + size_t len; + unsigned char *data; + char name[strlen(file) + (ext ? strlen(ext) : 0) + 2]; + struct stat stats; + + if (ext) + sprintf(name, "%s.%s", file, ext); + else + sprintf(name, "%s", file); + + log_info("Reading to %s\n", name); + + fp = fopen(name, "r"); + if (!fp) { + log_err("Failed to open: %s\n", name); + return NULL; + } + if (fstat(fileno(fp), &stats) == -1) { + log_err("Failed to fstat: %s (%s)\n", name, strerror(errno)); + fclose(fp); + return NULL; + } + len = stats.st_size; + + data = malloc(len); + if (!data) { + log_err("Failed to malloc %zu bytes: %s\n", len, name); + fclose(fp); + return NULL; + } + if (fread(data, len, 1, fp) != 1) { + log_err("Failed to fread %zu bytes: %s\n", len, name); + fclose(fp); + free(data); + return NULL; + } + fclose(fp); + + *size = (int)len; + return data; +} + +static int find_xattr(const char *list, int list_size, const char *xattr) +{ + int len; + + for (; list_size > 0; len++, list_size -= len, list += len) { + len = strlen(list); + if (!strcmp(list, xattr)) + return 1; + } + return 0; +} + +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +const char hex_asc[] = "0123456789abcdef"; + +/* this is faster than fprintf - makes sense? */ +static void bin2hex(uint8_t *buf, size_t buflen, FILE *stream) +{ + char asciihex[2]; + + for (; buflen--; buf++) { + asciihex[0] = hex_asc_hi(*buf); + asciihex[1] = hex_asc_lo(*buf); + fwrite(asciihex, 2, 1, stream); + } +} + +static int pack_uuid(const char *uuid_str, char *uuid) +{ + int i; + char *to = uuid; + + for (i = 0; i < 16; ++i) { + if (!uuid_str[0] || !uuid_str[1]) { + log_err("wrong UUID format\n"); + return -1; + } + *to++ = (hex_to_bin(*uuid_str) << 4) | + (hex_to_bin(*(uuid_str + 1))); + uuid_str += 2; + switch (i) { + case 3: + case 5: + case 7: + case 9: + if (*uuid_str != '-') { + log_err("wrong UUID format\n"); + return -1; + } + uuid_str++; + continue; + } + } + log_info("uuid: "); + log_dump(uuid, 16); + return 0; +} + +static int get_uuid(struct stat *st, char *uuid) +{ + uint32_t dev; + unsigned minor, major; + char path[PATH_MAX], _uuid[37]; + FILE *fp; + size_t len; + + if (uuid_str) + return pack_uuid(uuid_str, uuid); + + dev = st->st_dev; + major = (dev & 0xfff00) >> 8; + minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + + log_debug("dev: %u:%u\n", major, minor); + sprintf(path, "blkid -s UUID -o value /dev/block/%u:%u", major, minor); + + fp = popen(path, "r"); + if (!fp) + goto err; + + len = fread(_uuid, 1, sizeof(_uuid), fp); + pclose(fp); + if (len != sizeof(_uuid)) + goto err; + + return pack_uuid(_uuid, uuid); +err: + log_err("Failed to read UUID. Root access might require.\n"); + return -1; +} + +static int calc_evm_hash(const char *file, unsigned char *hash) +{ + const EVP_MD *md; + struct stat st; + int err; + uint32_t generation = 0; + EVP_MD_CTX *pctx; + unsigned int mdlen; + char **xattrname; + char xattr_value[1024]; + char list[1024]; + ssize_t list_size; + char uuid[16]; + struct h_misc_64 hmac_misc; + int hmac_size; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + EVP_MD_CTX ctx; + pctx = &ctx; +#else + pctx = EVP_MD_CTX_new(); +#endif + + if (lstat(file, &st)) { + log_err("Failed to stat: %s\n", file); + return -1; + } + + if (generation_str) + generation = strtoul(generation_str, NULL, 10); + if (ino_str) + st.st_ino = strtoul(ino_str, NULL, 10); + if (uid_str) + st.st_uid = strtoul(uid_str, NULL, 10); + if (gid_str) + st.st_gid = strtoul(gid_str, NULL, 10); + if (mode_str) + st.st_mode = strtoul(mode_str, NULL, 10); + + if (!evm_immutable) { + if ((S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) && !generation_str) { + /* we cannot at the momement to get generation of + special files kernel API does not support it */ + int fd = open(file, 0); + + if (fd < 0) { + log_err("Failed to open: %s\n", file); + return -1; + } + if (ioctl(fd, FS_IOC_GETVERSION, &generation)) { + log_err("ioctl() failed\n"); + close(fd); + return -1; + } + close(fd); + } + log_info("generation: %u\n", generation); + } + + list_size = llistxattr(file, list, sizeof(list)); + if (list_size < 0) { + log_err("llistxattr() failed\n"); + return -1; + } + + md = EVP_get_digestbyname(imaevm_params.hash_algo); + if (!md) { + log_err("EVP_get_digestbyname(%s) failed\n", + imaevm_params.hash_algo); + return 1; + } + + err = EVP_DigestInit(pctx, md); + if (!err) { + log_err("EVP_DigestInit() failed\n"); + return 1; + } + + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + if (!strcmp(*xattrname, XATTR_NAME_SELINUX) && selinux_str) { + err = strlen(selinux_str) + 1; + if (err > sizeof(xattr_value)) { + log_err("selinux[%u] value is too long to fit into xattr[%zu]\n", + err, sizeof(xattr_value)); + return -1; + } + strcpy(xattr_value, selinux_str); + } else if (!strcmp(*xattrname, XATTR_NAME_IMA) && ima_str) { + err = strlen(ima_str) / 2; + if (err > sizeof(xattr_value)) { + log_err("ima[%u] value is too long to fit into xattr[%zu]\n", + err, sizeof(xattr_value)); + return -1; + } + hex2bin(xattr_value, ima_str, err); + } else if (!strcmp(*xattrname, XATTR_NAME_CAPS) && (hmac_flags & HMAC_FLAG_CAPS_SET)) { + if (!caps_str) + continue; + err = strlen(caps_str); + if (err >= sizeof(xattr_value)) { + log_err("caps[%u] value is too long to fit into xattr[%zu]\n", + err + 1, sizeof(xattr_value)); + return -1; + } + strcpy(xattr_value, caps_str); + } else { + err = lgetxattr(file, *xattrname, xattr_value, sizeof(xattr_value)); + if (err < 0) { + log_info("no xattr: %s\n", *xattrname); + continue; + } + if (!find_xattr(list, list_size, *xattrname)) { + log_info("skipping xattr: %s\n", *xattrname); + continue; + } + } + /*log_debug("name: %s, value: %s, size: %d\n", *xattrname, xattr_value, err);*/ + log_info("name: %s, size: %d\n", *xattrname, err); + log_debug_dump(xattr_value, err); + err = EVP_DigestUpdate(pctx, xattr_value, err); + if (!err) { + log_err("EVP_DigestUpdate() failed\n"); + return 1; + } + } + + memset(&hmac_misc, 0, sizeof(hmac_misc)); + + if (evm_immutable) { + struct h_misc_digsig *hmac = (struct h_misc_digsig *)&hmac_misc; + + hmac_size = sizeof(*hmac); + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } else if (msize == 0) { + struct h_misc *hmac = (struct h_misc *)&hmac_misc; + + hmac_size = sizeof(*hmac); + if (!evm_portable) { + hmac->ino = st.st_ino; + hmac->generation = generation; + } + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } else if (msize == 64) { + struct h_misc_64 *hmac = (struct h_misc_64 *)&hmac_misc; + + hmac_size = sizeof(*hmac); + if (!evm_portable) { + hmac->ino = st.st_ino; + hmac->generation = generation; + } + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } else { + struct h_misc_32 *hmac = (struct h_misc_32 *)&hmac_misc; + + hmac_size = sizeof(*hmac); + if (!evm_portable) { + hmac->ino = st.st_ino; + hmac->generation = generation; + } + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } + + log_debug("hmac_misc (%d): ", hmac_size); + log_debug_dump(&hmac_misc, hmac_size); + + err = EVP_DigestUpdate(pctx, &hmac_misc, hmac_size); + if (!err) { + log_err("EVP_DigestUpdate() failed\n"); + return 1; + } + + if (!evm_immutable && !evm_portable && + !(hmac_flags & HMAC_FLAG_NO_UUID)) { + err = get_uuid(&st, uuid); + if (err) + return -1; + + err = EVP_DigestUpdate(pctx, (const unsigned char *)uuid, sizeof(uuid)); + if (!err) { + log_err("EVP_DigestUpdate() failed\n"); + return 1; + } + } + + err = EVP_DigestFinal(pctx, hash, &mdlen); + if (!err) { + log_err("EVP_DigestFinal() failed\n"); + return 1; + } + + return mdlen; +} + +static int sign_evm(const char *file, const char *key) +{ + unsigned char hash[MAX_DIGEST_SIZE]; + unsigned char sig[MAX_SIGNATURE_SIZE]; + int len, err; + + len = calc_evm_hash(file, hash); + if (len <= 1) + return len; + assert(len <= sizeof(hash)); + + len = sign_hash(imaevm_params.hash_algo, hash, len, key, NULL, sig + 1); + if (len <= 1) + return len; + assert(len < sizeof(sig)); + + /* add header */ + len++; + if (evm_portable) + sig[0] = EVM_XATTR_PORTABLE_DIGSIG; + else + sig[0] = EVM_IMA_XATTR_DIGSIG; + + if (evm_immutable) + sig[1] = 3; /* immutable signature version */ + + if (sigdump || imaevm_params.verbose >= LOG_INFO) + imaevm_hexdump(sig, len); + + if (xattr) { + err = lsetxattr(file, xattr_evm, sig, len, 0); + if (err < 0) { + log_err("setxattr failed: %s\n", file); + return err; + } + } + + return 0; +} + +static int hash_ima(const char *file) +{ + unsigned char hash[MAX_DIGEST_SIZE + 2]; /* +2 byte xattr header */ + int len, err, offset; + int algo = imaevm_get_hash_algo(imaevm_params.hash_algo); + + if (algo < 0) { + log_err("Unknown hash algo: %s\n", imaevm_params.hash_algo); + return -1; + } + if (algo > PKEY_HASH_SHA1) { + hash[0] = IMA_XATTR_DIGEST_NG; + hash[1] = algo; + offset = 2; + } else { + hash[0] = IMA_XATTR_DIGEST; + offset = 1; + } + + len = ima_calc_hash(file, hash + offset); + if (len <= 1) + return len; + assert(len + offset <= sizeof(hash)); + + len += offset; + + if (imaevm_params.verbose >= LOG_INFO) + log_info("hash(%s): ", imaevm_params.hash_algo); + + if (sigdump || imaevm_params.verbose >= LOG_INFO) + imaevm_hexdump(hash, len); + + if (xattr) { + err = lsetxattr(file, xattr_ima, hash, len, 0); + if (err < 0) { + log_err("setxattr failed: %s\n", file); + return err; + } + } + + return 0; +} + +static int sign_ima(const char *file, const char *key) +{ + unsigned char hash[MAX_DIGEST_SIZE]; + unsigned char sig[MAX_SIGNATURE_SIZE]; + int len, err; + + len = ima_calc_hash(file, hash); + if (len <= 1) + return len; + assert(len <= sizeof(hash)); + + len = sign_hash(imaevm_params.hash_algo, hash, len, key, NULL, sig + 1); + if (len <= 1) + return len; + assert(len < sizeof(sig)); + + /* add header */ + len++; + sig[0] = EVM_IMA_XATTR_DIGSIG; + + if (sigdump || imaevm_params.verbose >= LOG_INFO) + imaevm_hexdump(sig, len); + + if (sigfile) + bin2file(file, "sig", sig, len); + + if (xattr) { + err = lsetxattr(file, xattr_ima, sig, len, 0); + if (err < 0) { + log_err("setxattr failed: %s\n", file); + return err; + } + } + + return 0; +} + +static int get_file_type(const char *path, const char *search_type) +{ + int err, dts = 0, i; + struct stat st; + + for (i = 0; search_type[i]; i++) { + switch (search_type[i]) { + case 'f': + dts |= REG_MASK; break; + case 'd': + dts |= DIR_MASK; break; + case 's': + dts |= BLK_MASK | CHR_MASK | LNK_MASK; break; + case 'x': + check_xattr = true; break; + case 'm': + /* stay within the same filesystem*/ + err = lstat(path, &st); + if (err < 0) { + log_err("Failed to stat: %s\n", path); + return err; + } + fs_dev = st.st_dev; /* filesystem to start from */ + break; + } + } + + return dts; +} + +static int do_cmd(struct command *cmd, find_cb_t func) +{ + char *path = g_argv[optind++]; + int err, dts = REG_MASK; /* only regular files by default */ + + if (!path) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + if (recursive) { + if (search_type) { + dts = get_file_type(path, search_type); + if (dts < 0) + return dts; + } + err = find(path, dts, func); + } else { + err = func(path); + } + + return err; +} + +static int cmd_hash_ima(struct command *cmd) +{ + return do_cmd(cmd, hash_ima); +} + +static int sign_ima_file(const char *file) +{ + const char *key; + + key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem"; + + return sign_ima(file, key); +} + +static int cmd_sign_ima(struct command *cmd) +{ + return do_cmd(cmd, sign_ima_file); +} + +static int cmd_sign_hash(struct command *cmd) +{ + const char *key; + char *token, *line = NULL; + int hashlen = 0; + size_t line_len; + ssize_t len; + unsigned char hash[MAX_DIGEST_SIZE]; + unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03"; + int siglen; + + key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem"; + + /* support reading hash (eg. output of shasum) */ + while ((len = getline(&line, &line_len, stdin)) > 0) { + /* remove end of line */ + if (line[len - 1] == '\n') + line[--len] = '\0'; + + /* find the end of the hash */ + token = strpbrk(line, ", \t"); + hashlen = token ? token - line : strlen(line); + + assert(hashlen / 2 <= sizeof(hash)); + hex2bin(hash, line, hashlen / 2); + siglen = sign_hash(imaevm_params.hash_algo, hash, hashlen / 2, + key, NULL, sig + 1); + if (siglen <= 1) + return siglen; + assert(siglen < sizeof(sig)); + + fwrite(line, len, 1, stdout); + fprintf(stdout, " "); + bin2hex(sig, siglen + 1, stdout); + fprintf(stdout, "\n"); + } + + if (!hashlen) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + return 0; +} + +static int sign_evm_path(const char *file) +{ + const char *key; + int err; + + key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem"; + + if (digsig) { + err = sign_ima(file, key); + if (err) + return err; + } + + if (digest) { + err = hash_ima(file); + if (err) + return err; + } + + return sign_evm(file, key); +} + +static int cmd_sign_evm(struct command *cmd) +{ + return do_cmd(cmd, sign_evm_path); +} + +static int verify_evm(const char *file) +{ + unsigned char hash[MAX_DIGEST_SIZE]; + unsigned char sig[MAX_SIGNATURE_SIZE]; + int sig_hash_algo; + int mdlen; + int len; + + len = lgetxattr(file, xattr_evm, sig, sizeof(sig)); + if (len < 0) { + log_err("getxattr failed: %s\n", file); + return len; + } + + if (sig[0] != 0x03) { + log_err("%s has no signature\n", xattr_evm); + return -1; + } + + sig_hash_algo = imaevm_hash_algo_from_sig(sig + 1); + if (sig_hash_algo < 0) { + log_err("unknown hash algo: %s\n", file); + return -1; + } + imaevm_params.hash_algo = imaevm_hash_algo_by_id(sig_hash_algo); + + mdlen = calc_evm_hash(file, hash); + if (mdlen <= 1) + return mdlen; + assert(mdlen <= sizeof(hash)); + + return verify_hash(file, hash, mdlen, sig + 1, len - 1); +} + +static int cmd_verify_evm(struct command *cmd) +{ + char *file = g_argv[optind++]; + int err; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + if (imaevm_params.x509) { + if (imaevm_params.keyfile) /* Support multiple public keys */ + init_public_keys(imaevm_params.keyfile); + else /* assume read pubkey from x509 cert */ + init_public_keys("/etc/keys/x509_evm.der"); + } + + err = verify_evm(file); + if (!err && imaevm_params.verbose >= LOG_INFO) + log_info("%s: verification is OK\n", file); + return err; +} + +static int verify_ima(const char *file) +{ + unsigned char sig[MAX_SIGNATURE_SIZE]; + int len; + + if (sigfile) { + void *tmp = file2bin(file, "sig", &len); + + if (!tmp) { + log_err("Failed reading: %s\n", file); + return -1; + } + if (len > sizeof(sig)) { + log_err("Signature file is too big: %s\n", file); + free(tmp); + return -1; + } + memcpy(sig, tmp, len); + free(tmp); + } else { + len = lgetxattr(file, xattr_ima, sig, sizeof(sig)); + if (len < 0) { + log_err("getxattr failed: %s\n", file); + return len; + } + } + + return ima_verify_signature(file, sig, len, NULL, 0); +} + +static int cmd_verify_ima(struct command *cmd) +{ + char *file = g_argv[optind++]; + int err, fails = 0; + + if (imaevm_params.x509) { + if (imaevm_params.keyfile) /* Support multiple public keys */ + init_public_keys(imaevm_params.keyfile); + else /* assume read pubkey from x509 cert */ + init_public_keys("/etc/keys/x509_evm.der"); + } + + errno = 0; + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + do { + err = verify_ima(file); + if (err) + fails++; + if (!err && imaevm_params.verbose >= LOG_INFO) + log_info("%s: verification is OK\n", file); + } while ((file = g_argv[optind++])); + return fails > 0; +} + +static int cmd_convert(struct command *cmd) +{ + char *inkey; + unsigned char _pub[1024], *pub = _pub; + int len, err = 0; + char name[20]; + uint8_t keyid[8]; + RSA *key; + + imaevm_params.x509 = 0; + + inkey = g_argv[optind++]; + if (!inkey) { + inkey = imaevm_params.x509 ? "/etc/keys/x509_evm.der" : + "/etc/keys/pubkey_evm.pem"; + } + + key = read_pub_key(inkey, imaevm_params.x509); + if (!key) + return 1; + + len = key2bin(key, pub); + calc_keyid_v1(keyid, name, pub, len); + + bin2file(inkey, "bin", pub, len); + bin2file(inkey, "keyid", (const unsigned char *)name, strlen(name)); + + RSA_free(key); + return err; +} + +static int cmd_import(struct command *cmd) +{ + char *inkey, *ring = NULL; + unsigned char _pub[1024], *pub = _pub; + int id, len, err = 0; + char name[20]; + uint8_t keyid[8]; + + inkey = g_argv[optind++]; + if (!inkey) { + inkey = imaevm_params.x509 ? "/etc/keys/x509_evm.der" : + "/etc/keys/pubkey_evm.pem"; + } else + ring = g_argv[optind++]; + + id = KEY_SPEC_USER_KEYRING; /* default keyring */ + + if (ring) { + if (ring[0] != '@') { + int base = 10; + + if (ring[0] == '0' && ring[1] == 'x') + base = 16; + id = strtoul(ring, NULL, base); + } else { + if (strcmp(ring, "@t") == 0) + id = -1; + else if (strcmp(ring, "@p") == 0) + id = -2; + else if (strcmp(ring, "@s") == 0) + id = -3; + else if (strcmp(ring, "@u") == 0) + id = -4; + else if (strcmp(ring, "@us") == 0) + id = -5; + else if (strcmp(ring, "@g") == 0) + id = -6; + } + } + + if (imaevm_params.x509) { + EVP_PKEY *pkey = read_pub_pkey(inkey, imaevm_params.x509); + + if (!pkey) + return 1; + pub = file2bin(inkey, NULL, &len); + if (!pub) { + EVP_PKEY_free(pkey); + return 1; + } + calc_keyid_v2((uint32_t *)keyid, name, pkey); + EVP_PKEY_free(pkey); + } else { + RSA *key = read_pub_key(inkey, imaevm_params.x509); + + if (!key) + return 1; + len = key2bin(key, pub); + calc_keyid_v1(keyid, name, pub, len); + RSA_free(key); + } + + log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id); + + id = add_key(imaevm_params.x509 ? "asymmetric" : "user", + imaevm_params.x509 ? NULL : name, pub, len, id); + if (id < 0) { + log_err("add_key failed\n"); + err = id; + } else { + log_info("keyid: %d\n", id); + printf("%d\n", id); + } + if (imaevm_params.x509) + free(pub); + return err; +} + +static int setxattr_ima(const char *file, char *sig_file) +{ + unsigned char *sig; + int len, err; + + if (sig_file) + sig = file2bin(sig_file, NULL, &len); + else + sig = file2bin(file, "sig", &len); + if (!sig) + return 0; + + err = lsetxattr(file, xattr_ima, sig, len, 0); + if (err < 0) + log_err("setxattr failed: %s\n", file); + free(sig); + return err; +} + +static int cmd_setxattr_ima(struct command *cmd) +{ + char *file, *sig = NULL; + + if (sigfile) + sig = g_argv[optind++]; + file = g_argv[optind++]; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + return setxattr_ima(file, sig); +} + +#define MAX_KEY_SIZE 128 + +static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *hash) +{ + const EVP_MD *md; + struct stat st; + int err = -1; + uint32_t generation = 0; + HMAC_CTX *pctx; + unsigned int mdlen; + char **xattrname; + unsigned char xattr_value[1024]; + unsigned char *key; + int keylen; + unsigned char evmkey[MAX_KEY_SIZE]; + char list[1024]; + ssize_t list_size; + struct h_misc_64 hmac_misc; + int hmac_size; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + HMAC_CTX ctx; + pctx = &ctx; +#else + pctx = HMAC_CTX_new(); +#endif + + key = file2bin(keyfile, NULL, &keylen); + if (!key) { + log_err("Failed to read a key: %s\n", keyfile); + return -1; + } + + if (keylen > sizeof(evmkey)) { + log_err("key is too long: %d\n", keylen); + goto out; + } + + /* EVM key is 128 bytes */ + memcpy(evmkey, key, keylen); + memset(evmkey + keylen, 0, sizeof(evmkey) - keylen); + + if (lstat(file, &st)) { + log_err("Failed to stat: %s\n", file); + goto out; + } + + if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) { + /* we cannot at the momement to get generation of special files.. + * kernel API does not support it */ + int fd = open(file, 0); + + if (fd < 0) { + log_err("Failed to open %s\n", file); + goto out; + } + if (ioctl(fd, FS_IOC_GETVERSION, &generation)) { + log_err("ioctl() failed\n"); + close(fd); + goto out; + } + close(fd); + } + + log_info("generation: %u\n", generation); + + list_size = llistxattr(file, list, sizeof(list)); + if (list_size <= 0) { + log_err("llistxattr() failed: %s\n", file); + goto out; + } + + md = EVP_get_digestbyname(imaevm_params.hash_algo); + if (!md) { + log_err("EVP_get_digestbyname(%s) failed\n", + imaevm_params.hash_algo); + goto out; + } + + err = !HMAC_Init_ex(pctx, evmkey, sizeof(evmkey), md, NULL); + if (err) { + log_err("HMAC_Init() failed\n"); + goto out; + } + + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + err = lgetxattr(file, *xattrname, xattr_value, sizeof(xattr_value)); + if (err < 0) { + log_info("no xattr: %s\n", *xattrname); + continue; + } + if (!find_xattr(list, list_size, *xattrname)) { + log_info("skipping xattr: %s\n", *xattrname); + continue; + } + /*log_debug("name: %s, value: %s, size: %d\n", *xattrname, xattr_value, err);*/ + log_info("name: %s, size: %d\n", *xattrname, err); + log_debug_dump(xattr_value, err); + err = !HMAC_Update(pctx, xattr_value, err); + if (err) { + log_err("HMAC_Update() failed\n"); + goto out_ctx_cleanup; + } + } + + memset(&hmac_misc, 0, sizeof(hmac_misc)); + + if (msize == 0) { + struct h_misc *hmac = (struct h_misc *)&hmac_misc; + + hmac_size = sizeof(*hmac); + hmac->ino = st.st_ino; + hmac->generation = generation; + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } else if (msize == 64) { + struct h_misc_64 *hmac = (struct h_misc_64 *)&hmac_misc; + + hmac_size = sizeof(*hmac); + hmac->ino = st.st_ino; + hmac->generation = generation; + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } else { + struct h_misc_32 *hmac = (struct h_misc_32 *)&hmac_misc; + + hmac_size = sizeof(*hmac); + hmac->ino = st.st_ino; + hmac->generation = generation; + hmac->uid = st.st_uid; + hmac->gid = st.st_gid; + hmac->mode = st.st_mode; + } + + log_debug("hmac_misc (%d): ", hmac_size); + log_debug_dump(&hmac_misc, hmac_size); + + err = !HMAC_Update(pctx, (const unsigned char *)&hmac_misc, hmac_size); + if (err) { + log_err("HMAC_Update() failed\n"); + goto out_ctx_cleanup; + } + err = !HMAC_Final(pctx, hash, &mdlen); + if (err) + log_err("HMAC_Final() failed\n"); +out_ctx_cleanup: +#if OPENSSL_VERSION_NUMBER < 0x10100000 + HMAC_CTX_cleanup(pctx); +#else + HMAC_CTX_free(pctx); +#endif +out: + free(key); + return err ?: mdlen; +} + +static int hmac_evm(const char *file, const char *key) +{ + unsigned char hash[MAX_DIGEST_SIZE]; + unsigned char sig[MAX_SIGNATURE_SIZE]; + int len, err; + + len = calc_evm_hmac(file, key, hash); + if (len <= 1) + return len; + assert(len <= sizeof(hash)); + + log_info("hmac: "); + log_dump(hash, len); + assert(len < sizeof(sig)); + memcpy(sig + 1, hash, len); + + if (xattr) { + sig[0] = EVM_XATTR_HMAC; + err = lsetxattr(file, xattr_evm, sig, len + 1, 0); + if (err < 0) { + log_err("setxattr failed: %s\n", file); + return err; + } + } + + return 0; +} + +static int cmd_hmac_evm(struct command *cmd) +{ + const char *key, *file = g_argv[optind++]; + int err; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem"; + + if (digsig) { + err = sign_ima(file, key); + if (err) + return err; + } + + if (digest) { + err = hash_ima(file); + if (err) + return err; + } + + return hmac_evm(file, "/etc/keys/evm-key-plain"); +} + +static int ima_fix(const char *path) +{ + int fd, size, len, ima = 0, evm = 0; + char buf[1024], *list = buf; + + log_info("%s\n", path); + + if (check_xattr) { + /* re-measuring takes a time + * in some cases we can skip labeling if xattrs exists + */ + size = llistxattr(path, list, sizeof(buf)); + if (size < 0) { + log_errno("Failed to read xattrs (llistxattr): %s\n", path); + return -1; + } + for (; size > 0; len++, size -= len, list += len) { + len = strlen(list); + if (!strcmp(list, xattr_ima)) + ima = 1; + else if (!strcmp(list, xattr_evm)) + evm = 1; + } + if (ima && evm) + return 0; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + log_errno("Failed to open file: %s", path); + return -1; + } + + close(fd); + + return 0; +} + +static int find(const char *path, int dts, find_cb_t func) +{ + struct dirent *de; + DIR *dir; + + if (fs_dev) { + struct stat st; + int err = lstat(path, &st); + + if (err < 0) { + log_err("Failed to stat: %s\n", path); + return err; + } + if (st.st_dev != fs_dev) + return 0; + } + + dir = opendir(path); + if (!dir) { + log_err("Failed to open directory %s\n", path); + return -1; + } + + if (fchdir(dirfd(dir))) { + log_err("Failed to chdir %s\n", path); + return -1; + } + + while ((de = readdir(dir))) { + if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) + continue; + log_debug("path: %s, type: %u\n", de->d_name, de->d_type); + if (de->d_type == DT_DIR) + find(de->d_name, dts, func); + else if (dts & (1 << de->d_type)) + func(de->d_name); + } + + if (chdir("..")) { + log_err("Failed to chdir: %s\n", path); + return -1; + } + + if (dts & DIR_MASK) + func(path); + + closedir(dir); + + return 0; +} + +static int cmd_ima_fix(struct command *cmd) +{ + return do_cmd(cmd, ima_fix); +} + +static int ima_clear(const char *path) +{ + log_info("%s\n", path); + lremovexattr(path, xattr_ima); + lremovexattr(path, xattr_evm); + + return 0; +} + +static int cmd_ima_clear(struct command *cmd) +{ + return do_cmd(cmd, ima_clear); +} + +#define TCG_EVENT_NAME_LEN_MAX 255 + +struct template_entry { + struct { + uint32_t pcr; + uint8_t digest[SHA_DIGEST_LENGTH]; + uint32_t name_len; + } header __packed; + char name[TCG_EVENT_NAME_LEN_MAX + 1]; + uint32_t template_buf_len; + uint32_t template_len; + uint8_t *template; +}; + +static uint8_t zero[MAX_DIGEST_SIZE]; + +static int ignore_violations = 0; + +static int ima_verify_template_hash(struct template_entry *entry) +{ + uint8_t digest[SHA_DIGEST_LENGTH]; + static int line = 0; + + line++; + + if (!memcmp(zero, entry->header.digest, sizeof(digest))) + return 0; + + SHA1(entry->template, entry->template_len, digest); + + if (memcmp(digest, entry->header.digest, sizeof(digest))) { + if (imaevm_params.verbose > LOG_INFO) + log_info("Failed to verify template data digest(line %d).\n", + line); + return 1; + } + + return 0; +} + +void ima_show(struct template_entry *entry) +{ + if (imaevm_params.verbose <= LOG_INFO) + return; + + log_info("%d ", entry->header.pcr); + log_dump_n(entry->header.digest, sizeof(entry->header.digest)); + log_info(" %s ", entry->name); + log_dump_n(entry->template, SHA_DIGEST_LENGTH); + log_info(" %s\n", entry->template + SHA_DIGEST_LENGTH); +} + +/* + * Keep track of unknown or malformed template names. + * + * Return 1 for found, return 0 for not found. + */ +static int lookup_template_name_entry(char *template_name) +{ + struct template_name_entry { + struct template_name_entry *next; + char name[]; + } *entry; + static struct template_name_entry *template_names = NULL; + + for (entry = template_names; entry != NULL; entry = entry->next) { + if (strcmp(entry->name, template_name) == 0) + return 1; + } + + entry = malloc(sizeof(struct template_name_entry) + + strlen(template_name) + 1); + if (entry) { + strcpy(entry->name, template_name); + entry->next = template_names; + template_names = entry; + } + return 0; +} + +void ima_ng_show(struct template_entry *entry) +{ + uint8_t *fieldp = entry->template; + uint32_t field_len; + int total_len = entry->template_len, digest_len, len, sig_len, fbuf_len; + uint8_t *digest, *sig = NULL, *fbuf = NULL; + char *algo, *path; + int found; + int err; + + /* get binary digest */ + field_len = *(uint32_t *)fieldp; + fieldp += sizeof(field_len); + total_len -= sizeof(field_len); + + algo = (char *)fieldp; + len = strlen(algo) + 1; + digest_len = field_len - len; + digest = fieldp + len; + + /* move to next field */ + fieldp += field_len; + total_len -= field_len; + + /* get path */ + field_len = *(uint32_t *)fieldp; + fieldp += sizeof(field_len); + total_len -= sizeof(field_len); + + path = (char *)fieldp; + + /* move to next field */ + fieldp += field_len; + total_len -= field_len; + + if (!strcmp(entry->name, "ima-sig")) { + /* get signature */ + field_len = *(uint32_t *)fieldp; + fieldp += sizeof(field_len); + total_len -= sizeof(field_len); + + if (field_len) { + sig = fieldp; + sig_len = field_len; + + /* move to next field */ + fieldp += field_len; + total_len -= field_len; + } + } else if (!strcmp(entry->name, "ima-buf")) { + field_len = *(uint32_t *)fieldp; + fieldp += sizeof(field_len); + total_len -= sizeof(field_len); + if (field_len) { + fbuf = fieldp; + fbuf_len = field_len; + + /* move to next field */ + fieldp += field_len; + total_len -= field_len; + } + } + + /* ascii_runtime_measurements */ + if (imaevm_params.verbose > LOG_INFO) { + log_info("%d ", entry->header.pcr); + log_dump_n(entry->header.digest, sizeof(entry->header.digest)); + log_info(" %s %s", entry->name, algo); + log_dump_n(digest, digest_len); + log_info(" %s", path); + if (fbuf) { + log_info(" "); + log_dump_n(fbuf, fbuf_len); + } + } + + if (sig) { + if (imaevm_params.verbose > LOG_INFO) { + log_info(" "); + log_dump(sig, sig_len); + } + if (verify_list_sig) + err = ima_verify_signature(path, sig, sig_len, + digest, digest_len); + else + err = ima_verify_signature(path, sig, sig_len, NULL, 0); + if (!err && imaevm_params.verbose > LOG_INFO) + log_info("%s: verification is OK\n", path); + } else { + if (imaevm_params.verbose > LOG_INFO) + log_info("\n"); + } + + if (total_len) { + found = lookup_template_name_entry(entry->name); + if (!found) + log_err("Template \"%s\" contains unprocessed data: " + "%d bytes\n", entry->name, total_len); + } +} + +static void set_bank_info(struct tpm_bank_info *bank, const char *algo_name) +{ + const EVP_MD *md; + + bank->algo_name = algo_name; + md = EVP_get_digestbyname(bank->algo_name); + if (!md) + return; + + bank->supported = 1; + bank->digest_size = EVP_MD_size(md); +} + +static struct tpm_bank_info *init_tpm_banks(int *num_banks) +{ + struct tpm_bank_info *banks = NULL; + const char *default_algos[] = {"sha1", "sha256"}; + int num_algos = sizeof(default_algos) / sizeof(default_algos[0]); + int i, j; + + banks = calloc(num_algos, sizeof(struct tpm_bank_info)); + if (!banks) + return banks; + + /* re-calculate the PCRs digests for only known algorithms */ + *num_banks = num_algos; + for (i = 0; i < num_algos; i++) { + for (j = 0; j < HASH_ALGO__LAST; j++) { + if (!strcmp(default_algos[i], hash_algo_name[j])) + set_bank_info(&banks[i], hash_algo_name[j]); + } + } + return banks; +} + +/* + * Compare the calculated TPM PCR banks against the PCR values read. + * On failure to match any TPM bank, fail comparison. + */ +static int compare_tpm_banks(int num_banks, struct tpm_bank_info *bank, + struct tpm_bank_info *tpm_bank) +{ + int i, j; + int ret = 0; + + for (i = 0; i < num_banks; i++) { + if (!bank[i].supported || !tpm_bank[i].supported) + continue; + for (j = 0; j < NUM_PCRS; j++) { + if (memcmp(bank[i].pcr[j], zero, bank[i].digest_size) + == 0) + continue; + + if (memcmp(bank[i].pcr[j], tpm_bank[i].pcr[j], + bank[i].digest_size) != 0) + ret = 1; + + if ((!ret && imaevm_params.verbose <= LOG_INFO) || + (ret && imaevm_params.verbose <= LOG_DEBUG)) + continue; + + log_info("%s: PCRAgg %d: ", bank[i].algo_name, j); + log_dump(bank[i].pcr[j], bank[i].digest_size); + + log_info("%s: TPM PCR-%d: ", tpm_bank[i].algo_name, j); + log_dump(tpm_bank[i].pcr[j], tpm_bank[i].digest_size); + + if (!ret) + log_info("%s PCR-%d: succeed\n", + bank[i].algo_name, j); + else + log_info("%s: PCRAgg %d does not match TPM PCR-%d\n", + bank[i].algo_name, j, j); + } + } + return ret; +} + +/* Calculate the template hash for a particular hash algorithm */ +static int calculate_template_digest(EVP_MD_CTX *pctx, const EVP_MD *md, + struct template_entry *entry, + struct tpm_bank_info *bank) +{ + unsigned int mdlen; + int err; + + err = EVP_DigestInit(pctx, md); + if (!err) { + printf("EVP_DigestInit() failed\n"); + goto out; + } + + err = EVP_DigestUpdate(pctx, entry->template, entry->template_len); + if (!err) { + printf("EVP_DigestUpdate() failed\n"); + goto out; + } + + err = EVP_DigestFinal(pctx, bank->digest, &mdlen); + if (!err) + printf("EVP_DigestUpdate() failed\n"); +out: + if (!err) + err = 1; + return err; +} + +/* Extend a specific TPM bank with the template hash */ +static int extend_tpm_bank(EVP_MD_CTX *pctx, const EVP_MD *md, + struct template_entry *entry, + struct tpm_bank_info *bank) +{ + unsigned int mdlen; + int err; + + err = EVP_DigestInit(pctx, md); + if (!err) { + printf("EVP_DigestInit() failed\n"); + goto out; + } + + err = EVP_DigestUpdate(pctx, bank->pcr[entry->header.pcr], + bank->digest_size); + if (!err) { + printf("EVP_DigestUpdate() failed\n"); + goto out; + } + + err = EVP_DigestUpdate(pctx, bank->digest, bank->digest_size); + if (!err) { + printf("EVP_DigestUpdate() failed\n"); + goto out; + } + + err = EVP_DigestFinal(pctx, bank->pcr[entry->header.pcr], &mdlen); + if (!err) + printf("EVP_DigestFinal() failed\n"); + +out: + if (!err) + err = 1; + return err; +} + +/* Calculate and extend the template hash for multiple hash algorithms */ +static void extend_tpm_banks(struct template_entry *entry, int num_banks, + struct tpm_bank_info *bank, + struct tpm_bank_info *padded_bank) +{ + EVP_MD_CTX *pctx; + const EVP_MD *md; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + EVP_MD_CTX ctx; + pctx = &ctx; +#else + pctx = EVP_MD_CTX_new(); +#endif + int err; + int i; + + for (i = 0; i < num_banks; i++) { + if (!bank[i].supported) + continue; + md = EVP_get_digestbyname(bank[i].algo_name); + if (!md) { + printf("EVP_get_digestbyname(%s) failed\n", + bank[i].algo_name); + bank[i].supported = 0; + continue; + } + + /* + * Measurement violations are 0x00 digests, which are extended + * into the TPM as 0xff. Verifying the IMA measurement list + * will fail, unless the 0x00 digests are converted to 0xff's. + * + * Initially the sha1 digest, including violations, was padded + * with zeroes before being extended into the TPM. With the + * per TPM bank digest, violations are the full per bank digest + * size. + */ + if (memcmp(entry->header.digest, zero, SHA_DIGEST_LENGTH) == 0) { + if (!ignore_violations) { + memset(bank[i].digest, 0x00, bank[i].digest_size); + memset(padded_bank[i].digest, 0x00, padded_bank[i].digest_size); + } else { + memset(bank[i].digest, 0xff, + bank[i].digest_size); + + memset(padded_bank[i].digest, 0x00, + padded_bank[i].digest_size); + memset(padded_bank[i].digest, 0xff, + SHA_DIGEST_LENGTH); + } + } else { + err = calculate_template_digest(pctx, md, entry, + &bank[i]); + if (!err) { + bank[i].supported = 0; + continue; + } + + /* + * calloc set the memory to zero, so just copy the + * sha1 digest. + */ + memcpy(padded_bank[i].digest, entry->header.digest, + SHA_DIGEST_LENGTH); + } + + /* extend TPM BANK with template digest */ + err = extend_tpm_bank(pctx, md, entry, &bank[i]); + if (!err) + bank[i].supported = 0; + + /* extend TPM BANK with zero padded sha1 template digest */ + err = extend_tpm_bank(pctx, md, entry, &padded_bank[i]); + if (!err) + padded_bank[i].supported = 0; + } +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + EVP_MD_CTX_free(pctx); +#endif +} + +static int read_one_bank(struct tpm_bank_info *tpm_bank, FILE *fp) +{ + char *p, pcr_str[8], buf[MAX_DIGEST_SIZE * 2 + 8]; + int i = 0; + int result = -1; + for (;;) { + p = fgets(buf, sizeof(buf), fp); + if (!p || i >= NUM_PCRS) + break; + sprintf(pcr_str, "PCR-%2.2d", i); + if (!strncmp(p, pcr_str, 6)) + hex2bin(tpm_bank->pcr[i++], p + 7, tpm_bank->digest_size); + result = 0; + } + return result; +} + +static char *pcrs = "/sys/class/tpm/tpm0/device/pcrs"; /* Kernels >= 4.0 */ +static char *misc_pcrs = "/sys/class/misc/tpm0/device/pcrs"; + +/* Read one of the TPM 1.2 sysfs files if present */ +static int read_sysfs_pcrs(int num_banks, struct tpm_bank_info *tpm_banks) +{ + FILE *fp; + int i, result; + + fp = fopen(pcrs, "r"); + if (!fp) + fp = fopen(misc_pcrs, "r"); + if (!fp) + return -1; + + result = read_one_bank(&tpm_banks[0], fp); + fclose(fp); + if (result < 0) + return result; + tpm_banks[0].supported = 1; + for (i = 1; i < num_banks; i++) + tpm_banks[i].supported = 0; + return 0; + +} + +/* Read PCRs from per-bank file(s) specified via --pcrs */ +static int read_file_pcrs(int num_banks, struct tpm_bank_info *tpm_banks) +{ + struct stat s; + FILE *fp; + char *p; + const char *alg, *path; + int i, j, bank, result; + + for (i = 0; i < num_banks; i++) + tpm_banks[i].supported = 0; + + for (i = 0; i < npcrfile; i++) { + p = strchr(pcrfile[i], ','); + if (p) { + *p = 0; + alg = pcrfile[i]; + path = ++p; + } else { + alg = "sha1"; + path = pcrfile[i]; + } + + bank = -1; + for (j = 0; j < num_banks; j++) { + if (!strcmp(tpm_banks[j].algo_name, alg)) { + bank = j; + break; + } + } + if (bank < 0) { + log_err("Unknown algorithm '%s'\n", alg); + return -1; + } + + if (stat(path, &s) == -1) { + log_err("Could not stat '%s'\n", path); + return -1; + } + + if (!S_ISREG(s.st_mode)) { + log_err("PCR file: not a regular file or link to regular file\n"); + return -1; + } + + fp = fopen(path, "r"); + if (!fp) { + log_err("Could not open '%s'\n", path); + return -1; + } + + result = read_one_bank(&tpm_banks[bank], fp); + fclose(fp); + if (result < 0) + return result; + tpm_banks[bank].supported = 1; + } + + return 0; +} + +/* + * Attempt to read TPM PCRs from either TPM 1.2 or multiple TPM 2.0 banks. + * + * On success reading from any TPM bank, return 0. + */ +static int read_tpm_banks(int num_banks, struct tpm_bank_info *bank) +{ + int tpm_enabled = 0; + char *errmsg = NULL; + int i, j; + int err; + + /* If --pcrs was specified, read only from the specified file(s) */ + if (npcrfile) + return read_file_pcrs(num_banks, bank); + + /* Else try reading PCRs from the sysfs file if present */ + if (read_sysfs_pcrs(num_banks, bank) == 0) + return 0; + + /* Any userspace applications available for reading TPM 2.0 PCRs? */ + if (!tpm2_pcr_supported()) { + log_debug("Failed to read TPM 2.0 PCRs\n"); + return 1; + } + + /* Read PCRs from multiple TPM 2.0 banks */ + for (i = 0; i < num_banks; i++) { + err = 0; + for (j = 0; j < NUM_PCRS && !err; j++) { + err = tpm2_pcr_read(bank[i].algo_name, j, + bank[i].pcr[j], bank[i].digest_size, + &errmsg); + if (err) { + log_debug("Failed to read %s PCRs: (%s)\n", + bank[i].algo_name, errmsg); + free(errmsg); + bank[i].supported = 0; + } + } + if (bank[i].supported) + tpm_enabled = 1; + } + return tpm_enabled ? 0 : 1; +} + +static int ima_measurement(const char *file) +{ + struct tpm_bank_info *pseudo_padded_banks; + struct tpm_bank_info *pseudo_banks; + struct tpm_bank_info *tpm_banks; + int is_ima_template, cur_template_fmt; + int num_banks = 0; + int tpmbanks = 1; + int first_record = 1; + + struct template_entry entry = { .template = 0 }; + FILE *fp; + int invalid_template_digest = 0; + int err_padded = -1; + int err = -1; + + errno = 0; + memset(zero, 0, MAX_DIGEST_SIZE); + + pseudo_padded_banks = init_tpm_banks(&num_banks); + pseudo_banks = init_tpm_banks(&num_banks); + tpm_banks = init_tpm_banks(&num_banks); + + fp = fopen(file, "rb"); + if (!fp) { + log_err("Failed to open measurement file: %s\n", file); + return -1; + } + + if (imaevm_params.keyfile) /* Support multiple public keys */ + init_public_keys(imaevm_params.keyfile); + else /* assume read pubkey from x509 cert */ + init_public_keys("/etc/keys/x509_evm.der"); + + /* + * Reading the PCRs before walking the IMA measurement list + * guarantees that all of the measurements are included in + * the PCRs. + */ + if (read_tpm_banks(num_banks, tpm_banks) != 0) + tpmbanks = 0; + + while (fread(&entry.header, sizeof(entry.header), 1, fp)) { + if (entry.header.name_len > TCG_EVENT_NAME_LEN_MAX) { + log_err("%d ERROR: event name too long!\n", + entry.header.name_len); + fclose(fp); + exit(1); + } + + memset(entry.name, 0x00, sizeof(entry.name)); + if (!fread(entry.name, entry.header.name_len, 1, fp)) { + log_err("Unable to read template name\n"); + goto out; + } + + /* + * The "ima" template format can not be mixed with other + * template formats records. + */ + if (!first_record) { + cur_template_fmt = strcmp(entry.name, "ima") == 0 ? 1 : 0; + if ((is_ima_template && !cur_template_fmt) || + (!is_ima_template && cur_template_fmt)) { + log_err("Mixed measurement list containing \"ima\" and other template formats not supported.\n"); + goto out; + } + } else { + first_record = 0; + is_ima_template = strcmp(entry.name, "ima") == 0 ? 1 : 0; + } + + /* The "ima" template data is not length prefixed. Skip it. */ + if (!is_ima_template) { + if (!fread(&entry.template_len, + sizeof(entry.template_len), 1, fp)) { + log_err("Unable to read template length\n"); + goto out; + } + } else { + entry.template_len = SHA_DIGEST_LENGTH + + TCG_EVENT_NAME_LEN_MAX + 1; + } + + if (entry.template_buf_len < entry.template_len) { + free(entry.template); + entry.template_buf_len = entry.template_len; + entry.template = malloc(entry.template_len); + } + + if (!is_ima_template) { + if (!fread(entry.template, entry.template_len, 1, fp)) { + log_errno("Unable to read template\n"); + goto out; + } + } else { + uint32_t field_len; + uint32_t len; + + /* + * The "ima" template data format is digest, + * filename length, filename. + */ + if (!fread(entry.template, SHA_DIGEST_LENGTH, 1, fp)) { + log_errno("Unable to read file data hash\n"); + goto out; + } + + /* + * Read the filename length, but it isn't included + * in the template data hash calculation. + */ + len = fread(&field_len, sizeof(field_len), 1, fp); + if (len <= 0) { + log_errno("Failed reading file name length\n"); + goto out; + } + if (field_len > TCG_EVENT_NAME_LEN_MAX) { + log_err("file pathname is too long\n"); + goto out; + } + + len = fread(entry.template + SHA_DIGEST_LENGTH, + field_len, 1, fp); + if (len != 1) { + log_errno("Failed reading file name\n"); + goto out; + } + + /* + * The template data is fixed sized, zero out + * the remaining memory. + */ + len = SHA_DIGEST_LENGTH + field_len; + memset(entry.template + len, 0x00, + entry.template_buf_len - len); + } + + extend_tpm_banks(&entry, num_banks, pseudo_banks, + pseudo_padded_banks); + + /* Recalculate and verify template data digest */ + err = ima_verify_template_hash(&entry); + if (err) + invalid_template_digest = 1; + + if (is_ima_template) + ima_show(&entry); + else + ima_ng_show(&entry); + + if (!tpmbanks) + continue; + + /* The measurement list might contain too many entries, + * compare the re-calculated TPM PCR values after each + * extend. + */ + err = compare_tpm_banks(num_banks, pseudo_banks, tpm_banks); + if (!err) + break; + + /* Compare against original SHA1 zero padded TPM PCR values */ + err_padded = compare_tpm_banks(num_banks, pseudo_padded_banks, + tpm_banks); + if (!err_padded) + break; + } + + if (tpmbanks == 0) + log_info("Failed to read any TPM PCRs\n"); + else { + if (!err) + log_info("Matched per TPM bank calculated digest(s).\n"); + else if (!err_padded) { + log_info("Matched SHA1 padded TPM digest(s).\n"); + err = 0; + } else + log_info("Failed to match per TPM bank or SHA1 padded TPM digest(s).\n"); + } + + if (invalid_template_digest) { + log_info("Failed to verify template data digest.\n"); + err = 1; + } + +out: + fclose(fp); + return err; +} + +static int cmd_ima_measurement(struct command *cmd) +{ + char *file = g_argv[optind++]; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + return ima_measurement(file); +} + +#define MAX_EVENT_DATA_SIZE 200000 +static int read_binary_bios_measurements(char *file, struct tpm_bank_info *bank) +{ + struct { + struct { + uint32_t pcr; + int type; + unsigned char digest[SHA_DIGEST_LENGTH]; + uint32_t len; + } header; + unsigned char data[MAX_EVENT_DATA_SIZE]; + } event; + struct stat s; + FILE *fp; + SHA_CTX c; + int err = 0; + int len; + int i; + + if (stat(file, &s) == -1) { + errno = 0; + return 1; + } + + if (!S_ISREG(s.st_mode)) { + log_info("Bios event log: not a regular file or link to regular file\n"); + return 1; + } + + fp = fopen(file, "r"); + if (!fp) { + log_errno("Failed to open TPM 1.2 event log.\n"); + return 1; + } + + if (imaevm_params.verbose > LOG_INFO) + log_info("Reading the TPM 1.2 event log %s.\n", file); + + /* Extend the pseudo TPM PCRs with the event digest */ + while (fread(&event, sizeof(event.header), 1, fp)) { + if (imaevm_params.verbose > LOG_INFO) { + log_info("%02u ", event.header.pcr); + log_dump(event.header.digest, SHA_DIGEST_LENGTH); + } + if (event.header.pcr > NUM_PCRS) { + log_err("Invalid PCR %d.\n", event.header.pcr); + err = 1; + break; + } + SHA1_Init(&c); + SHA1_Update(&c, bank->pcr[event.header.pcr], 20); + SHA1_Update(&c, event.header.digest, 20); + SHA1_Final(bank->pcr[event.header.pcr], &c); + if (event.header.len > MAX_EVENT_DATA_SIZE) { + log_err("Event data event too long.\n"); + err = 1; + break; + } + len = fread(event.data, event.header.len, 1, fp); + if (len != 1) { + log_errno("Failed reading event data (short read)\n"); + break; + } + } + fclose(fp); + + if (imaevm_params.verbose <= LOG_INFO) + return err; + + for (i = 0; i < NUM_PCRS; i++) { + log_info("PCR-%2.2x ", i); + log_dump(bank->pcr[i], SHA_DIGEST_LENGTH); + + } + return err; +} + +static void calc_bootaggr(struct tpm_bank_info *bank) +{ + EVP_MD_CTX *pctx; + unsigned int mdlen; + const EVP_MD *md; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + EVP_MD_CTX ctx; + pctx = &ctx; +#else + pctx = EVP_MD_CTX_new(); +#endif + int err = 0; + int i; + + md = EVP_get_digestbyname(bank->algo_name); + + err = EVP_DigestInit(pctx, md); + if (!err) { + printf("EVP_DigestInit() failed\n"); + goto out; + } + + for (i = 0; i < 8; i++) { + err = EVP_DigestUpdate(pctx, bank->pcr[i], bank->digest_size); + if (!err) { + log_err("EVP_DigestUpdate() failed\n"); + goto out; + } + } + + if (strcmp(bank->algo_name, "sha1") != 0) { + for (i = 8; i < 10; i++) { + err = EVP_DigestUpdate(pctx, bank->pcr[i], bank->digest_size); + if (!err) { + log_err("EVP_DigestUpdate() failed\n"); + goto out; + } + } + } + + err = EVP_DigestFinal(pctx, bank->digest, &mdlen); + if (!err) { + log_err("EVP_DigestFinal() failed\n"); + goto out; + } + +out: +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + EVP_MD_CTX_free(pctx); +#endif + return; +} + +/* + * The "boot_aggregate" format is the TPM PCR bank algorithm, a colon + * separator, followed by a per bank TPM PCR bank specific digest. + * Store the TPM PCR bank specific "boot_aggregate" value as a newline + * terminated string in the provided buffer. + */ +static int append_bootaggr(char *bootaggr, struct tpm_bank_info *tpm_banks) +{ + uint8_t *buf; + int j; + + strcpy(bootaggr, tpm_banks->algo_name); + j = strlen(tpm_banks->algo_name); + bootaggr[j++] = ':'; + + for (buf = tpm_banks->digest; + buf < (tpm_banks->digest + tpm_banks->digest_size); + buf++) { + bootaggr[j++] = hex_asc_hi(*buf); + bootaggr[j++] = hex_asc_lo(*buf); + } + + bootaggr[j++] = '\n'; + return j; +} + +/* + * The IMA measurement list boot_aggregate is the link between the preboot + * event log and the IMA measurement list. Read and calculate all the + * possible per TPM bank boot_aggregate digests based on the existing PCRs + * 0 - 9 to validate against the IMA boot_aggregate record. If the digest + * algorithm is SHA1, only PCRs 0 - 7 are considered to avoid ambiguity. + */ +static int cmd_ima_bootaggr(struct command *cmd) +{ + struct tpm_bank_info *tpm_banks; + int bootaggr_len = 0; + char *bootaggr; + int num_banks = 0; + int offset = 0; + int err = 0; + int i; + + char *file = g_argv[optind++]; + + /* + * Instead of just reading the TPM 1.2 PCRs, walk the exported + * TPM 1.2 SHA1 event log, calculating the PCRs. + */ + if (file) { + tpm_banks = init_tpm_banks(&num_banks); + + /* TPM 1.2 only supports SHA1.*/ + for (i = 1; i < num_banks; i++) + tpm_banks[i].supported = 0; + + err = read_binary_bios_measurements(file, tpm_banks); + if (err) { + log_info("Failed reading the TPM 1.2 event log %s.\n", + file); + return -1; + } + } else { + tpm_banks = init_tpm_banks(&num_banks); + if (read_tpm_banks(num_banks, tpm_banks) != 0) { + log_info("Failed to read any TPM PCRs\n"); + return -1; + } + } + + /* + * Allocate enough memory for the per TPM 2.0 PCR bank algorithm, + * the colon separator, the boot_aggregate digest and newline. + * + * Format: :\n ... + */ + for (i = 0; i < num_banks; i++) { + if (!tpm_banks[i].supported) + continue; + bootaggr_len += strlen(tpm_banks[i].algo_name) + 1; + bootaggr_len += (tpm_banks[i].digest_size * 2) + 1; + } + /* Make room for the trailing null */ + bootaggr = malloc(bootaggr_len + 1); + + /* + * Calculate and convert the per TPM 2.0 PCR bank algorithm + * "boot_aggregate" digest from binary to asciihex. Store the + * "boot_aggregate" values as a list of newline terminated + * strings. + */ + for (i = 0; i < num_banks; i++) { + if (!tpm_banks[i].supported) + continue; + calc_bootaggr(&tpm_banks[i]); + offset += append_bootaggr(bootaggr + offset, tpm_banks + i); + } + bootaggr[bootaggr_len] = '\0'; + printf("%s", bootaggr); + free(bootaggr); + return 0; +} + +static void print_usage(struct command *cmd) +{ + printf("usage: %s %s\n", cmd->name, cmd->arg ? cmd->arg : ""); +} + +static void print_full_usage(struct command *cmd) +{ + if (cmd->name) + printf("usage: %s %s\n", cmd->name, cmd->arg ? cmd->arg : ""); + if (cmd->msg) + printf("%s", cmd->msg); +} + +static int print_command_usage(struct command *cmds, char *command) +{ + struct command *cmd; + + for (cmd = cmds; cmd->name; cmd++) { + if (strcmp(cmd->name, command) == 0) { + print_full_usage(cmd); + return 0; + } + } + printf("invalid command: %s\n", command); + return -1; +} + +static void print_all_usage(struct command *cmds) +{ + struct command *cmd; + + printf("commands:\n"); + + for (cmd = cmds; cmd->name; cmd++) { + if (cmd->arg) + printf(" %s %s\n", cmd->name, cmd->arg); + else if (cmd->msg) + printf(" %s", cmd->msg); + } +} + +static int call_command(struct command *cmds, char *command) +{ + struct command *cmd; + + for (cmd = cmds; cmd->name; cmd++) { + if (strcasecmp(cmd->name, command) == 0) + return cmd->func(cmd); + } + printf("Invalid command: %s\n", command); + return -1; +} + +static int cmd_help(struct command *cmd) +{ + if (!g_argv[optind]) { + print_usage(cmd); + return 0; + } else + return print_command_usage(cmds, g_argv[optind]); +} + +static void usage(void) +{ + printf("Usage: evmctl [-v] [OPTIONS]\n"); + + print_all_usage(cmds); + + printf( + "\n" + " -a, --hashalgo sha1 (default), sha224, sha256, sha384, sha512, streebog256, streebog512\n" + " -s, --imasig make IMA signature\n" + " -d, --imahash make IMA hash\n" + " -f, --sigfile store IMA signature in .sig file instead of xattr\n" + " --xattr-user store xattrs in user namespace (for testing purposes)\n" + " --rsa use RSA key type and signing scheme v1\n" + " -k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem)\n" + " -o, --portable generate portable EVM signatures\n" + " -p, --pass password for encrypted signing key\n" + " -r, --recursive recurse into directories (sign)\n" + " -t, --type file types to fix 'fdsxm' (f: file, d: directory, s: block/char/symlink)\n" + " x - skip fixing if both ima and evm xattrs exist (use with caution)\n" + " m - stay on the same filesystem (like 'find -xdev')\n" + " -n print result to stdout instead of setting xattr\n" + " -u, --uuid use custom FS UUID for EVM (unspecified: from FS, empty: do not use)\n" + " --smack use extra SMACK xattrs for EVM\n" + " --m32 force EVM hmac/signature for 32 bit target system\n" + " --m64 force EVM hmac/signature for 64 bit target system\n" + " --ino use custom inode for EVM\n" + " --uid use custom UID for EVM\n" + " --gid use custom GID for EVM\n" + " --mode use custom Mode for EVM\n" + " --generation use custom Generation for EVM(unspecified: from FS, empty: use 0)\n" + " --ima use custom IMA signature for EVM\n" + " --selinux use custom Selinux label for EVM\n" + " --caps use custom Capabilities for EVM(unspecified: from FS, empty: do not use)\n" + " --verify-sig verify measurement list signatures\n" + " --engine e preload OpenSSL engine e (such as: gost)\n" + " --ignore-violations ignore ToMToU measurement violations\n" + " -v increase verbosity level\n" + " -h, --help display this help and exit\n" + "\n"); +} + +struct command cmds[] = { + {"--version", NULL, 0, ""}, + {"help", cmd_help, 0, ""}, + {"import", cmd_import, 0, "[--rsa] pubkey keyring", "Import public key into the keyring.\n"}, + {"convert", cmd_convert, 0, "key", "convert public key into the keyring.\n"}, + {"sign", cmd_sign_evm, 0, "[-r] [--imahash | --imasig ] [--key key] [--pass [password] file", "Sign file metadata.\n"}, + {"verify", cmd_verify_evm, 0, "file", "Verify EVM signature (for debugging).\n"}, + {"ima_sign", cmd_sign_ima, 0, "[--sigfile] [--key key] [--pass [password] file", "Make file content signature.\n"}, + {"ima_verify", cmd_verify_ima, 0, "file", "Verify IMA signature (for debugging).\n"}, + {"ima_setxattr", cmd_setxattr_ima, 0, "[--sigfile file]", "Set IMA signature from sigfile\n"}, + {"ima_hash", cmd_hash_ima, 0, "file", "Make file content hash.\n"}, + {"ima_measurement", cmd_ima_measurement, 0, "[--ignore-violations] [--verify-sig [--key key1, key2, ...]] [--pcrs [hash-algorithm,]file [--pcrs hash-algorithm,file] ...] file", "Verify measurement list (experimental).\n"}, + {"ima_boot_aggregate", cmd_ima_bootaggr, 0, "[--pcrs hash-algorithm,file] [TPM 1.2 BIOS event log]", "Calculate per TPM bank boot_aggregate digests\n"}, + {"ima_fix", cmd_ima_fix, 0, "[-t fdsxm] path", "Recursively fix IMA/EVM xattrs in fix mode.\n"}, + {"ima_clear", cmd_ima_clear, 0, "[-t fdsxm] path", "Recursively remove IMA/EVM xattrs.\n"}, + {"sign_hash", cmd_sign_hash, 0, "[--key key] [--pass [password]", "Sign hashes from shaXsum output.\n"}, +#ifdef DEBUG + {"hmac", cmd_hmac_evm, 0, "[--imahash | --imasig ] file", "Sign file metadata with HMAC using symmetric key (for testing purpose).\n"}, +#endif + {0, 0, 0, NULL} +}; + +static struct option opts[] = { + {"help", 0, 0, 'h'}, + {"imasig", 0, 0, 's'}, + {"imahash", 0, 0, 'd'}, + {"hashalgo", 1, 0, 'a'}, + {"pass", 2, 0, 'p'}, + {"sigfile", 0, 0, 'f'}, + {"uuid", 2, 0, 'u'}, + {"rsa", 0, 0, '1'}, + {"key", 1, 0, 'k'}, + {"type", 1, 0, 't'}, + {"recursive", 0, 0, 'r'}, + {"m32", 0, 0, '3'}, + {"m64", 0, 0, '6'}, + {"portable", 0, 0, 'o'}, + {"smack", 0, 0, 128}, + {"version", 0, 0, 129}, + {"inode", 1, 0, 130}, + {"uid", 1, 0, 131}, + {"gid", 1, 0, 132}, + {"mode", 1, 0, 133}, + {"generation", 1, 0, 134}, + {"ima", 1, 0, 135}, + {"selinux", 1, 0, 136}, + {"caps", 2, 0, 137}, + {"verify-sig", 0, 0, 138}, + {"engine", 1, 0, 139}, + {"xattr-user", 0, 0, 140}, + {"ignore-violations", 0, 0, 141}, + {"pcrs", 1, 0, 142}, + {} + +}; + +static char *get_password(void) +{ + struct termios flags, tmp_flags; + char *password, *pwd; + int passlen = 64; + + password = malloc(passlen); + if (!password) { + perror("malloc"); + return NULL; + } + + tcgetattr(fileno(stdin), &flags); + tmp_flags = flags; + tmp_flags.c_lflag &= ~ECHO; + tmp_flags.c_lflag |= ECHONL; + + if (tcsetattr(fileno(stdin), TCSANOW, &tmp_flags) != 0) { + perror("tcsetattr"); + free(password); + return NULL; + } + + printf("PEM password: "); + pwd = fgets(password, passlen, stdin); + + /* restore terminal */ + if (tcsetattr(fileno(stdin), TCSANOW, &flags) != 0) { + perror("tcsetattr"); + free(password); + return NULL; + } + + return pwd; +} + +int main(int argc, char *argv[]) +{ + int err = 0, c, lind; + ENGINE *eng = NULL; + +#if !(OPENSSL_VERSION_NUMBER < 0x10100000) + OPENSSL_init_crypto( +#ifndef DISABLE_OPENSSL_CONF + OPENSSL_INIT_LOAD_CONFIG | +#endif + OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL); +#endif + g_argv = argv; + g_argc = argc; + + while (1) { + c = getopt_long(argc, argv, "hvnsda:op::fu::k:t:ri", opts, &lind); + if (c == -1) + break; + + switch (c) { + case 'h': + usage(); + exit(0); + break; + case 'v': + imaevm_params.verbose++; + break; + case 'd': + digest = 1; + break; + case 's': + digsig = 1; + break; + case 'n': + /* do not set Extended Attributes... just print signature */ + xattr = 0; + sigdump = 1; + break; + case 'a': + imaevm_params.hash_algo = optarg; + break; + case 'p': + if (optarg) + imaevm_params.keypass = optarg; + else + imaevm_params.keypass = get_password(); + break; + case 'f': + sigfile = 1; + break; + case 'u': + uuid_str = optarg; + if (!uuid_str) + hmac_flags |= HMAC_FLAG_NO_UUID; + break; + case '1': + imaevm_params.x509 = 0; + break; + case 'k': + imaevm_params.keyfile = optarg; + break; + case 'i': + if (evm_portable) + log_err("Portable and immutable options are exclusive, ignoring immutable option."); + else + evm_immutable = true; + break; + case 'o': + if (evm_immutable) + log_err("Portable and immutable options are exclusive, ignoring portable option."); + else + evm_portable = true; + break; + case 't': + search_type = optarg; + break; + case 'r': + recursive = 1; + break; + case '3': + msize = 32; + break; + case '6': + msize = 64; + break; + case 128: + evm_config_xattrnames = evm_extra_smack_xattrs; + break; + case 129: + printf("evmctl %s\n", VERSION); + exit(0); + break; + case 130: + ino_str = optarg; + break; + case 131: + uid_str = optarg; + break; + case 132: + gid_str = optarg; + break; + case 133: + mode_str = optarg; + break; + case 134: + generation_str = optarg; + break; + case 135: + ima_str = optarg; + break; + case 136: + selinux_str = optarg; + break; + case 137: + caps_str = optarg; + hmac_flags |= HMAC_FLAG_CAPS_SET; + break; + case 138: + verify_list_sig = 1; + break; + case 139: /* --engine e */ + eng = ENGINE_by_id(optarg); + if (!eng) { + log_err("engine %s isn't available\n", optarg); + ERR_print_errors_fp(stderr); + } else if (!ENGINE_init(eng)) { + log_err("engine %s init failed\n", optarg); + ERR_print_errors_fp(stderr); + ENGINE_free(eng); + eng = NULL; + } + ENGINE_set_default(eng, ENGINE_METHOD_ALL); + break; + case 140: /* --xattr-user */ + xattr_ima = "user.ima"; + xattr_evm = "user.evm"; + break; + case 141: /* --ignore-violations */ + ignore_violations = 1; + break; + case 142: + if (npcrfile >= MAX_PCRFILE) { + log_err("too many --pcrfile options\n"); + exit(1); + } + pcrfile[npcrfile++] = optarg; + break; + case '?': + exit(1); + break; + default: + log_err("getopt() returned: %d (%c)\n", c, c); + } + } + + if (argv[optind] == NULL) + usage(); + else + err = call_command(cmds, argv[optind++]); + + if (err) { + unsigned long error; + + if (errno) + log_err("errno: %s (%d)\n", strerror(errno), errno); + for (;;) { + error = ERR_get_error(); + if (!error) + break; + log_err("%s\n", ERR_error_string(error, NULL)); + } + if (err < 0) + err = 125; + } + + if (eng) { + ENGINE_finish(eng); + ENGINE_free(eng); +#if OPENSSL_API_COMPAT < 0x10100000L + ENGINE_cleanup(); +#endif + } + ERR_free_strings(); + EVP_cleanup(); + BIO_free(NULL); + return err; +} diff --git a/src/hash_info.gen b/src/hash_info.gen new file mode 100755 index 0000000..5f7a97f --- /dev/null +++ b/src/hash_info.gen @@ -0,0 +1,92 @@ +#!/bin/sh +# +# Generate hash_info.h from kernel headers +# +# Copyright (C) 2018 +# +# 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 2, 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. + +KERNEL_HEADERS=$1 +HASH_INFO_H=uapi/linux/hash_info.h +HASH_INFO=$KERNEL_HEADERS/include/$HASH_INFO_H + +TMPHASHINFO="./tmp_hash_info.h" +gen_hashinfo() { +cat << __EOF__ >$TMPHASHINFO +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Hash Info: Hash algorithms information + * + * Copyright (c) 2013 Dmitry Kasatkin + * + * 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 2 of the License, or (at your option) + * any later version. + * + */ + +enum hash_algo { + HASH_ALGO_MD4, + HASH_ALGO_MD5, + HASH_ALGO_SHA1, + HASH_ALGO_RIPE_MD_160, + HASH_ALGO_SHA256, + HASH_ALGO_SHA384, + HASH_ALGO_SHA512, + HASH_ALGO_SHA224, + HASH_ALGO_RIPE_MD_128, + HASH_ALGO_RIPE_MD_256, + HASH_ALGO_RIPE_MD_320, + HASH_ALGO_WP_256, + HASH_ALGO_WP_384, + HASH_ALGO_WP_512, + HASH_ALGO_TGR_128, + HASH_ALGO_TGR_160, + HASH_ALGO_TGR_192, + HASH_ALGO_SM3_256, + HASH_ALGO__LAST +}; +__EOF__ +} + +# Allow to specify kernel-headers past include/ +if [ ! -e $HASH_INFO ]; then + HASH_INFO2=$KERNEL_HEADERS/$HASH_INFO_H + if [ -e $HASH_INFO2 ]; then + HASH_INFO=$HASH_INFO2 + else + gen_hashinfo + HASH_INFO="$TMPHASHINFO" + fi +fi + +if [ ! -e $HASH_INFO ]; then + echo "/* $HASH_INFO is not found */" + HASH_INFO=/dev/null +else + echo "/* $HASH_INFO is found */" +fi + +echo "enum hash_algo {" +grep HASH_ALGO_.*, $HASH_INFO +printf "\tHASH_ALGO__LAST\n" +echo "};" + +echo "const char *const hash_algo_name[HASH_ALGO__LAST] = {" +sed -n 's/HASH_ALGO_\(.*\),/\1 \L\1\E/p' $HASH_INFO | \ + while read a b; do + # Normalize text hash name: if it contains underscore between + # digits replace it with a dash, other underscores are removed. + b=$(echo "$b" | sed "s/\([0-9]\)_\([0-9]\)/\1-\2/g;s/_//g") + printf '\t%-26s = "%s",\n' "[HASH_ALGO_$a]" "$b" + done +echo "};" diff --git a/src/imaevm.h b/src/imaevm.h new file mode 100644 index 0000000..4503919 --- /dev/null +++ b/src/imaevm.h @@ -0,0 +1,229 @@ +/* + * ima-evm-utils - IMA/EVM support utilities + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011,2012,2013 Intel Corporation + * Copyright (C) 2013,2014 Samsung Electronics + * + * Authors: + * Dmitry Kasatkin + * + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 . + * + * 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 program with the OpenSSL library. You + * must comply with the GNU General Public License in all respects + * for all of the code used other than as permitted herein. 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 in the license file. + * + * File: imaevm.h + * IMA/EVM header file + */ + +#ifndef _LIBIMAEVM_H +#define _LIBIMAEVM_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_FPRINTF +#define do_log(level, fmt, args...) \ + ({ if (level <= imaevm_params.verbose) fprintf(stderr, fmt, ##args); }) +#define do_log_dump(level, p, len, cr) \ + ({ if (level <= imaevm_params.verbose) imaevm_do_hexdump(stderr, p, len, cr); }) +#else +#define do_log(level, fmt, args...) syslog(level, fmt, ##args) +#define do_log_dump(level, p, len, cr) +#endif + +#ifdef DEBUG +#define log_debug(fmt, args...) do_log(LOG_DEBUG, "%s:%d " fmt, __func__ , __LINE__ , ##args) +#define log_debug_dump(p, len) do_log_dump(LOG_DEBUG, p, len, true) +#define log_debug_dump_n(p, len) do_log_dump(LOG_DEBUG, p, len, false) +#else +#define log_debug(fmt, args...) +#define log_debug_dump(p, len) +#endif + +#define log_dump(p, len) do_log_dump(LOG_INFO, p, len, true) +#define log_dump_n(p, len) do_log_dump(LOG_INFO, p, len, false) +#define log_info(fmt, args...) do_log(LOG_INFO, fmt, ##args) +#define log_err(fmt, args...) do_log(LOG_ERR, fmt, ##args) +#define log_errno(fmt, args...) do_log(LOG_ERR, fmt ": errno: %s (%d)\n", ##args, strerror(errno), errno) + +#define DATA_SIZE 4096 +#define SHA1_HASH_LEN 20 + +#define MAX_DIGEST_SIZE 64 +#define MAX_SIGNATURE_SIZE 1024 + +#define __packed __attribute__((packed)) + +enum evm_ima_xattr_type { + IMA_XATTR_DIGEST = 0x01, + EVM_XATTR_HMAC, + EVM_IMA_XATTR_DIGSIG, + IMA_XATTR_DIGEST_NG, + EVM_XATTR_PORTABLE_DIGSIG, +}; + +struct h_misc { + unsigned long ino; + uint32_t generation; + uid_t uid; + gid_t gid; + unsigned short mode; +}; + +struct h_misc_32 { + uint32_t ino; + uint32_t generation; + uid_t uid; + gid_t gid; + unsigned short mode; +}; + +struct h_misc_64 { + uint64_t ino; + uint32_t generation; + uid_t uid; + gid_t gid; + unsigned short mode; +}; + +struct h_misc_digsig { + uid_t uid; + gid_t gid; + unsigned short mode; +}; + +enum pubkey_algo { + PUBKEY_ALGO_RSA, + PUBKEY_ALGO_MAX, +}; + +enum digest_algo { + DIGEST_ALGO_SHA1, + DIGEST_ALGO_SHA256, + DIGEST_ALGO_MAX +}; + +enum digsig_version { + DIGSIG_VERSION_1 = 1, + DIGSIG_VERSION_2 +}; + +struct pubkey_hdr { + uint8_t version; /* key format version */ + uint32_t timestamp; /* key made, always 0 for now */ + uint8_t algo; + uint8_t nmpi; + char mpi[0]; +} __packed; + +struct signature_hdr { + uint8_t version; /* signature format version */ + uint32_t timestamp; /* signature made */ + uint8_t algo; + uint8_t hash; + uint8_t keyid[8]; + uint8_t nmpi; + char mpi[0]; +} __packed; + +/* reflect enum hash_algo from include/uapi/linux/hash_info.h */ +enum pkey_hash_algo { + PKEY_HASH_MD4, + PKEY_HASH_MD5, + PKEY_HASH_SHA1, + PKEY_HASH_RIPE_MD_160, + PKEY_HASH_SHA256, + PKEY_HASH_SHA384, + PKEY_HASH_SHA512, + PKEY_HASH_SHA224, + PKEY_HASH_RIPE_MD_128, + PKEY_HASH_RIPE_MD_256, + PKEY_HASH_RIPE_MD_320, + PKEY_HASH_WP_256, + PKEY_HASH_WP_384, + PKEY_HASH_WP_512, + PKEY_HASH_TGR_128, + PKEY_HASH_TGR_160, + PKEY_HASH_TGR_192, + PKEY_HASH_SM3_256, + PKEY_HASH_STREEBOG_256, + PKEY_HASH_STREEBOG_512, + PKEY_HASH__LAST +}; + +/* + * signature format v2 - for using with asymmetric keys + */ +struct signature_v2_hdr { + uint8_t version; /* signature format version */ + uint8_t hash_algo; /* Digest algorithm [enum pkey_hash_algo] */ + uint32_t keyid; /* IMA key identifier - not X509/PGP specific*/ + uint16_t sig_size; /* signature size */ + uint8_t sig[0]; /* signature payload */ +} __packed; + +struct libimaevm_params { + int verbose; + int x509; + const char *hash_algo; + const char *keyfile; + const char *keypass; +}; + +struct RSA_ASN1_template { + const uint8_t *data; + size_t size; +}; + +#define NUM_PCRS 24 +#define DEFAULT_PCR 10 + +extern struct libimaevm_params imaevm_params; + +void imaevm_do_hexdump(FILE *fp, const void *ptr, int len, bool cr); +void imaevm_hexdump(const void *ptr, int len); +int ima_calc_hash(const char *file, uint8_t *hash); +int imaevm_get_hash_algo(const char *algo); +RSA *read_pub_key(const char *keyfile, int x509); +EVP_PKEY *read_pub_pkey(const char *keyfile, int x509); + +void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len); +void calc_keyid_v2(uint32_t *keyid, char *str, EVP_PKEY *pkey); +int key2bin(RSA *key, unsigned char *pub); + +int sign_hash(const char *algo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig); +int verify_hash(const char *file, const unsigned char *hash, int size, unsigned char *sig, int siglen); +int ima_verify_signature(const char *file, unsigned char *sig, int siglen, unsigned char *digest, int digestlen); +void init_public_keys(const char *keyfiles); +int imaevm_hash_algo_from_sig(unsigned char *sig); +const char *imaevm_hash_algo_by_id(int algo); + +#endif diff --git a/src/libimaevm.c b/src/libimaevm.c new file mode 100644 index 0000000..fa6c278 --- /dev/null +++ b/src/libimaevm.c @@ -0,0 +1,995 @@ +/* + * ima-evm-utils - IMA/EVM support utilities + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011,2012,2013 Intel Corporation + * Copyright (C) 2013,2014 Samsung Electronics + * + * Authors: + * Dmitry Kasatkin + * + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 . + * + * 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 program with the OpenSSL library. You + * must comply with the GNU General Public License in all respects + * for all of the code used other than as permitted herein. 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 in the license file. + * + * File: libimaevm.c + * IMA/EVM library + */ + +/* should we use logger instead for library? */ +#define USE_FPRINTF + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "imaevm.h" +#include "hash_info.h" + +/* Names that are primary for OpenSSL. */ +static const char *const pkey_hash_algo[PKEY_HASH__LAST] = { + [PKEY_HASH_MD4] = "md4", + [PKEY_HASH_MD5] = "md5", + [PKEY_HASH_SHA1] = "sha1", + [PKEY_HASH_RIPE_MD_160] = "rmd160", + [PKEY_HASH_SHA256] = "sha256", + [PKEY_HASH_SHA384] = "sha384", + [PKEY_HASH_SHA512] = "sha512", + [PKEY_HASH_SHA224] = "sha224", + [PKEY_HASH_SM3_256] = "sm3", + [PKEY_HASH_STREEBOG_256] = "md_gost12_256", + [PKEY_HASH_STREEBOG_512] = "md_gost12_512", +}; + +/* Names that are primary for the kernel. */ +static const char *const pkey_hash_algo_kern[PKEY_HASH__LAST] = { + [PKEY_HASH_STREEBOG_256] = "streebog256", + [PKEY_HASH_STREEBOG_512] = "streebog512", +}; + +struct libimaevm_params imaevm_params = { + .verbose = LOG_INFO, + .x509 = 1, + .hash_algo = "sha1", +}; + +static void __attribute__ ((constructor)) libinit(void); + +void imaevm_do_hexdump(FILE *fp, const void *ptr, int len, bool cr) +{ + int i; + uint8_t *data = (uint8_t *) ptr; + + for (i = 0; i < len; i++) + fprintf(fp, "%02x", data[i]); + if (cr) + fprintf(fp, "\n"); +} + +void imaevm_hexdump(const void *ptr, int len) +{ + imaevm_do_hexdump(stdout, ptr, len, true); +} + +const char *imaevm_hash_algo_by_id(int algo) +{ + if (algo < PKEY_HASH__LAST) + return pkey_hash_algo[algo]; + if (algo < HASH_ALGO__LAST) + return hash_algo_name[algo]; + + log_err("digest %d not found\n", algo); + return NULL; +} + +/* Output all remaining openssl error messages. */ +static void output_openssl_errors(void) +{ + while (ERR_peek_error()) { + char buf[256]; + /* buf must be at least 256 bytes long according to man */ + + ERR_error_string(ERR_get_error(), buf); + log_err("openssl: %s\n", buf); + } +} + +static int add_file_hash(const char *file, EVP_MD_CTX *ctx) +{ + uint8_t *data; + int err = -1, bs = DATA_SIZE; + off_t size, len; + FILE *fp; + struct stat stats; + + fp = fopen(file, "r"); + if (!fp) { + log_err("Failed to open: %s\n", file); + return -1; + } + + data = malloc(bs); + if (!data) { + log_err("malloc failed\n"); + goto out; + } + + if (fstat(fileno(fp), &stats) == -1) { + log_err("Failed to fstat: %s (%s)\n", file, strerror(errno)); + goto out; + } + + for (size = stats.st_size; size; size -= len) { + len = MIN(size, bs); + if (!fread(data, len, 1, fp)) { + if (ferror(fp)) { + log_err("fread() failed\n\n"); + goto out; + } + break; + } + if (!EVP_DigestUpdate(ctx, data, len)) { + log_err("EVP_DigestUpdate() failed\n"); + err = 1; + goto out; + } + } + err = 0; +out: + fclose(fp); + free(data); + + return err; +} + +static int add_dir_hash(const char *file, EVP_MD_CTX *ctx) +{ + int err; + struct dirent *de; + DIR *dir; + unsigned long long ino, off; + unsigned int type; + int result = 0; + + dir = opendir(file); + if (!dir) { + log_err("Failed to open: %s\n", file); + return -1; + } + + while ((de = readdir(dir))) { + ino = de->d_ino; + off = de->d_off; + type = de->d_type; + log_debug("entry: %s, ino: %llu, type: %u, off: %llu, reclen: %hu\n", + de->d_name, ino, type, off, de->d_reclen); + err = EVP_DigestUpdate(ctx, de->d_name, strlen(de->d_name)); + /*err |= EVP_DigestUpdate(ctx, &off, sizeof(off));*/ + err |= EVP_DigestUpdate(ctx, &ino, sizeof(ino)); + err |= EVP_DigestUpdate(ctx, &type, sizeof(type)); + if (!err) { + log_err("EVP_DigestUpdate() failed\n"); + output_openssl_errors(); + result = 1; + break; + } + } + + closedir(dir); + + return result; +} + +static int add_link_hash(const char *path, EVP_MD_CTX *ctx) +{ + int err; + char buf[1024]; + + err = readlink(path, buf, sizeof(buf)); + if (err <= 0) + return -1; + + log_info("link: %s -> %.*s\n", path, err, buf); + return !EVP_DigestUpdate(ctx, buf, err); +} + +static int add_dev_hash(struct stat *st, EVP_MD_CTX *ctx) +{ + uint32_t dev = st->st_rdev; + unsigned major = (dev & 0xfff00) >> 8; + unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + + log_info("device: %u:%u\n", major, minor); + return !EVP_DigestUpdate(ctx, &dev, sizeof(dev)); +} + +int ima_calc_hash(const char *file, uint8_t *hash) +{ + const EVP_MD *md; + struct stat st; + EVP_MD_CTX *pctx; + unsigned int mdlen; + int err; +#if OPENSSL_VERSION_NUMBER < 0x10100000 + EVP_MD_CTX ctx; + pctx = &ctx; +#else + pctx = EVP_MD_CTX_new(); +#endif + + /* Need to know the file length */ + err = lstat(file, &st); + if (err < 0) { + log_err("Failed to stat: %s\n", file); + goto err; + } + + md = EVP_get_digestbyname(imaevm_params.hash_algo); + if (!md) { + log_err("EVP_get_digestbyname(%s) failed\n", + imaevm_params.hash_algo); + err = 1; + goto err; + } + + err = EVP_DigestInit(pctx, md); + if (!err) { + log_err("EVP_DigestInit() failed\n"); + err = 1; + goto err; + } + + switch (st.st_mode & S_IFMT) { + case S_IFREG: + err = add_file_hash(file, pctx); + break; + case S_IFDIR: + err = add_dir_hash(file, pctx); + break; + case S_IFLNK: + err = add_link_hash(file, pctx); + break; + case S_IFIFO: case S_IFSOCK: + case S_IFCHR: case S_IFBLK: + err = add_dev_hash(&st, pctx); + break; + default: + log_errno("Unsupported file type"); + err = -1; + goto err; + } + + if (err) + goto err; + + err = EVP_DigestFinal(pctx, hash, &mdlen); + if (!err) { + log_err("EVP_DigestFinal() failed\n"); + err = 1; + goto err; + } + err = mdlen; +err: + if (err == 1) + output_openssl_errors(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + EVP_MD_CTX_free(pctx); +#endif + return err; +} + +EVP_PKEY *read_pub_pkey(const char *keyfile, int x509) +{ + FILE *fp; + EVP_PKEY *pkey = NULL; + + if (!keyfile) + return NULL; + + fp = fopen(keyfile, "r"); + if (!fp) { + if (imaevm_params.verbose > LOG_INFO) + log_info("Failed to open keyfile: %s\n", keyfile); + return NULL; + } + + if (x509) { + X509 *crt = d2i_X509_fp(fp, NULL); + + if (!crt) { + log_err("Failed to d2i_X509_fp key file: %s\n", + keyfile); + goto out; + } + pkey = X509_extract_key(crt); + X509_free(crt); + if (!pkey) { + log_err("Failed to X509_extract_key key file: %s\n", + keyfile); + goto out; + } + } else { + pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (!pkey) + log_err("Failed to PEM_read_PUBKEY key file: %s\n", + keyfile); + } + +out: + if (!pkey) + output_openssl_errors(); + fclose(fp); + return pkey; +} + +RSA *read_pub_key(const char *keyfile, int x509) +{ + EVP_PKEY *pkey; + RSA *key; + + pkey = read_pub_pkey(keyfile, x509); + if (!pkey) + return NULL; + key = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_free(pkey); + if (!key) { + log_err("read_pub_key: unsupported key type\n"); + output_openssl_errors(); + return NULL; + } + return key; +} + +static int verify_hash_v1(const char *file, const unsigned char *hash, int size, + unsigned char *sig, int siglen, const char *keyfile) +{ + int err, len; + SHA_CTX ctx; + unsigned char out[1024]; + RSA *key; + unsigned char sighash[20]; + struct signature_hdr *hdr = (struct signature_hdr *)sig; + + log_info("hash-v1: "); + log_dump(hash, size); + + key = read_pub_key(keyfile, 0); + if (!key) + return 1; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, hash, size); + SHA1_Update(&ctx, hdr, sizeof(*hdr)); + SHA1_Final(sighash, &ctx); + log_info("sighash: "); + log_dump(sighash, sizeof(sighash)); + + err = RSA_public_decrypt(siglen - sizeof(*hdr) - 2, sig + sizeof(*hdr) + 2, out, key, RSA_PKCS1_PADDING); + RSA_free(key); + if (err < 0) { + log_err("%s: RSA_public_decrypt() failed: %d\n", file, err); + output_openssl_errors(); + return 1; + } + + len = err; + + if (len != sizeof(sighash) || memcmp(out, sighash, len) != 0) { + log_err("%s: verification failed: %d\n", file, err); + return -1; + } + + return 0; +} + +struct public_key_entry { + struct public_key_entry *next; + uint32_t keyid; + char name[9]; + EVP_PKEY *key; +}; +static struct public_key_entry *public_keys = NULL; + +static EVP_PKEY *find_keyid(uint32_t keyid) +{ + struct public_key_entry *entry, *tail = public_keys; + int i = 1; + + for (entry = public_keys; entry != NULL; entry = entry->next) { + if (entry->keyid == keyid) + return entry->key; + i++; + tail = entry; + } + + /* add unknown keys to list */ + entry = calloc(1, sizeof(struct public_key_entry)); + if (!entry) { + perror("calloc"); + return 0; + } + entry->keyid = keyid; + if (tail) + tail->next = entry; + else + public_keys = entry; + log_err("key %d: %x (unknown keyid)\n", i, __be32_to_cpup(&keyid)); + return 0; +} + +void init_public_keys(const char *keyfiles) +{ + struct public_key_entry *entry; + char *tmp_keyfiles, *keyfiles_free; + char *keyfile; + int i = 1; + + tmp_keyfiles = strdup(keyfiles); + keyfiles_free = tmp_keyfiles; + + while ((keyfile = strsep(&tmp_keyfiles, ", \t")) != NULL) { + if (!keyfile) + break; + if ((*keyfile == '\0') || (*keyfile == ' ') || + (*keyfile == '\t')) + continue; + + entry = malloc(sizeof(struct public_key_entry)); + if (!entry) { + perror("malloc"); + break; + } + + entry->key = read_pub_pkey(keyfile, 1); + if (!entry->key) { + free(entry); + continue; + } + + calc_keyid_v2(&entry->keyid, entry->name, entry->key); + sprintf(entry->name, "%x", __be32_to_cpup(&entry->keyid)); + log_info("key %d: %s %s\n", i++, entry->name, keyfile); + entry->next = public_keys; + public_keys = entry; + } + free(keyfiles_free); +} + +/* + * Return: 0 verification good, 1 verification bad, -1 error. + */ +static int verify_hash_v2(const char *file, const unsigned char *hash, int size, + unsigned char *sig, int siglen) +{ + int ret = -1; + EVP_PKEY *pkey, *pkey_free = NULL; + struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig; + EVP_PKEY_CTX *ctx; + const EVP_MD *md; + const char *st; + + if (imaevm_params.verbose > LOG_INFO) { + log_info("hash(%s): ", imaevm_params.hash_algo); + log_dump(hash, size); + } + + pkey = find_keyid(hdr->keyid); + if (!pkey) { + uint32_t keyid = hdr->keyid; + + if (imaevm_params.verbose > LOG_INFO) + log_info("%s: verification failed: unknown keyid %x\n", + file, __be32_to_cpup(&keyid)); + return -1; + } + + st = "EVP_PKEY_CTX_new"; + if (!(ctx = EVP_PKEY_CTX_new(pkey, NULL))) + goto err; + st = "EVP_PKEY_verify_init"; + if (!EVP_PKEY_verify_init(ctx)) + goto err; + st = "EVP_get_digestbyname"; + if (!(md = EVP_get_digestbyname(imaevm_params.hash_algo))) + goto err; + st = "EVP_PKEY_CTX_set_signature_md"; + if (!EVP_PKEY_CTX_set_signature_md(ctx, md)) + goto err; + st = "EVP_PKEY_verify"; + ret = EVP_PKEY_verify(ctx, sig + sizeof(*hdr), + siglen - sizeof(*hdr), hash, size); + if (ret == 1) + ret = 0; + else if (ret == 0) { + log_err("%s: verification failed: %d (%s)\n", + file, ret, ERR_reason_error_string(ERR_get_error())); + output_openssl_errors(); + ret = 1; + } +err: + if (ret < 0 || ret > 1) { + log_err("%s: verification failed: %d (%s) in %s\n", + file, ret, ERR_reason_error_string(ERR_peek_error()), + st); + output_openssl_errors(); + ret = -1; + } + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey_free); + return ret; +} + +int imaevm_get_hash_algo(const char *algo) +{ + int i; + + /* first iterate over builtin algorithms */ + for (i = 0; i < PKEY_HASH__LAST; i++) + if (pkey_hash_algo[i] && + !strcmp(algo, pkey_hash_algo[i])) + return i; + + for (i = 0; i < PKEY_HASH__LAST; i++) + if (pkey_hash_algo_kern[i] && + !strcmp(algo, pkey_hash_algo_kern[i])) + return i; + + /* iterate over algorithms provided by kernel-headers */ + for (i = 0; i < HASH_ALGO__LAST; i++) + if (hash_algo_name[i] && + !strcmp(algo, hash_algo_name[i])) + return i; + + return -1; +} + +int imaevm_hash_algo_from_sig(unsigned char *sig) +{ + uint8_t hashalgo; + + if (sig[0] == DIGSIG_VERSION_1) { + hashalgo = ((struct signature_hdr *)sig)->hash; + + if (hashalgo >= DIGEST_ALGO_MAX) + return -1; + + switch (hashalgo) { + case DIGEST_ALGO_SHA1: + return PKEY_HASH_SHA1; + case DIGEST_ALGO_SHA256: + return PKEY_HASH_SHA256; + default: + return -1; + } + } else if (sig[0] == DIGSIG_VERSION_2) { + hashalgo = ((struct signature_v2_hdr *)sig)->hash_algo; + if (hashalgo >= PKEY_HASH__LAST) + return -1; + return hashalgo; + } else + return -1; +} + +int verify_hash(const char *file, const unsigned char *hash, int size, unsigned char *sig, + int siglen) +{ + /* Get signature type from sig header */ + if (sig[0] == DIGSIG_VERSION_1) { + const char *key = NULL; + + /* Read pubkey from RSA key */ + if (!imaevm_params.keyfile) + key = "/etc/keys/pubkey_evm.pem"; + else + key = imaevm_params.keyfile; + return verify_hash_v1(file, hash, size, sig, siglen, key); + } else if (sig[0] == DIGSIG_VERSION_2) { + return verify_hash_v2(file, hash, size, sig, siglen); + } else + return -1; +} + +int ima_verify_signature(const char *file, unsigned char *sig, int siglen, + unsigned char *digest, int digestlen) +{ + unsigned char hash[MAX_DIGEST_SIZE]; + int hashlen, sig_hash_algo; + + if (sig[0] != EVM_IMA_XATTR_DIGSIG) { + log_err("%s: xattr ima has no signature\n", file); + return -1; + } + + sig_hash_algo = imaevm_hash_algo_from_sig(sig + 1); + if (sig_hash_algo < 0) { + log_err("%s: Invalid signature\n", file); + return -1; + } + /* Use hash algorithm as retrieved from signature */ + imaevm_params.hash_algo = imaevm_hash_algo_by_id(sig_hash_algo); + + /* + * Validate the signature based on the digest included in the + * measurement list, not by calculating the local file digest. + */ + if (digestlen > 0) + return verify_hash(file, digest, digestlen, sig + 1, siglen - 1); + + hashlen = ima_calc_hash(file, hash); + if (hashlen <= 1) + return hashlen; + assert(hashlen <= sizeof(hash)); + + return verify_hash(file, hash, hashlen, sig + 1, siglen - 1); +} + +/* + * Create binary key representation suitable for kernel + */ +int key2bin(RSA *key, unsigned char *pub) +{ + int len, b, offset = 0; + struct pubkey_hdr *pkh = (struct pubkey_hdr *)pub; + const BIGNUM *n, *e; + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + n = key->n; + e = key->e; +#else + RSA_get0_key(key, &n, &e, NULL); +#endif + + /* add key header */ + pkh->version = 1; + pkh->timestamp = 0; /* PEM has no timestamp?? */ + pkh->algo = PUBKEY_ALGO_RSA; + pkh->nmpi = 2; + + offset += sizeof(*pkh); + + len = BN_num_bytes(n); + b = BN_num_bits(n); + pub[offset++] = b >> 8; + pub[offset++] = b & 0xff; + BN_bn2bin(n, &pub[offset]); + offset += len; + + len = BN_num_bytes(e); + b = BN_num_bits(e); + pub[offset++] = b >> 8; + pub[offset++] = b & 0xff; + BN_bn2bin(e, &pub[offset]); + offset += len; + + return offset; +} + +void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len) +{ + uint8_t sha1[SHA_DIGEST_LENGTH]; + uint64_t id; + + SHA1(pkey, len, sha1); + + /* sha1[12 - 19] is exactly keyid from gpg file */ + memcpy(keyid, sha1 + 12, 8); + log_debug("keyid: "); + log_debug_dump(keyid, 8); + id = __be64_to_cpup((__be64 *) keyid); + sprintf(str, "%llX", (unsigned long long)id); + + if (imaevm_params.verbose > LOG_INFO) + log_info("keyid-v1: %s\n", str); +} + +/* + * Calculate keyid of the public_key part of EVP_PKEY + */ +void calc_keyid_v2(uint32_t *keyid, char *str, EVP_PKEY *pkey) +{ + X509_PUBKEY *pk = NULL; + const unsigned char *public_key = NULL; + int len; + + /* This is more generic than i2d_PublicKey() */ + if (X509_PUBKEY_set(&pk, pkey) && + X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk)) { + uint8_t sha1[SHA_DIGEST_LENGTH]; + + SHA1(public_key, len, sha1); + /* sha1[12 - 19] is exactly keyid from gpg file */ + memcpy(keyid, sha1 + 16, 4); + } else + *keyid = 0; + + log_debug("keyid: "); + log_debug_dump(keyid, 4); + sprintf(str, "%x", __be32_to_cpup(keyid)); + + if (imaevm_params.verbose > LOG_INFO) + log_info("keyid: %s\n", str); + + X509_PUBKEY_free(pk); +} + +static EVP_PKEY *read_priv_pkey(const char *keyfile, const char *keypass) +{ + FILE *fp; + EVP_PKEY *pkey; + + fp = fopen(keyfile, "r"); + if (!fp) { + log_err("Failed to open keyfile: %s\n", keyfile); + return NULL; + } + pkey = PEM_read_PrivateKey(fp, NULL, NULL, (void *)keypass); + if (!pkey) { + log_err("Failed to PEM_read_PrivateKey key file: %s\n", + keyfile); + output_openssl_errors(); + } + + fclose(fp); + return pkey; +} + +static RSA *read_priv_key(const char *keyfile, const char *keypass) +{ + EVP_PKEY *pkey; + RSA *key; + + pkey = read_priv_pkey(keyfile, keypass); + if (!pkey) + return NULL; + key = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_free(pkey); + if (!key) { + log_err("read_priv_key: unsupported key type\n"); + output_openssl_errors(); + return NULL; + } + return key; +} + +static int get_hash_algo_v1(const char *algo) +{ + + if (!strcmp(algo, "sha1")) + return DIGEST_ALGO_SHA1; + else if (!strcmp(algo, "sha256")) + return DIGEST_ALGO_SHA256; + + return -1; +} + +static int sign_hash_v1(const char *hashalgo, const unsigned char *hash, + int size, const char *keyfile, unsigned char *sig) +{ + int len = -1, hashalgo_idx; + SHA_CTX ctx; + unsigned char pub[1024]; + RSA *key; + char name[20]; + unsigned char sighash[20]; + struct signature_hdr *hdr; + uint16_t *blen; + + if (!hash) { + log_err("sign_hash_v1: hash is null\n"); + return -1; + } + + if (size < 0) { + log_err("sign_hash_v1: size is negative: %d\n", size); + return -1; + } + + if (!hashalgo) { + log_err("sign_hash_v1: hashalgo is null\n"); + return -1; + } + + if (!sig) { + log_err("sign_hash_v1: sig is null\n"); + return -1; + } + + log_info("hash(%s): ", hashalgo); + log_dump(hash, size); + + key = read_priv_key(keyfile, imaevm_params.keypass); + if (!key) + return -1; + + hdr = (struct signature_hdr *)sig; + + /* now create a new hash */ + hdr->version = (uint8_t) DIGSIG_VERSION_1; + hdr->timestamp = time(NULL); + hdr->algo = PUBKEY_ALGO_RSA; + hashalgo_idx = get_hash_algo_v1(hashalgo); + if (hashalgo_idx < 0) { + log_err("Signature version 1 does not support hash algo %s\n", + hashalgo); + goto out; + } + hdr->hash = (uint8_t) hashalgo_idx; + + len = key2bin(key, pub); + calc_keyid_v1(hdr->keyid, name, pub, len); + + hdr->nmpi = 1; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, hash, size); + SHA1_Update(&ctx, hdr, sizeof(*hdr)); + SHA1_Final(sighash, &ctx); + log_info("sighash: "); + log_dump(sighash, sizeof(sighash)); + + len = RSA_private_encrypt(sizeof(sighash), sighash, sig + sizeof(*hdr) + 2, key, RSA_PKCS1_PADDING); + if (len < 0) { + log_err("RSA_private_encrypt() failed: %d\n", len); + output_openssl_errors(); + goto out; + } + + /* we add bit length of the signature to make it gnupg compatible */ + blen = (uint16_t *) (sig + sizeof(*hdr)); + *blen = __cpu_to_be16(len << 3); + len += sizeof(*hdr) + 2; + log_info("evm/ima signature-v1: %d bytes\n", len); +out: + RSA_free(key); + return len; +} + +/* + * @sig is assumed to be of (MAX_SIGNATURE_SIZE - 1) size + * Return: -1 signing error, >0 length of signature + */ +static int sign_hash_v2(const char *algo, const unsigned char *hash, + int size, const char *keyfile, unsigned char *sig) +{ + struct signature_v2_hdr *hdr; + int len = -1; + EVP_PKEY *pkey; + char name[20]; + EVP_PKEY_CTX *ctx = NULL; + const EVP_MD *md; + size_t sigsize; + const char *st; + uint32_t keyid; + + if (!hash) { + log_err("sign_hash_v2: hash is null\n"); + return -1; + } + + if (size < 0) { + log_err("sign_hash_v2: size is negative: %d\n", size); + return -1; + } + + if (!sig) { + log_err("sign_hash_v2: sig is null\n"); + return -1; + } + + if (!algo) { + log_err("sign_hash_v2: algo is null\n"); + return -1; + } + + log_info("hash(%s): ", imaevm_params.hash_algo); + log_dump(hash, size); + + pkey = read_priv_pkey(keyfile, imaevm_params.keypass); + if (!pkey) + return -1; + + hdr = (struct signature_v2_hdr *)sig; + hdr->version = (uint8_t) DIGSIG_VERSION_2; + + hdr->hash_algo = imaevm_get_hash_algo(algo); + if (hdr->hash_algo == (uint8_t)-1) { + log_err("sign_hash_v2: hash algo is unknown: %s\n", algo); + return -1; + } + + calc_keyid_v2(&keyid, name, pkey); + hdr->keyid = keyid; + + st = "EVP_PKEY_CTX_new"; + if (!(ctx = EVP_PKEY_CTX_new(pkey, NULL))) + goto err; + st = "EVP_PKEY_sign_init"; + if (!EVP_PKEY_sign_init(ctx)) + goto err; + st = "EVP_get_digestbyname"; + if (!(md = EVP_get_digestbyname(imaevm_params.hash_algo))) + goto err; + st = "EVP_PKEY_CTX_set_signature_md"; + if (!EVP_PKEY_CTX_set_signature_md(ctx, md)) + goto err; + st = "EVP_PKEY_sign"; + sigsize = MAX_SIGNATURE_SIZE - sizeof(struct signature_v2_hdr) - 1; + if (!EVP_PKEY_sign(ctx, hdr->sig, &sigsize, hash, size)) + goto err; + len = (int)sigsize; + + /* we add bit length of the signature to make it gnupg compatible */ + hdr->sig_size = __cpu_to_be16(len); + len += sizeof(*hdr); + log_info("evm/ima signature: %d bytes\n", len); + +err: + if (len == -1) { + log_err("sign_hash_v2: signing failed: (%s) in %s\n", + ERR_reason_error_string(ERR_peek_error()), st); + output_openssl_errors(); + } + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return len; +} + + +int sign_hash(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig) +{ + if (keypass) + imaevm_params.keypass = keypass; + + return imaevm_params.x509 ? + sign_hash_v2(hashalgo, hash, size, keyfile, sig) : + sign_hash_v1(hashalgo, hash, size, keyfile, sig); +} + +static void libinit() +{ + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + OpenSSL_add_all_algorithms(); + OPENSSL_add_all_algorithms_conf(); +#else + + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | + OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); + ERR_load_crypto_strings(); +#endif +} diff --git a/src/pcr.h b/src/pcr.h new file mode 100644 index 0000000..79547bd --- /dev/null +++ b/src/pcr.h @@ -0,0 +1,3 @@ +int tpm2_pcr_supported(void); +int tpm2_pcr_read(const char *algo_name, int idx, uint8_t *hwpcr, + int len, char **errmsg); diff --git a/src/pcr_tss.c b/src/pcr_tss.c new file mode 100644 index 0000000..feb1ff7 --- /dev/null +++ b/src/pcr_tss.c @@ -0,0 +1,191 @@ +/* + * ima-evm-utils - IMA/EVM support utilities + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011,2012,2013 Intel Corporation + * Copyright (C) 2013,2014 Samsung Electronics + * + * Authors: + * Dmitry Kasatkin + * + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 . + * + * 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 program with the OpenSSL library. You + * must comply with the GNU General Public License in all respects + * for all of the code used other than as permitted herein. 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 in the license file. + * + * File: pcr_tss.c + * PCR reading implementation based on Intel TSS2 + */ + +#include +#include + +#include + +#ifdef HAVE_LIBTSS2_ESYS +# include + +# ifdef HAVE_LIBTSS2_RC +# include +# define LIB "tss2-rc-decode" +# else +# define LIB "tss2-esys" +# endif + +#endif /* HAVE_LIBTSS2_ESYS */ + +#define USE_FPRINTF +#include "imaevm.h" + +int tpm2_pcr_supported(void) +{ + if (imaevm_params.verbose > LOG_INFO) + log_info("Using %s to read PCRs.\n", LIB); + + return 1; +} + +static int pcr_selections_match(TPML_PCR_SELECTION *a, TPML_PCR_SELECTION *b) +{ + int i, j; + + if (a->count != b->count) + return 0; + + for (i = 0; i < a->count; i++) { + if (a->pcrSelections[i].hash != b->pcrSelections[i].hash) + return 0; + if (a->pcrSelections[i].sizeofSelect != b->pcrSelections[i].sizeofSelect) + return 0; + for (j = 0; j < a->pcrSelections[i].sizeofSelect; j++) { + if (a->pcrSelections[i].pcrSelect[j] != b->pcrSelections[i].pcrSelect[j]) + return 0; + } + } + + return 1; +} + +static inline int tpm2_set_errmsg(char **errmsg, const char *message, TSS2_RC ret) +{ +#ifdef HAVE_LIBTSS2_RC + return asprintf(errmsg, "%s: %s", message, Tss2_RC_Decode(ret)); +#else + return asprintf(errmsg, "%s: #%d", message, ret); +#endif +} + +static TPM2_ALG_ID algo_to_tss2(const char *algo_name) +{ + if (!strcmp(algo_name, "sha1")) + return TPM2_ALG_SHA1; + else if (!strcmp(algo_name, "sha256")) + return TPM2_ALG_SHA256; + + return TPM2_ALG_ERROR; +} + +int tpm2_pcr_read(const char *algo_name, int idx, uint8_t *hwpcr, + int len, char **errmsg) +{ + TSS2_ABI_VERSION abi_version = { + .tssCreator = 1, + .tssFamily = 2, + .tssLevel = 1, + .tssVersion = 108, + }; + ESYS_CONTEXT *ctx = NULL; + TSS2_RC ret = 0; + TPML_PCR_SELECTION *pcr_select_out; + TPML_DIGEST *pcr_digests; + UINT32 pcr_update_counter; + + TPM2_ALG_ID algid = algo_to_tss2(algo_name); + if (algid == TPM2_ALG_ERROR) { + ret = asprintf(errmsg, "unsupported tss2 algorithm"); + if (ret == -1) /* the contents of errmsg are undefined */ + *errmsg = NULL; + return -1; + } + + TPML_PCR_SELECTION pcr_select_in = { + .count = 1, + .pcrSelections = { + { + .hash = algid, + .sizeofSelect = 3, + .pcrSelect = { 0x00, 0x00, 0x00 }, + } + } + }; + + pcr_select_in.pcrSelections[0].pcrSelect[idx / 8] = (1 << (idx % 8)); + + ret = Esys_Initialize(&ctx, NULL, &abi_version); + if (ret != TPM2_RC_SUCCESS) { + ret = tpm2_set_errmsg(errmsg, "esys initialize failed", ret); + if (ret == -1) /* the contents of errmsg are undefined */ + *errmsg = NULL; + return -1; + } + + ret = Esys_PCR_Read(ctx, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &pcr_select_in, + &pcr_update_counter, + &pcr_select_out, + &pcr_digests); + Esys_Finalize(&ctx); + if (ret != TPM2_RC_SUCCESS) { + ret = tpm2_set_errmsg(errmsg, "esys PCR reading failed", ret); + if (ret == -1) /* the contents of errmsg is undefined */ + *errmsg = NULL; + return -1; + } + + if (!pcr_selections_match(&pcr_select_in, pcr_select_out)) { + Esys_Free(pcr_select_out); + Esys_Free(pcr_digests); + + ret = asprintf(errmsg, "TPM returned incorrect PCRs"); + if (ret == -1) /* the contents of errmsg are undefined */ + *errmsg = NULL; + return -1; + } + Esys_Free(pcr_select_out); + + if (pcr_digests->count != 1 || pcr_digests->digests[0].size != len) { + Esys_Free(pcr_digests); + ret = asprintf(errmsg, "TPM returned incorrect digests"); + if (ret == -1) /* the contents of errmsg is undefined */ + *errmsg = NULL; + return -1; + } + + memcpy(hwpcr, pcr_digests->digests[0].buffer, len); + Esys_Free(pcr_digests); + return 0; +} diff --git a/src/pcr_tsspcrread.c b/src/pcr_tsspcrread.c new file mode 100644 index 0000000..462f270 --- /dev/null +++ b/src/pcr_tsspcrread.c @@ -0,0 +1,111 @@ +/* + * ima-evm-utils - IMA/EVM support utilities + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011,2012,2013 Intel Corporation + * Copyright (C) 2013,2014 Samsung Electronics + * + * Authors: + * Dmitry Kasatkin + * + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 . + * + * 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 program with the OpenSSL library. You + * must comply with the GNU General Public License in all respects + * for all of the code used other than as permitted herein. 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 in the license file. + * + * File: pcr_tsspcrread.c + * PCR reading implementation based on IBM TSS2 + */ + +#include +#include +#include +#include +#include + +#include + +#define USE_FPRINTF +#include "utils.h" +#include "imaevm.h" + +#define CMD "tsspcrread" + +static char path[PATH_MAX]; + +int tpm2_pcr_supported(void) +{ + if (imaevm_params.verbose > LOG_INFO) + log_info("Using %s to read PCRs.\n", CMD); + + if (get_cmd_path(CMD, path, sizeof(path))) { + log_debug("Couldn't find '%s' in $PATH", CMD); + return 0; + } + + log_debug("Found '%s' in $PATH", CMD); + return 1; +} + +int tpm2_pcr_read(const char *algo_name, int idx, uint8_t *hwpcr, + int len, char **errmsg) +{ + FILE *fp; + char pcr[100]; /* may contain an error */ + char cmd[PATH_MAX + 50]; + int ret; + + sprintf(cmd, "%s -halg %s -ha %d -ns 2> /dev/null", + path, algo_name, idx); + fp = popen(cmd, "r"); + if (!fp) { + ret = asprintf(errmsg, "popen failed: %s", strerror(errno)); + if (ret == -1) /* the contents of errmsg is undefined */ + *errmsg = NULL; + return -1; + } + + if (fgets(pcr, sizeof(pcr), fp) == NULL) { + ret = asprintf(errmsg, "tsspcrread failed: %s", + strerror(errno)); + if (ret == -1) /* the contents of errmsg is undefined */ + *errmsg = NULL; + ret = pclose(fp); + return -1; + } + + /* get the popen "cmd" return code */ + ret = pclose(fp); + + /* Treat an unallocated bank as an error */ + if (!ret && (strlen(pcr) < SHA_DIGEST_LENGTH)) + ret = -1; + + if (!ret) + hex2bin(hwpcr, pcr, len); + else + *errmsg = strndup(pcr, strlen(pcr) - 1); /* remove newline */ + + return ret; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..fbb6a4b --- /dev/null +++ b/src/utils.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * utils: set of common functions + * + * Copyright (C) 2020 Patrick Uiterwijk + * Copyright (C) 2010 Cyril Hrubis + */ +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +#ifndef MIN +# define MIN(a, b) ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ +}) +#endif /* MIN */ + +static int file_exist(const char *path) +{ + struct stat st; + + if (!access(path, R_OK) && !stat(path, &st) && S_ISREG(st.st_mode)) + return 1; + + return 0; +} + +int get_cmd_path(const char *prog_name, char *buf, size_t buf_len) +{ + const char *path = (const char *)getenv("PATH"); + const char *start = path; + const char *end; + size_t size, ret; + + if (path == NULL) + return -1; + + do { + end = strchr(start, ':'); + + if (end != NULL) + snprintf(buf, MIN(buf_len, (size_t) (end - start + 1)), + "%s", start); + else + snprintf(buf, buf_len, "%s", start); + + size = strlen(buf); + + /* + * "::" inside $PATH, $PATH ending with ':' or $PATH starting + * with ':' should be expanded into current working directory. + */ + if (size == 0) { + snprintf(buf, buf_len, "."); + size = strlen(buf); + } + + /* + * If there is no '/' ad the end of path from $PATH add it. + */ + if (buf[size - 1] != '/') + ret = + snprintf(buf + size, buf_len - size, "/%s", + prog_name); + else + ret = + snprintf(buf + size, buf_len - size, "%s", + prog_name); + + if (buf_len - size > ret && file_exist(buf)) + return 0; + + start = end + 1; + + } while (end != NULL); + + return -1; +} + +int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +int hex2bin(void *dst, const char *src, size_t count) +{ + int hi, lo; + + while (count--) { + if (*src == ' ') + src++; + + hi = hex_to_bin(*src++); + lo = hex_to_bin(*src++); + + if ((hi < 0) || (lo < 0)) + return -1; + + *(uint8_t *)dst++ = (hi << 4) | lo; + } + return 0; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..9ea179f --- /dev/null +++ b/src/utils.h @@ -0,0 +1,6 @@ +#include +#include + +int get_cmd_path(const char *prog_name, char *buf, size_t buf_len); +int hex_to_bin(char ch); +int hex2bin(void *dst, const char *src, size_t count); diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..9ecc984 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,16 @@ +# Generated by test driver +*.log +*.trs + +# Generated by tests +*.txt +*.out +*.sig +*.sig2 + +# Generated certs and keys (by gen-keys.sh) +*.cer +*.pub +*.key +*.conf + diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..ff928e1 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,12 @@ +check_SCRIPTS = +TESTS = $(check_SCRIPTS) + +check_SCRIPTS += ima_hash.test sign_verify.test boot_aggregate.test + +clean-local: + -rm -f *.txt *.out *.sig *.sig2 + +distclean: distclean-keys +.PHONY: distclean-keys +distclean-keys: + ./gen-keys.sh clean diff --git a/tests/boot_aggregate.test b/tests/boot_aggregate.test new file mode 100755 index 0000000..d711566 --- /dev/null +++ b/tests/boot_aggregate.test @@ -0,0 +1,197 @@ +#!/bin/bash + +# +# Calculate the boot_aggregate for each TPM bank, verifying that the +# boot_aggregate in the IMA measurement list matches one of them. +# +# A software TPM may be used to verify the boot_aggregate. If a +# software TPM is not already running on the system, this test +# starts one and initializes the TPM PCR banks by walking the sample +# binary_bios_measurements event log, included in this directory, and +# extending the TPM PCRs. The associated ascii_runtime_measurements +# for verifying the calculated boot_aggregate is included in this +# directory as well. + +trap cleanup SIGINT SIGTERM EXIT + +# Base VERBOSE on the environment variable, if set. +VERBOSE="${VERBOSE:-0}" + +cd "$(dirname "$0")" +export PATH=../src:$PATH +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH +. ./functions.sh +_require evmctl +TSSDIR="$(dirname -- "$(which tssstartup)")" +PCRFILE="/sys/class/tpm/tpm0/device/pcrs" +MISC_PCRFILE="/sys/class/misc/tpm0/device/pcrs" + +# Only stop this test's software TPM +cleanup() { + if [ -n "${SWTPM_PID}" ]; then + kill -SIGTERM "${SWTPM_PID}" + elif [ -n "${TPMSERVER_PID}" ]; then + "${TSSDIR}/tsstpmcmd" -stop + fi +} + +# Try to start a software TPM if needed. +swtpm_start() { + local tpm_server swtpm + + tpm_server="$(which tpm_server)" + swtpm="$(which swtpm)" + if [ -z "${tpm_server}" ] && [ -z "${swtpm}" ]; then + echo "${CYAN}SKIP: Software TPM (tpm_server and swtpm) not found${NORM}" + return "$SKIP" + fi + + if [ -n "${swtpm}" ]; then + pgrep swtpm + if [ $? -eq 0 ]; then + echo "INFO: Software TPM (swtpm) already running" + return 114 + else + echo "INFO: Starting software TPM: ${swtpm}" + mkdir -p ./myvtpm + ${swtpm} socket --tpmstate dir=./myvtpm --tpm2 --ctrl type=tcp,port=2322 --server type=tcp,port=2321 --flags not-need-init > /dev/null 2>&1 & + SWTPM_PID=$! + fi + elif [ -n "${tpm_server}" ]; then + # tpm_server uses the Microsoft simulator encapsulated packet format + export TPM_SERVER_TYPE="mssim" + pgrep tpm_server + if [ $? -eq 0 ]; then + echo "INFO: Software TPM (tpm_server) already running" + return 114 + else + echo "INFO: Starting software TPM: ${tpm_server}" + ${tpm_server} > /dev/null 2>&1 & + TPMSERVER_PID=$! + fi + fi + return 0 +} + +# Initialize the software TPM using the sample binary_bios_measurements log. +swtpm_init() { + if [ ! -f "${TSSDIR}/tssstartup" ] || [ ! -f "${TSSDIR}/tsseventextend" ]; then + echo "${CYAN}SKIP: tssstartup and tsseventextend needed for test${NORM}" + return "$SKIP" + fi + + echo "INFO: Sending software TPM startup" + "${TSSDIR}/tssstartup" + if [ $? -ne 0 ]; then + echo "INFO: Retry sending software TPM startup" + sleep 1 + "${TSSDIR}/tssstartup" + fi + + if [ $? -ne 0 ]; then + echo "INFO: Software TPM startup failed" + return "$SKIP" + fi + + echo "INFO: Walking ${BINARY_BIOS_MEASUREMENTS} initializing the software TPM" +# $(${TSSDIR}/tsseventextend -tpm -if "${BINARY_BIOS_MEASUREMENTS}" -v) 2>&1 > /dev/null + "${TSSDIR}/tsseventextend" -tpm -if "${BINARY_BIOS_MEASUREMENTS}" -v > /dev/null 2>&1 +} + +# In VERBOSE mode, display the calculated TPM PCRs for the different banks. +display_pcrs() { + local PCRMAX=9 + local banks=("sha1" "sha256") + local i; + + for bank in "${banks[@]}"; do + echo "INFO: Displaying ${bank} TPM bank (PCRs 0 - 9)" + for i in $(seq 0 $PCRMAX); do + rc=0 + pcr=$("${TSSDIR}/tsspcrread" -halg "${bank}" -ha "${i}" -ns) + if [ $rc -ne 0 ]; then + echo "INFO: tsspcrread failed: $pcr" + break + fi + echo "$i: $pcr" + done + done +} + +# The first entry in the IMA measurement list is the "boot_aggregate". +# For each kexec, an additional "boot_aggregate" will appear in the +# measurement list, assuming the previous measurement list is carried +# across the kexec. +# +# Verify that the last "boot_aggregate" record in the IMA measurement +# list matches. +check() { + echo "INFO: Calculating the boot_aggregate (PCRs 0 - 9) for multiple banks" + bootaggr=$(evmctl ima_boot_aggregate) + if [ $? -ne 0 ]; then + echo "${CYAN}SKIP: evmctl ima_boot_aggregate: $bootaggr${NORM}" + exit "$SKIP" + fi + + boot_aggr=( $bootaggr ) + + echo "INFO: Searching for the boot_aggregate in ${ASCII_RUNTIME_MEASUREMENTS}" + for hash in "${boot_aggr[@]}"; do + if [ "$VERBOSE" != "0" ]; then + echo "$hash" + fi + if grep -e " boot_aggregate$" -e " boot_aggregate.$" "${ASCII_RUNTIME_MEASUREMENTS}" | tail -n 1 | grep -q "${hash}"; then + echo "${GREEN}SUCCESS: boot_aggregate ${hash} found${NORM}" + return "$OK" + fi + done + echo "${RED}FAILURE: boot_aggregate not found${NORM}" + echo "$bootaggr" + return "$FAIL" +} + +if [ "$(id -u)" = 0 ] && [ -c "/dev/tpm0" ]; then + ASCII_RUNTIME_MEASUREMENTS="/sys/kernel/security/ima/ascii_runtime_measurements" + if [ ! -d "/sys/kernel/security/ima" ]; then + echo "${CYAN}SKIP: CONFIG_IMA not enabled${NORM}" + exit "$SKIP" + fi +else + BINARY_BIOS_MEASUREMENTS="./sample-binary_bios_measurements-pcrs-8-9" + ASCII_RUNTIME_MEASUREMENTS="./sample-ascii_runtime_measurements-pcrs-8-9" + export TPM_INTERFACE_TYPE="socsim" + export TPM_COMMAND_PORT=2321 + export TPM_PLATFORM_PORT=2322 + export TPM_SERVER_NAME="localhost" + + # swtpm uses the raw, unencapsulated packet format + export TPM_SERVER_TYPE="raw" +fi + +# Start and initialize a software TPM as needed +if [ "$(id -u)" != 0 ] || [ ! -c "/dev/tpm0" ]; then + if [ -f "$PCRFILE" ] || [ -f "$MISC_PCRFILE" ]; then + echo "${CYAN}SKIP: system has discrete TPM 1.2, sample TPM 2.0 event log test not supported.${NORM}" + exit "$SKIP" + fi + + swtpm_start + error=$? + if [ $error -eq "$SKIP" ]; then + echo "skip: swtpm not installed" + exit "$SKIP" + fi + + if [ $error -eq 0 ]; then + swtpm_init + if [ $? -eq "$SKIP" ]; then + echo "testing boot_aggregate without entries" + exit "$SKIP" + fi + fi + if [ "$VERBOSE" != "0" ]; then + display_pcrs + fi +fi + +expect_pass check diff --git a/tests/functions.sh b/tests/functions.sh new file mode 100755 index 0000000..91cd5d9 --- /dev/null +++ b/tests/functions.sh @@ -0,0 +1,274 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# ima-evm-utils tests bash functions +# +# Copyright (C) 2020 Vitaly Chikunov +# +# 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 2, 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. + +# Tests accounting +declare -i testspass=0 testsfail=0 testsskip=0 + +# Exit codes (compatible with automake) +declare -r OK=0 +declare -r FAIL=1 +declare -r HARDFAIL=99 # hard failure no matter testing mode +declare -r SKIP=77 + +# You can set env VERBOSE=1 to see more output from evmctl +VERBOSE=${VERBOSE:-0} +V=vvvv +V=${V:0:$VERBOSE} +V=${V:+-$V} + +# Exit if env FAILEARLY is defined. +# Used in expect_{pass,fail}. +exit_early() { + if [ "$FAILEARLY" ]; then + exit "$1" + fi +} + +# Require particular executables to be present +_require() { + ret= + for i; do + if ! type $i; then + echo "$i is required for test" + ret=1 + fi + done + [ $ret ] && exit "$HARDFAIL" +} + +# Non-TTY output is never colored +if [ -t 1 ]; then + RED=$'\e[1;31m' + GREEN=$'\e[1;32m' + YELLOW=$'\e[1;33m' + BLUE=$'\e[1;34m' + CYAN=$'\e[1;36m' + NORM=$'\e[m' + export RED GREEN YELLOW BLUE CYAN NORM +fi + +# Test mode determined by TFAIL variable: +# undefined: to success testing +# defined: failure testing +TFAIL= +TMODE=+ # mode character to prepend running command in log +declare -i TNESTED=0 # just for sanity checking + +# Run positive test (one that should pass) and account its result +expect_pass() { + local -i ret + + if [ $TNESTED -gt 0 ]; then + echo $RED"expect_pass should not be run nested"$NORM + testsfail+=1 + exit "$HARDFAIL" + fi + TFAIL= + TMODE=+ + TNESTED+=1 + [ "$VERBOSE" -gt 1 ] && echo "____ START positive test: $*" + "$@" + ret=$? + [ "$VERBOSE" -gt 1 ] && echo "^^^^ STOP ($ret) positive test: $*" + TNESTED+=-1 + case $ret in + 0) testspass+=1 ;; + 77) testsskip+=1 ;; + 99) testsfail+=1; exit_early 1 ;; + *) testsfail+=1; exit_early 2 ;; + esac + return $ret +} + +# Eval negative test (one that should fail) and account its result +expect_fail() { + local ret + + if [ $TNESTED -gt 0 ]; then + echo $RED"expect_fail should not be run nested"$NORM + testsfail+=1 + exit "$HARDFAIL" + fi + + TFAIL=yes + TMODE=- + TNESTED+=1 + [ "$VERBOSE" -gt 1 ] && echo "____ START negative test: $*" + "$@" + ret=$? + [ "$VERBOSE" -gt 1 ] && echo "^^^^ STOP ($ret) negative test: $*" + TNESTED+=-1 + case $ret in + 0) testsfail+=1; exit_early 3 ;; + 77) testsskip+=1 ;; + 99) testsfail+=1; exit_early 4 ;; + *) testspass+=1 ;; + esac + # Restore defaults (as in positive tests) + # for tests to run without wrappers + TFAIL= + TMODE=+ + return $ret +} + +# return true if current test is positive +_test_expected_to_pass() { + [ ! $TFAIL ] +} + +# return true if current test is negative +_test_expected_to_fail() { + [ $TFAIL ] +} + +# Show blank line and color following text to red +# if it's real error (ie we are in expect_pass mode). +color_red_on_failure() { + if _test_expected_to_pass; then + echo "$RED" + COLOR_RESTORE=true + fi +} + +# For hard errors +color_red() { + echo "$RED" + COLOR_RESTORE=true +} + +color_restore() { + [ $COLOR_RESTORE ] && echo "$NORM" + COLOR_RESTORE= +} + +ADD_DEL= +ADD_TEXT_FOR= +# _evmctl_run should be run as `_evmctl_run ... || return' +_evmctl_run() { + local op=$1 out=$1-$$.out + local text_for=${FOR:+for $ADD_TEXT_FOR} + # Additional parameters: + # ADD_DEL: additional files to rm on failure + # ADD_TEXT_FOR: append to text as 'for $ADD_TEXT_FOR' + + cmd="evmctl $V $EVMCTL_ENGINE $*" + echo $YELLOW$TMODE "$cmd"$NORM + $cmd >"$out" 2>&1 + ret=$? + + # Shell special and signal exit codes (except 255) + if [ $ret -ge 126 ] && [ $ret -lt 255 ]; then + color_red + echo "evmctl $op failed hard with ($ret) $text_for" + sed 's/^/ /' "$out" + color_restore + rm "$out" $ADD_DEL + ADD_DEL= + ADD_TEXT_FOR= + return "$HARDFAIL" + elif [ $ret -gt 0 ]; then + color_red_on_failure + echo "evmctl $op failed" ${TFAIL:+properly} "with ($ret) $text_for" + # Show evmctl output only in verbose mode or if real failure. + if _test_expected_to_pass || [ "$VERBOSE" ]; then + sed 's/^/ /' "$out" + fi + color_restore + rm "$out" $ADD_DEL + ADD_DEL= + ADD_TEXT_FOR= + return "$FAIL" + elif _test_expected_to_fail; then + color_red + echo "evmctl $op wrongly succeeded $text_for" + sed 's/^/ /' "$out" + color_restore + else + [ "$VERBOSE" ] && sed 's/^/ /' "$out" + fi + rm "$out" + ADD_DEL= + ADD_TEXT_FOR= + return "$OK" +} + +# Extract xattr $attr from $file into $out file skipping $pref'ix +_extract_xattr() { + local file=$1 attr=$2 out=$3 pref=$4 + + getfattr -n "$attr" -e hex "$file" \ + | grep "^$attr=" \ + | sed "s/^$attr=$pref//" \ + | xxd -r -p > "$out" +} + +# Test if xattr $attr in $file matches $prefix +# Show error and fail otherwise. +_test_xattr() { + local file=$1 attr=$2 prefix=$3 + local text_for=${ADD_TEXT_FOR:+ for $ADD_TEXT_FOR} + + if ! getfattr -n "$attr" -e hex "$file" | egrep -qx "$attr=$prefix"; then + color_red_on_failure + echo "Did not find expected hash$text_for:" + echo " $attr=$prefix" + echo "" + echo "Actual output below:" + getfattr -n "$attr" -e hex "$file" | sed 's/^/ /' + color_restore + rm "$file" + ADD_TEXT_FOR= + return "$FAIL" + fi + ADD_TEXT_FOR= +} + +# Try to enable gost-engine if needed. +_enable_gost_engine() { + # Do not enable if it's already working (enabled by user) + if ! openssl md_gost12_256 /dev/null >/dev/null 2>&1 \ + && openssl engine gost >/dev/null 2>&1; then + export EVMCTL_ENGINE="--engine gost" + export OPENSSL_ENGINE="-engine gost" + fi +} + +# Show test stats and exit into automake test system +# with proper exit code (same as ours). +_report_exit() { + if [ $testsfail -gt 0 ]; then + echo "=================================" + echo " Run with FAILEARLY=1 $0 $*" + echo " To stop after first failure" + echo "=================================" + fi + [ $testspass -gt 0 ] && echo -n "$GREEN" || echo -n "$NORM" + echo -n "PASS: $testspass" + [ $testsskip -gt 0 ] && echo -n "$YELLOW" || echo -n "$NORM" + echo -n " SKIP: $testsskip" + [ $testsfail -gt 0 ] && echo -n "$RED" || echo -n "$NORM" + echo " FAIL: $testsfail" + echo "$NORM" + if [ $testsfail -gt 0 ]; then + exit "$FAIL" + elif [ $testspass -gt 0 ]; then + exit "$OK" + else + exit "$SKIP" + fi +} + diff --git a/tests/gen-keys.sh b/tests/gen-keys.sh new file mode 100755 index 0000000..407876b --- /dev/null +++ b/tests/gen-keys.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Generate keys for the tests +# +# Copyright (C) 2020 Vitaly Chikunov +# +# 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 2, 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. + +cd "$(dirname "$0")" || exit 1 +PATH=../src:$PATH +type openssl + +log() { + echo - "$*" + eval "$@" +} + +if [ "$1" = clean ]; then + rm -f test-ca.conf +elif [ "$1" = force ] || [ ! -e test-ca.conf ]; then +cat > test-ca.conf <<- EOF + [ req ] + distinguished_name = req_distinguished_name + prompt = no + string_mask = utf8only + x509_extensions = v3_ca + + [ req_distinguished_name ] + O = IMA-CA + CN = IMA/EVM certificate signing key + emailAddress = ca@ima-ca + + [ v3_ca ] + basicConstraints=CA:TRUE + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid:always,issuer +EOF +fi + +# RSA +# Second key will be used for wrong key tests. +for m in 1024 2048; do + if [ "$1" = clean ] || [ "$1" = force ]; then + rm -f test-rsa$m.cer test-rsa$m.key test-rsa$m.pub + fi + if [ "$1" = clean ]; then + continue + fi + if [ ! -e test-rsa$m.key ]; then + log openssl req -verbose -new -nodes -utf8 -sha1 -days 10000 -batch -x509 \ + -config test-ca.conf \ + -newkey rsa:$m \ + -out test-rsa$m.cer -outform DER \ + -keyout test-rsa$m.key + # for v1 signatures + log openssl pkey -in test-rsa$m.key -out test-rsa$m.pub -pubout + fi +done + +# EC-RDSA +for m in \ + gost2012_256:A \ + gost2012_256:B \ + gost2012_256:C \ + gost2012_512:A \ + gost2012_512:B; do + IFS=':' read -r algo param <<< "$m" + if [ "$1" = clean ] || [ "$1" = force ]; then + rm -f "test-$algo-$param.key" "test-$algo-$param.cer" "test-$algo-$param.pub" + fi + if [ "$1" = clean ]; then + continue + fi + [ -e "test-$algo-$param.key" ] && continue + log openssl req -nodes -x509 -utf8 -days 10000 -batch \ + -config test-ca.conf \ + -newkey "$algo" \ + -pkeyopt "paramset:$param" \ + -out "test-$algo-$param.cer" -outform DER \ + -keyout "test-$algo-$param.key" + if [ -s "test-$algo-$param.key" ]; then + log openssl pkey -in "test-$algo-$param.key" -out "test-$algo-$param.pub" -pubout + fi +done + +# This script leaves test-ca.conf, *.cer, *.pub, *.key files for sing/verify tests. +# They are never deleted except by `make distclean'. + diff --git a/tests/ima_hash.test b/tests/ima_hash.test new file mode 100755 index 0000000..8d66e59 --- /dev/null +++ b/tests/ima_hash.test @@ -0,0 +1,80 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# evmctl ima_hash tests +# +# Copyright (C) 2020 Vitaly Chikunov +# +# 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 2, 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. + +cd "$(dirname "$0")" || exit 1 +PATH=../src:$PATH +source ./functions.sh +_require evmctl openssl getfattr + +trap _report_exit EXIT +set -f # disable globbing + +check() { + local alg=$1 prefix=$2 chash=$3 hash + local file=$alg-hash.txt + + rm -f "$file" + touch "$file" + # Generate hash with openssl, if it failed skip test, + # unless it's negative test, then pass to evmctl + cmd="openssl dgst $OPENSSL_ENGINE -$alg $file" + echo - "$cmd" + hash=$(set -o pipefail; $cmd 2>/dev/null | cut -d' ' -f2) + if [ $? -ne 0 ] && _test_expected_to_pass; then + echo "${CYAN}$alg test is skipped$NORM" + rm "$file" + return "$SKIP" + fi + if [ "$chash" ] && [ "$chash" != "$hash" ]; then + color_red + echo "Invalid hash for $alg from openssl" + echo "Expected: $chash" + echo "Returned: $hash" + color_restore + rm "$file" + return "$HARDFAIL" + fi + + ADD_TEXT_FOR=$alg ADD_DEL=$file \ + _evmctl_run ima_hash --hashalgo "$alg" --xattr-user "$file" || return + ADD_TEXT_FOR=$alg \ + _test_xattr "$file" user.ima "$prefix$hash" || return + rm "$file" + return "$OK" +} + +# check args: algo hdr-prefix canonic-hash +expect_pass check md4 0x01 31d6cfe0d16ae931b73c59d7e0c089c0 +expect_pass check md5 0x01 d41d8cd98f00b204e9800998ecf8427e +expect_pass check sha1 0x01 da39a3ee5e6b4b0d3255bfef95601890afd80709 +expect_fail check SHA1 0x01 # uppercase +expect_fail check sha512-224 0x01 # valid for pkcs1 +expect_fail check sha512-256 0x01 # valid for pkcs1 +expect_fail check unknown 0x01 # nonexistent +expect_pass check sha224 0x0407 d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f +expect_pass check sha256 0x0404 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +expect_pass check sha384 0x0405 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b +expect_pass check sha512 0x0406 cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +expect_pass check rmd160 0x0403 9c1185a5c5e9fc54612808977ee8f548b2258d31 +expect_fail check sm3 0x01 +expect_fail check sm3-256 0x01 +_enable_gost_engine +expect_pass check md_gost12_256 0x0412 3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb +expect_pass check streebog256 0x0412 3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb +expect_pass check md_gost12_512 0x0413 8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a +expect_pass check streebog512 0x0413 8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a + diff --git a/tests/install-swtpm.sh b/tests/install-swtpm.sh new file mode 100755 index 0000000..2d8293a --- /dev/null +++ b/tests/install-swtpm.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -ex + +version=1637 + +wget --no-check-certificate https://sourceforge.net/projects/ibmswtpm2/files/ibmtpm${version}.tar.gz/download +mkdir ibmtpm$version +cd ibmtpm$version +tar -xvzf ../download +cd src +make -j$(nproc) +sudo cp tpm_server /usr/local/bin/ +cd ../.. diff --git a/tests/install-tss.sh b/tests/install-tss.sh new file mode 100755 index 0000000..c9c179e --- /dev/null +++ b/tests/install-tss.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -ex +git clone https://git.code.sf.net/p/ibmtpm20tss/tss +cd tss +autoreconf -i && ./configure --disable-tpm-1.2 --disable-hwtpm && make -j$(nproc) && sudo make install +cd .. +rm -rf tss diff --git a/tests/sample-ascii_runtime_measurements-pcrs-8-9 b/tests/sample-ascii_runtime_measurements-pcrs-8-9 new file mode 100644 index 0000000..43034d8 --- /dev/null +++ b/tests/sample-ascii_runtime_measurements-pcrs-8-9 @@ -0,0 +1 @@ +10 2e03b3fdb0014fc8bae2a07ca33ae67125b290f3 ima-ng sha256:83d19723ef3b3c05bb8ae70d86b3886c158f2408f1b71ed265886a7b79eb700e boot_aggregate diff --git a/tests/sample-binary_bios_measurements-pcrs-8-9 b/tests/sample-binary_bios_measurements-pcrs-8-9 new file mode 100644 index 0000000..2330828 Binary files /dev/null and b/tests/sample-binary_bios_measurements-pcrs-8-9 differ diff --git a/tests/sample-tpm-2.0-pcrs-8-9 b/tests/sample-tpm-2.0-pcrs-8-9 new file mode 100644 index 0000000..1f4cc6e --- /dev/null +++ b/tests/sample-tpm-2.0-pcrs-8-9 @@ -0,0 +1,25 @@ +pcrread: tsspcrread -halg sha1 +0: 92c1850372e9493929aa9a2e9ea953e21ff1be45 +1: 41c54039ca2750ea60d8ab7c48b142b10aba5667 +2: b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236 +3: b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236 +4: 4c1a19aad90f770956ff5ee00334a2d548b1a350 +5: a1444a8a9904666165730168b3ae489447d3cef7 +6: b2a83b0ebf2f8374299a5b2bdfc31ea955ad7236 +7: 5c6327a67ff36f138e0b7bb1d2eafbf8a6e52ebf +8: fed489d2e5f9f85136e5ff53553d5f8b978dbe1a +9: a2fa191f2622bb014702013bfebfca9fe210d9e5 +10: 3134641a3e8a1f5f75fa850bb21c3104d6ab863b +11: 0000000000000000000000000000000000000000 +12: 0000000000000000000000000000000000000000 +13: 0000000000000000000000000000000000000000 +14: 71161a5707051fa7d6f584d812240b2e80f61942 +15: 0000000000000000000000000000000000000000 +16: 0000000000000000000000000000000000000000 +17: ffffffffffffffffffffffffffffffffffffffff +18: ffffffffffffffffffffffffffffffffffffffff +19: ffffffffffffffffffffffffffffffffffffffff +20: ffffffffffffffffffffffffffffffffffffffff +21: ffffffffffffffffffffffffffffffffffffffff +22: ffffffffffffffffffffffffffffffffffffffff +23: 0000000000000000000000000000000000000000 diff --git a/tests/sign_verify.test b/tests/sign_verify.test new file mode 100755 index 0000000..288e133 --- /dev/null +++ b/tests/sign_verify.test @@ -0,0 +1,381 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# evmctl {,ima_}{sign,verify} tests +# +# Copyright (C) 2020 Vitaly Chikunov +# +# 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 2, 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. + +cd "$(dirname "$0")" || exit 1 +PATH=../src:$PATH +source ./functions.sh + +_require cmp evmctl getfattr openssl xxd + +if cmp -b 2>&1 | grep -q "invalid option"; then + echo "cmp does not support -b (cmp from busybox?) Use cmp from diffutils" + exit "$HARDFAIL" +fi + +./gen-keys.sh >/dev/null 2>&1 + +trap _report_exit EXIT +set -f # disable globbing + +# Determine keyid from a cert +_keyid_from_cert() { + local cer=${1%.*}.cer cmd + local tmp + + cer=test-${cer#test-} + # shellcheck disable=SC2086 + cmd="openssl x509 $OPENSSL_ENGINE \ + -in $cer -inform DER -pubkey -noout" + id=$($cmd 2>/dev/null \ + | openssl asn1parse \ + | grep BIT.STRING \ + | cut -d: -f1) + if [ -z "$id" ]; then + echo - "$cmd" >&2 + echo "Cannot asn1parse $cer to determine keyid" >&2 + exit 1 + fi + tmp=$(mktemp) + # shellcheck disable=SC2086 + openssl x509 $OPENSSL_ENGINE \ + -in "$cer" -inform DER -pubkey -noout 2>/dev/null \ + | openssl asn1parse -strparse "$id" -out "$tmp" -noout + # shellcheck disable=SC2002 + cat "$tmp" \ + | openssl dgst -c -sha1 \ + | cut -d' ' -f2 \ + | grep -o ":..:..:..:..$" \ + | tr -d : + rm -f "$tmp" +} + +# Convert test $type into evmctl op prefix +_op() { + if [ "$1" = ima ]; then + echo ima_ + fi +} + +# Convert test $type into xattr name +_xattr() { + if [ "$1" = ima ]; then + echo user.ima + else + echo user.evm + fi +} + +# Check that detached signature matches xattr signature +_test_sigfile() { + local file=$1 attr=$2 file_sig=$3 file_sig2=$4 + + if [ ! -e "$file_sig" ]; then + color_red + echo "evmctl ima_sign: no detached signature $file_sig" + color_restore + rm "$file" + return "$FAIL" + fi + + _extract_xattr "$file" "$attr" "$file_sig2" + if ! cmp -bl "$file_sig" "$file_sig2"; then + color_red + echo "evmctl ima_sign: xattr signature on $file differ from detached $file_sig" + color_restore + rm "$file" "$file_sig" "$file_sig2" + return "$FAIL" + fi + + # Leave '$file_sig' for ima_verify --sigfile test. + rm "$file_sig2" +} + +# Run single sign command +_evmctl_sign() { + local type=$1 key=$2 alg=$3 file=$4 opts=$5 + + # Can check --sigfile for ima_sign + [ "$type" = ima ] && opts+=" --sigfile" + + # shellcheck disable=SC2086 + ADD_TEXT_FOR="$alg ($key)" ADD_DEL=$file \ + _evmctl_run "$(_op "$type")sign" $opts \ + --hashalgo "$alg" --key "$key" --xattr-user "$file" || return + + if [ "$type" = ima ]; then + _test_sigfile "$file" "$(_xattr "$type")" "$file.sig" "$file.sig2" + fi +} + +# Run and test {ima_,}sign operation +check_sign() { + # Arguments are passed via global vars: + # TYPE (ima or evm), + # KEY, + # ALG (hash algo), + # PREFIX (signature header prefix in hex), + # OPTS (additional options for evmctl), + # FILE (working file to sign). + local "$@" + local KEY=${KEY%.*}.key + local FILE=${FILE:-$ALG.txt} + + # Normalize key filename + KEY=test-${KEY#test-} + + # Append suffix to files for negative tests, because we may + # leave only good files for verify tests. + _test_expected_to_fail && FILE+='~' + + rm -f $FILE + if ! touch $FILE; then + color_red + echo "Can't create test file: $FILE" + color_restore + return "$HARDFAIL" + fi + + if _test_expected_to_pass; then + # Can openssl work with this digest? + cmd="openssl dgst $OPENSSL_ENGINE -$ALG $FILE" + echo - "$cmd" + if ! $cmd >/dev/null; then + echo "${CYAN}$ALG ($KEY) test is skipped (openssl is unable to digest)$NORM" + return "$SKIP" + fi + + if [ ! -e "$KEY" ]; then + echo "${CYAN}$ALG ($KEY) test is skipped (key file not found)$NORM" + return "$SKIP" + fi + + # Can openssl sign with this digest and key? + cmd="openssl dgst $OPENSSL_ENGINE -$ALG -sign $KEY -hex $FILE" + echo - "$cmd" + if ! $cmd >/dev/null; then + echo "${CYAN}$ALG ($KEY) test is skipped (openssl is unable to sign)$NORM" + return "$SKIP" + fi + fi + + # Insert keyid from cert into PREFIX in-place of marker `:K:' + if [[ $PREFIX =~ :K: ]]; then + keyid=$(_keyid_from_cert "$KEY") + if [ $? -ne 0 ]; then + color_red + echo "Unable to determine keyid for $KEY" + color_restore + return "$HARDFAIL" + fi + [ "$VERBOSE" -gt 2 ] && echo " Expected keyid: $keyid" + PREFIX=${PREFIX/:K:/$keyid} + fi + + # Perform signing by evmctl + _evmctl_sign "$TYPE" "$KEY" "$ALG" "$FILE" "$OPTS" || return + + # First simple pattern match the signature. + ADD_TEXT_FOR=$ALG \ + _test_xattr "$FILE" "$(_xattr "$TYPE")" "$PREFIX.*" || return + + # This is all we can do for v1 signatures. + [[ "$OPTS" =~ --rsa ]] && return "$OK" + + # This is all we can do for evm. + [[ "$TYPE" =~ evm ]] && return "$OK" + + # Extract signature to a file + _extract_xattr "$FILE" "$(_xattr "$TYPE")" "$FILE.sig2" "$PREFIX" + + # Verify extracted signature with openssl + cmd="openssl dgst $OPENSSL_ENGINE -$ALG -verify ${KEY%.*}.pub \ + -signature $FILE.sig2 $FILE" + echo - "$cmd" + if ! $cmd; then + color_red_on_failure + echo "Signature v2 verification with openssl is failed." + color_restore + rm "$FILE.sig2" + return "$FAIL" + fi + + rm "$FILE.sig2" + return "$OK" +} + +# Test verify operation +check_verify() { + # Arguments are passed via global vars: + # TYPE (ima or evm), + # KEY, + # ALG (hash algo), + # OPTS (additional options for evmctl), + # FILE (filename to verify). + local "$@" + + # shellcheck disable=SC2086 + if ! openssl dgst $OPENSSL_ENGINE -"$ALG" /dev/null >/dev/null 2>&1; then + echo $CYAN"$ALG ($KEY) test is skipped (openssl does not support $ALG)"$NORM + return $SKIP + fi + + # shellcheck disable=SC2086 + ADD_TEXT_FOR="$FILE ($KEY)" \ + _evmctl_run "$(_op "$TYPE")verify" --key "$KEY" --xattr-user $OPTS "$FILE" +} + +# Test runners + +# Perform sign and verify ima and evm testing +sign_verify() { + local key=$1 alg=$2 prefix="$3" opts="$4" + local file=$alg.txt + + # Set defaults: + # Public key is different for v1 and v2 (where x509 cert is used). + if [[ $opts =~ --rsa ]]; then + KEY=test-$key.pub + else + KEY=test-$key.cer + fi + ALG=$alg + PREFIX=$prefix + OPTS=$opts + FILE=$file + + TYPE=ima + if expect_pass check_sign; then + + # Normal verify with proper key should pass + expect_pass check_verify + expect_pass check_verify OPTS="--sigfile" + + # Multiple files and some don't verify + expect_fail check_verify FILE="/dev/null $file" + + rm "$FILE.sig" + fi + + TYPE=evm + # Avoid running blkid for evm tests which may require root + # No generation on overlayfs: + # ioctl(3, FS_IOC_GETVERSION, 0x7ffd8e0bd628) = -1 ENOTTY (Inappropriate ioctl for device) + OPTS="$opts --uuid --generation 0" + if expect_pass check_sign; then + + # Normal verify with proper key + expect_pass check_verify + + # Verify with wrong key + expect_fail check_verify KEY=rsa2048 + fi + + # Note: Leaving TYPE=evm and file is evm signed +} + +# Test --keys +try_different_keys() { + # This run after sign_verify which leaves + # TYPE=evm and file is evm signed + + # v2 signing can work with multiple keys in --key option + if [[ ! $OPTS =~ --rsa ]]; then + + # Have correct key in the key list + expect_pass check_verify KEY="test-rsa2048.cer,$KEY" + expect_pass check_verify KEY="/dev/null,$KEY," + fi + + # Try key that is not used for signing + expect_fail check_verify KEY=rsa2048 + + # Try completely wrong key files + expect_fail check_verify KEY=/dev/null + expect_fail check_verify KEY=/dev/zero +} + +try_different_sigs() { + # TYPE=evm and file is evm signed + + # Test --imasig + if expect_pass check_sign OPTS="$OPTS --imasig"; then + + # Verify both evm and ima sigs + expect_pass check_verify + expect_pass check_verify TYPE=ima + fi + + # Test --imahash + if expect_pass check_sign OPTS="$OPTS --imahash"; then + + expect_pass check_verify + + # IMA hash is not verifiable by ima_verify + expect_fail check_verify TYPE=ima + fi + + # Test --portable + expect_pass check_sign OPTS="$OPTS --portable" PREFIX=0x05 + # Cannot be verified for now, until that support is added to evmctl + + # Test -i (immutable) + expect_pass check_sign OPTS="$OPTS -i" PREFIX=0x0303 + # Cannot be verified for now +} + +# Single test args: type key hash signature-prefix "evmctl-options" +# sign_verify args: key hash signature-prefix "evmctl-options" +# Only single test can be prefixed with expect_{fail,pass} +# `sign_verify' can not be prefixed with expect_{fail,pass} because +# it runs multiple tests inside. See more tests there. +# signature-prefix can contain `:K:' which will be resolved to keyid (v2 only) + +## Test v1 signatures +# Signature v1 only supports sha1 and sha256 so any other should fail +expect_fail \ + check_sign TYPE=ima KEY=rsa1024 ALG=md5 PREFIX=0x0301 OPTS=--rsa + +sign_verify rsa1024 sha1 0x0301 --rsa +sign_verify rsa1024 sha256 0x0301 --rsa + try_different_keys + try_different_sigs + +## Test v2 signatures with RSA PKCS#1 +# List of allowed hashes much greater but not all are supported. +sign_verify rsa1024 md5 0x030201:K:0080 +sign_verify rsa1024 sha1 0x030202:K:0080 +sign_verify rsa1024 sha224 0x030207:K:0080 +sign_verify rsa1024 sha256 0x030204:K:0080 + try_different_keys + try_different_sigs +sign_verify rsa1024 sha384 0x030205:K:0080 +sign_verify rsa1024 sha512 0x030206:K:0080 +sign_verify rsa1024 rmd160 0x030203:K:0080 + +# Test v2 signatures with EC-RDSA +_enable_gost_engine +sign_verify gost2012_256-A md_gost12_256 0x030212:K:0040 +sign_verify gost2012_256-B md_gost12_256 0x030212:K:0040 +sign_verify gost2012_256-C md_gost12_256 0x030212:K:0040 +sign_verify gost2012_512-A md_gost12_512 0x030213:K:0080 +sign_verify gost2012_512-B md_gost12_512 0x030213:K:0080 +# Test if signing with wrong key length does not work. +expect_fail \ + check_sign TYPE=ima KEY=gost2012_512-B ALG=md_gost12_256 PREFIX=0x0302 OPTS= +expect_fail \ + check_sign TYPE=ima KEY=gost2012_256-B ALG=md_gost12_512 PREFIX=0x0302 OPTS= + diff --git a/tests/test_ascii_runtime_measurements b/tests/test_ascii_runtime_measurements new file mode 100644 index 0000000..937b503 --- /dev/null +++ b/tests/test_ascii_runtime_measurements @@ -0,0 +1,3 @@ +10 cf41b43c4031672fcc2bd358b309ad33b977424f ima-ng sha256:f1b4c7c9b27e94569f4c2b64051c452bc609c3cb891dd7fae06b758f8bc83d14 boot_aggregate +10 983dcd8e6f7c84a1a5f10e762d1850623966ceab ima-ng sha256:ae06e032a65fed8102aff5f8f31c678dcf2eb25b826f77ecb699faa0411f89e0 /init +10 b6e4d01c73f6e4b698eaf48e7d76a2bae0c02514 ima-ng sha256:4b1764ee112aa8b2a6ae9a3a2f1e272b6601681f610708497673cd49e5bd2f5c /bin/sh diff --git a/tests/test_binary_bios_measurements b/tests/test_binary_bios_measurements new file mode 100644 index 0000000..338ba22 Binary files /dev/null and b/tests/test_binary_bios_measurements differ diff --git a/travis/alpine.sh b/travis/alpine.sh new file mode 100755 index 0000000..63d7954 --- /dev/null +++ b/travis/alpine.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel +set -ex + +if [ -z "$CC" ]; then + echo "missing \$CC!" >&2 + exit 1 +fi + +case "$TSS" in +ibmtss) echo "No IBM TSS package, will be installed from git" >&2; TSS=;; +tpm2-tss) TSS="tpm2-tss-dev";; +'') echo "Missing TSS!" >&2; exit 1;; +*) echo "Unsupported TSS: '$TSS'!" >&2; exit 1;; +esac + +# ibmswtpm2 requires gcc +[ "$CC" = "gcc" ] || CC="gcc $CC" + +apk update + +apk add \ + $CC $TSS \ + asciidoc \ + attr \ + attr-dev \ + autoconf \ + automake \ + diffutils \ + docbook-xml \ + docbook-xsl \ + keyutils-dev \ + libtool \ + libxslt \ + linux-headers \ + make \ + musl-dev \ + openssl \ + openssl-dev \ + pkgconfig \ + procps \ + sudo \ + wget \ + which \ + xxd + +if [ ! "$TSS" ]; then + apk add git + ../tests/install-tss.sh +fi diff --git a/travis/centos.sh b/travis/centos.sh new file mode 120000 index 0000000..1479a43 --- /dev/null +++ b/travis/centos.sh @@ -0,0 +1 @@ +fedora.sh \ No newline at end of file diff --git a/travis/debian.cross-compile.sh b/travis/debian.cross-compile.sh new file mode 100755 index 0000000..5456d12 --- /dev/null +++ b/travis/debian.cross-compile.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel +set -ex + +if [ -z "$ARCH" ]; then + echo "missing \$ARCH!" >&2 + exit 1 +fi + +case "$ARCH" in +arm64) gcc_arch="aarch64";; +ppc64el) gcc_arch="powerpc64le";; +s390x) gcc_arch="$ARCH";; +*) echo "unsupported arch: '$ARCH'!" >&2; exit 1;; +esac + +dpkg --add-architecture $ARCH +apt update + +apt install -y --no-install-recommends \ + dpkg-dev \ + gcc-${gcc_arch}-linux-gnu \ + libc6-dev-${ARCH}-cross diff --git a/travis/debian.i386.sh b/travis/debian.i386.sh new file mode 100755 index 0000000..1cad06e --- /dev/null +++ b/travis/debian.i386.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel +set -ex + +dpkg --add-architecture i386 +apt update + +apt install -y --no-install-recommends \ + linux-libc-dev:i386 \ + gcc-multilib \ + pkg-config:i386 diff --git a/travis/debian.sh b/travis/debian.sh new file mode 100755 index 0000000..ad7d2c0 --- /dev/null +++ b/travis/debian.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel +set -ex + +if [ -z "$CC" ]; then + echo "missing \$CC!" >&2 + exit 1 +fi + +# debian.*.sh must be run first +if [ "$ARCH" ]; then + ARCH=":$ARCH" + unset CC +else + apt update +fi + +# ibmswtpm2 requires gcc +[ "$CC" = "gcc" ] || CC="gcc $CC" + +case "$TSS" in +ibmtss) TSS="libtss-dev";; +tpm2-tss) TSS="libtss2-dev";; +'') echo "Missing TSS!" >&2; exit 1;; +*) [ "$TSS" ] && echo "Unsupported TSS: '$TSS'!" >&2; exit 1;; +esac + +apt="apt install -y --no-install-recommends" + +$apt \ + $CC $TSS \ + asciidoc \ + attr \ + autoconf \ + automake \ + diffutils \ + debianutils \ + docbook-xml \ + docbook-xsl \ + gzip \ + libattr1-dev$ARCH \ + libkeyutils-dev$ARCH \ + libssl-dev$ARCH \ + libtool \ + make \ + openssl \ + pkg-config \ + procps \ + sudo \ + wget \ + xsltproc \ + +$apt xxd || $apt vim-common +$apt libengine-gost-openssl1.1$ARCH || true diff --git a/travis/fedora.sh b/travis/fedora.sh new file mode 100755 index 0000000..2d80915 --- /dev/null +++ b/travis/fedora.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel +set -e + +if [ -z "$CC" ]; then + echo "missing \$CC!" >&2 + exit 1 +fi + +case "$TSS" in +ibmtss) TSS="tss2-devel";; +tpm2-tss) TSS="tpm2-tss-devel";; +'') echo "Missing TSS!" >&2; exit 1;; +*) echo "Unsupported TSS: '$TSS'!" >&2; exit 1;; +esac + +# ibmswtpm2 requires gcc +[ "$CC" = "gcc" ] || CC="gcc $CC" + +yum -y install \ + $CC $TSS \ + asciidoc \ + attr \ + autoconf \ + automake \ + diffutils \ + docbook-xsl \ + gzip \ + keyutils-libs-devel \ + libattr-devel \ + libtool \ + libxslt \ + make \ + openssl \ + openssl-devel \ + pkg-config \ + procps \ + sudo \ + vim-common \ + wget \ + which + +yum -y install docbook5-style-xsl || true +yum -y install swtpm || true diff --git a/travis/opensuse.sh b/travis/opensuse.sh new file mode 120000 index 0000000..11c5f4b --- /dev/null +++ b/travis/opensuse.sh @@ -0,0 +1 @@ +tumbleweed.sh \ No newline at end of file diff --git a/travis/tumbleweed.sh b/travis/tumbleweed.sh new file mode 100755 index 0000000..ecd2372 --- /dev/null +++ b/travis/tumbleweed.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# Copyright (c) 2020 Petr Vorel +set -ex + +if [ -z "$CC" ]; then + echo "missing \$CC!" >&2 + exit 1 +fi + +case "$TSS" in +ibmtss) TSS="ibmtss-devel";; +tpm2-tss) TSS="tpm2-0-tss-devel";; +'') echo "Missing TSS!" >&2; exit 1;; +*) echo "Unsupported TSS: '$TSS'!" >&2; exit 1;; +esac + +# clang has some gcc dependency +[ "$CC" = "gcc" ] || CC="gcc $CC" + +zypper --non-interactive install --force-resolution --no-recommends \ + $CC $TSS \ + asciidoc \ + attr \ + autoconf \ + automake \ + diffutils \ + docbook_5 \ + docbook5-xsl-stylesheets \ + gzip \ + ibmswtpm2 \ + keyutils-devel \ + libattr-devel \ + libopenssl-devel \ + libtool \ + make \ + openssl \ + pkg-config \ + procps \ + sudo \ + vim \ + wget \ + which \ + xsltproc + +if [ -f /usr/lib/ibmtss/tpm_server ]; then + ln -s /usr/lib/ibmtss/tpm_server /usr/local/bin +fi diff --git a/travis/ubuntu.sh b/travis/ubuntu.sh new file mode 120000 index 0000000..0edcb8b --- /dev/null +++ b/travis/ubuntu.sh @@ -0,0 +1 @@ +debian.sh \ No newline at end of file