From c6d22bec2886b9b5639c99a93d8495e4d2975d09 Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 14 2020 09:24:35 +0000 Subject: ima-evm-utils-1.1 base --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca7a06e --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +*.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 + + +# 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 +*.8 +*.5 +manpage.links +manpage.refs + +# quilt's files +patches +series + +# test output + 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/ChangeLog b/ChangeLog new file mode 100644 index 0000000..9efc348 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,92 @@ +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/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..06ebf59 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,42 @@ +SUBDIRS = src +dist_man_MANS = evmctl.1 + +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) + +# requires asciidoc, xslproc, docbook-xsl +MANPAGE_DOCBOOK_XSL = /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/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 + +.PHONY: $(tarname) diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/NEWS diff --git a/README b/README new file mode 100644 index 0000000..4805564 --- /dev/null +++ b/README @@ -0,0 +1,439 @@ +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_sign [--sigfile] [--key key] [--pass password] file + ima_verify file + ima_hash file + ima_measurement [--key "key1, key2, ..."] [--list] 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 + --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 + -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..d01bb43 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,16 @@ +#! /bin/sh + +set -e + +# new way +# strange, but need this for Makefile.am, because it has -I m4 +test -d m4 || mkdir m4 +autoreconf -f -i + +# old way +#libtoolize --automake --copy --force +#aclocal +#autoconf --force +#autoheader --force +#automake --add-missing --copy --force-missing --gnu + 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/configure.ac b/configure.ac new file mode 100644 index 0000000..6822f39 --- /dev/null +++ b/configure.ac @@ -0,0 +1,62 @@ +# autoconf script + +AC_PREREQ([2.65]) +AC_INIT(ima-evm-utils, 1.1, zohar@linux.vnet.ibm.com) +AM_INIT_AUTOMAKE +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_CANONICAL_HOST + +# 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(OPENSSL, [ openssl >= 0.9.8 ]) +AC_SUBST(OPENSSL_CFLAGS) +AC_SUBST(OPENSSL_LIBS) +AC_CHECK_HEADER(unistd.h) +AC_CHECK_HEADERS(openssl/conf.h) + +AC_CHECK_HEADERS(attr/xattr.h, , [AC_MSG_ERROR([attr/xattr.h header not found. You need the libattr development package.])]) +AC_CHECK_HEADERS(keyutils.h, , [AC_MSG_ERROR([keyutils.h header not found. You need the libkeyutils development package.])]) + +#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 + +# 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 + packaging/ima-evm-utils.spec + ]) +AC_OUTPUT + +# Give some feedback +echo +echo +echo "Configuration:" +echo " debug: $pkg_cv_enable_debug" +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/packaging/ima-evm-utils.spec.in b/packaging/ima-evm-utils.spec.in new file mode 100644 index 0000000..7ca6c6f --- /dev/null +++ b/packaging/ima-evm-utils.spec.in @@ -0,0 +1,53 @@ +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: libattr-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/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..deb18fb --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,22 @@ +lib_LTLIBRARIES = libimaevm.la + +libimaevm_la_SOURCES = libimaevm.c +libimaevm_la_CPPFLAGS = $(OPENSSL_CFLAGS) +# current[:revision[:age]] +# result: [current-age].age.revision +libimaevm_la_LDFLAGS = -version-info 0:0:0 +libimaevm_la_LIBADD = $(OPENSSL_LIBS) + +include_HEADERS = imaevm.h + +bin_PROGRAMS = evmctl + +evmctl_SOURCES = evmctl.c +evmctl_CPPFLAGS = $(OPENSSL_CFLAGS) +evmctl_LDFLAGS = $(LDFLAGS_READLINE) +evmctl_LDADD = $(OPENSSL_LIBS) -lkeyutils libimaevm.la + +INCLUDES = -I$(top_srcdir) -include config.h + +DISTCLEANFILES = @DISTCLEANFILES@ + diff --git a/src/evmctl.c b/src/evmctl.c new file mode 100644 index 0000000..2ffee78 --- /dev/null +++ b/src/evmctl.c @@ -0,0 +1,1877 @@ +/* + * 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 + +#define USE_FPRINTF + +#include "imaevm.h" + +static char *evm_default_xattrs[] = { + XATTR_NAME_SELINUX, + XATTR_NAME_SMACK, + 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_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 measurement_list; +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 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; + int len; + unsigned char *data; + char name[strlen(file) + (ext ? strlen(ext) : 0) + 2]; + + if (ext) + sprintf(name, "%s.%s", file, ext); + else + sprintf(name, "%s", file); + + log_info("Reading to %s\n", name); + + len = get_filesize(name); + fp = fopen(name, "r"); + if (!fp) { + log_err("Failed to open: %s\n", name); + return NULL; + } + data = malloc(len); + if (!fread(data, len, 1, fp)) + len = 0; + fclose(fp); + + *size = 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; +} + +static 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; +} + +static 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; +} + +#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) +{ + 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; + } + + err = EVP_DigestInit(pctx, EVP_sha1()); + 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) { + strcpy(xattr_value, selinux_str); + err = strlen(selinux_str) + 1; + } else if (!strcmp(*xattrname, XATTR_NAME_IMA) && ima_str) { + hex2bin(xattr_value, ima_str, strlen(ima_str) / 2); + err = strlen(ima_str) / 2; + } else if (!strcmp(*xattrname, XATTR_NAME_CAPS) && (hmac_flags & HMAC_FLAG_CAPS_SET)) { + if (!caps_str) + continue; + strcpy(xattr_value, caps_str); + err = strlen(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[20]; + unsigned char sig[1024]; + int len, err; + + len = calc_evm_hash(file, hash); + if (len <= 1) + return len; + + len = sign_hash("sha1", hash, len, key, NULL, sig + 1); + if (len <= 1) + return len; + + /* 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 || params.verbose >= LOG_INFO) + dump(sig, len); + + if (xattr) { + err = lsetxattr(file, "security.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[66]; /* MAX hash size + 2 */ + int len, err, offset; + int algo = get_hash_algo(params.hash_algo); + + 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; + + len += offset; + + if (params.verbose >= LOG_INFO) + log_info("hash: "); + + if (sigdump || params.verbose >= LOG_INFO) + dump(hash, len); + + if (xattr) { + err = lsetxattr(file, "security.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[64]; + unsigned char sig[1024]; + int len, err; + + len = ima_calc_hash(file, hash); + if (len <= 1) + return len; + + len = sign_hash(params.hash_algo, hash, len, key, NULL, sig + 1); + if (len <= 1) + return len; + + /* add header */ + len++; + sig[0] = EVM_IMA_XATTR_DIGSIG; + + if (sigdump || params.verbose >= LOG_INFO) + dump(sig, len); + + if (sigfile) + bin2file(file, "sig", sig, len); + + if (xattr) { + err = lsetxattr(file, "security.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 = 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[64]; + unsigned char sig[1024] = "\x03"; + int siglen; + + key = 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); + + hex2bin(hash, line, hashlen); + siglen = sign_hash(params.hash_algo, hash, hashlen/2, + key, NULL, sig + 1); + if (siglen <= 1) + return siglen; + + 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 = 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[20]; + unsigned char sig[1024]; + int len; + + len = calc_evm_hash(file, hash); + if (len <= 1) + return len; + + len = lgetxattr(file, "security.evm", sig, sizeof(sig)); + if (len < 0) { + log_err("getxattr failed: %s\n", file); + return len; + } + + if (sig[0] != 0x03) { + log_err("security.evm has no signature\n"); + return -1; + } + + return verify_hash(file, hash, sizeof(hash), 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; + } + + err = verify_evm(file); + if (!err && params.verbose >= LOG_INFO) + log_info("%s: verification is OK\n", file); + return err; +} + +static int verify_ima(const char *file) +{ + unsigned char sig[1024]; + int len; + + if (sigfile) { + void *tmp = file2bin(file, "sig", &len); + + memcpy(sig, tmp, len); + free(tmp); + } else { + len = lgetxattr(file, "security.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; + + errno = 0; + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return -1; + } + + err = verify_ima(file); + if (!err && params.verbose >= LOG_INFO) + log_info("%s: verification is OK\n", file); + return err; +} + +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; + + params.x509 = 0; + + inkey = g_argv[optind++]; + if (!inkey) { + inkey = params.x509 ? "/etc/keys/x509_evm.der" : + "/etc/keys/pubkey_evm.pem"; + } + + key = read_pub_key(inkey, 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]; + RSA *key; + + inkey = g_argv[optind++]; + if (!inkey) { + inkey = 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; + } + } + + key = read_pub_key(inkey, params.x509); + if (!key) + return 1; + + if (params.x509) { + pub = file2bin(inkey, NULL, &len); + if (!pub) + goto out; + calc_keyid_v2((uint32_t *)keyid, name, key); + } else { + len = key2bin(key, pub); + calc_keyid_v1(keyid, name, pub, len); + } + + log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id); + + id = add_key(params.x509 ? "asymmetric" : "user", 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 (params.x509) + free(pub); +out: + RSA_free(key); + 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, "security.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) +{ + 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; + } + + err = !HMAC_Init_ex(pctx, evmkey, sizeof(evmkey), EVP_sha1(), 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[20]; + unsigned char sig[1024]; + int len, err; + + len = calc_evm_hmac(file, key, hash); + if (len <= 1) + return len; + + log_info("hmac: "); + log_dump(hash, len); + memcpy(sig + 1, hash, len); + + if (xattr) { + sig[0] = EVM_XATTR_HMAC; + err = lsetxattr(file, "security.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 = 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, "security.ima")) + ima = 1; + else if (!strcmp(list, "security.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, "security.ima"); + lremovexattr(path, "security.evm"); + + return 0; +} + +static int cmd_ima_clear(struct command *cmd) +{ + return do_cmd(cmd, ima_clear); +} + +static char *pcrs = "/sys/class/tpm/tpm0/device/pcrs"; /* Kernels >= 4.0 */ +static char *misc_pcrs = "/sys/class/misc/tpm0/device/pcrs"; + +static int tpm_pcr_read(int idx, uint8_t *pcr, int len) +{ + FILE *fp; + char *p, pcr_str[7], buf[70]; /* length of the TPM string */ + int result = -1; + + sprintf(pcr_str, "PCR-%d", idx); + + fp = fopen(pcrs, "r"); + if (!fp) + fp = fopen(misc_pcrs, "r"); + + if (!fp) { + log_err("Unable to open %s or %s\n", pcrs, misc_pcrs); + return -1; + } + + for (;;) { + p = fgets(buf, sizeof(buf), fp); + if (!p) + break; + if (!strncmp(p, pcr_str, 6)) { + hex2bin(pcr, p + 7, len); + result = 0; + break; + } + } + fclose(fp); + return result; +} + +#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]; + int template_len; + uint8_t *template; + int template_buf_len; +}; + +static uint8_t zero[SHA_DIGEST_LENGTH]; +static uint8_t fox[SHA_DIGEST_LENGTH]; + +int validate = 1; + +void ima_extend_pcr(uint8_t *pcr, uint8_t *digest, int length) +{ + SHA_CTX ctx; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, pcr, length); + if (validate && !memcmp(digest, zero, length)) + SHA1_Update(&ctx, fox, length); + else + SHA1_Update(&ctx, digest, length); + SHA1_Final(pcr, &ctx); +} + +static int ima_verify_template_hash(struct template_entry *entry) +{ + uint8_t digest[SHA_DIGEST_LENGTH]; + + if (!memcmp(zero, entry->header.digest, sizeof(zero))) + return 0; + + SHA1(entry->template, entry->template_len, digest); + + if (memcmp(digest, entry->header.digest, sizeof(digest))) { + log_err("template hash error\n"); + return 1; + } + + return 0; +} + +void ima_show(struct template_entry *entry) +{ + log_debug("ima, digest: "); + log_debug_dump(entry->header.digest, sizeof(entry->header.digest)); +} + +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; + uint8_t *digest, *sig = NULL; + char *algo, *path; + 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; + } + } + + /* ascii_runtime_measurements */ + if (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 (sig) { + if (params.verbose > LOG_INFO) { + log_info(" "); + log_dump(sig, sig_len); + } + if (measurement_list) + err = ima_verify_signature(path, sig, sig_len, + digest, digest_len); + else + err = ima_verify_signature(path, sig, sig_len, NULL, 0); + if (!err && params.verbose > LOG_INFO) + log_info("%s: verification is OK\n", path); + } else { + if (params.verbose > LOG_INFO) + log_info("\n"); + } + + if (total_len) + log_err("Remain unprocessed data: %d\n", total_len); +} + +static int ima_measurement(const char *file) +{ + uint8_t pcr[NUM_PCRS][SHA_DIGEST_LENGTH] = {{0}}; + uint8_t hwpcr[SHA_DIGEST_LENGTH]; + struct template_entry entry = { .template = 0 }; + FILE *fp; + int err = -1; + bool verify_failed = false; + int i; + + errno = 0; + memset(zero, 0, SHA_DIGEST_LENGTH); + memset(fox, 0xff, SHA_DIGEST_LENGTH); + + log_debug("Initial PCR value: "); + log_debug_dump(pcr, sizeof(pcr)); + + fp = fopen(file, "rb"); + if (!fp) { + log_err("Failed to open measurement file: %s\n", file); + return -1; + } + + /* Support multiple public keys */ + if (params.keyfile) + init_public_keys(params.keyfile); + + while (fread(&entry.header, sizeof(entry.header), 1, fp)) { + ima_extend_pcr(pcr[entry.header.pcr], entry.header.digest, + SHA_DIGEST_LENGTH); + + if (!fread(entry.name, entry.header.name_len, 1, fp)) { + log_err("Unable to read template name\n"); + goto out; + } + + entry.name[entry.header.name_len] = '\0'; + + if (!fread(&entry.template_len, sizeof(entry.template_len), 1, fp)) { + log_err("Unable to read template length\n"); + goto out; + } + + 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 (!fread(entry.template, entry.template_len, 1, fp)) { + log_err("Unable to read template\n"); + goto out; + } + + if (validate) + ima_verify_template_hash(&entry); + + if (!strcmp(entry.name, "ima")) + ima_show(&entry); + else + ima_ng_show(&entry); + } + + + for (i = 0; i < NUM_PCRS; i++) { + if (memcmp(pcr[i], zero, SHA_DIGEST_LENGTH) == 0) + continue; + + log_info("PCRAgg %.2d: ", i); + log_dump(pcr[i], SHA_DIGEST_LENGTH); + + tpm_pcr_read(i, hwpcr, sizeof(hwpcr)); + log_info("HW PCR-%d: ", i); + log_dump(hwpcr, sizeof(hwpcr)); + + if (memcmp(pcr[i], hwpcr, sizeof(SHA_DIGEST_LENGTH)) != 0) { + log_err("PCRAgg %d does not match HW PCR-%d\n", i, i); + + verify_failed = true; + } + } + + if (!verify_failed) + err = 0; +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); +} + +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\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" + " --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" + " --list measurement list verification\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, "file", "Verify measurement list (experimental).\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}, + {"list", 0, 0, 138}, + {} + +}; + +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"); + return NULL; + } + + printf("PEM password: "); + pwd = fgets(password, passlen, stdin); + + /* restore terminal */ + if (tcsetattr(fileno(stdin), TCSANOW, &flags) != 0) { + perror("tcsetattr"); + return NULL; + } + + return pwd; +} + +int main(int argc, char *argv[]) +{ + int err = 0, c, lind; + + 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': + 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': + params.hash_algo = optarg; + break; + case 'p': + if (optarg) + params.keypass = optarg; + else + 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': + params.x509 = 0; + break; + case 'k': + 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: + measurement_list = 1; + 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)); + } + } + + ERR_free_strings(); + EVP_cleanup(); + BIO_free(NULL); + return err; +} diff --git a/src/imaevm.h b/src/imaevm.h new file mode 100644 index 0000000..1bafaad --- /dev/null +++ b/src/imaevm.h @@ -0,0 +1,213 @@ +/* + * 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 + +#ifdef USE_FPRINTF +#define do_log(level, fmt, args...) ({ if (level <= params.verbose) fprintf(stderr, fmt, ##args); }) +#define do_log_dump(level, p, len, cr) ({ if (level <= params.verbose) do_dump(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 __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; + +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__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; + + +typedef int (*verify_hash_fn_t)(const char *file, const unsigned char *hash, int size, unsigned char *sig, int siglen, const char *keyfile); + +struct libevm_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 20 +#define DEFAULT_PCR 10 + +extern const struct RSA_ASN1_template RSA_ASN1_templates[PKEY_HASH__LAST]; +extern struct libevm_params params; + +void do_dump(FILE *fp, const void *ptr, int len, bool cr); +void dump(const void *ptr, int len); +int get_filesize(const char *filename); +int ima_calc_hash(const char *file, uint8_t *hash); +int get_hash_algo(const char *algo); +RSA *read_pub_key(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, RSA *key); +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); + +#endif diff --git a/src/libimaevm.c b/src/libimaevm.c new file mode 100644 index 0000000..6fa0ed4 --- /dev/null +++ b/src/libimaevm.c @@ -0,0 +1,903 @@ +/* + * 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 "imaevm.h" + +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", +}; + +/* + * Hash algorithm OIDs plus ASN.1 DER wrappings [RFC4880 sec 5.2.2]. + */ +static const uint8_t RSA_digest_info_MD5[] = { + 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, /* OID */ + 0x05, 0x00, 0x04, 0x10 +}; + +static const uint8_t RSA_digest_info_SHA1[] = { + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2B, 0x0E, 0x03, 0x02, 0x1A, + 0x05, 0x00, 0x04, 0x14 +}; + +static const uint8_t RSA_digest_info_RIPE_MD_160[] = { + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2B, 0x24, 0x03, 0x02, 0x01, + 0x05, 0x00, 0x04, 0x14 +}; + +static const uint8_t RSA_digest_info_SHA224[] = { + 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, + 0x05, 0x00, 0x04, 0x1C +}; + +static const uint8_t RSA_digest_info_SHA256[] = { + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0x04, 0x20 +}; + +static const uint8_t RSA_digest_info_SHA384[] = { + 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + 0x05, 0x00, 0x04, 0x30 +}; + +static const uint8_t RSA_digest_info_SHA512[] = { + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, 0x04, 0x40 +}; + +const struct RSA_ASN1_template RSA_ASN1_templates[PKEY_HASH__LAST] = { +#define _(X) { RSA_digest_info_##X, sizeof(RSA_digest_info_##X) } + [PKEY_HASH_MD5] = _(MD5), + [PKEY_HASH_SHA1] = _(SHA1), + [PKEY_HASH_RIPE_MD_160] = _(RIPE_MD_160), + [PKEY_HASH_SHA256] = _(SHA256), + [PKEY_HASH_SHA384] = _(SHA384), + [PKEY_HASH_SHA512] = _(SHA512), + [PKEY_HASH_SHA224] = _(SHA224), +#undef _ +}; + +struct libevm_params params = { + .verbose = LOG_INFO - 1, + .x509 = 1, + .hash_algo = "sha1", +}; + +static void __attribute__ ((constructor)) libinit(void); + +void do_dump(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 dump(const void *ptr, int len) +{ + do_dump(stdout, ptr, len, true); +} + +int get_filesize(const char *filename) +{ + struct stat stats; + /* Need to know the file length */ + stat(filename, &stats); + return (int)stats.st_size; +} + +static inline off_t get_fdsize(int fd) +{ + struct stat stats; + /* Need to know the file length */ + fstat(fd, &stats); + return stats.st_size; +} + +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; + + 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; + } + + for (size = get_fdsize(fileno(fp)); 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"); + 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); + return err; + } + + md = EVP_get_digestbyname(params.hash_algo); + if (!md) { + log_err("EVP_get_digestbyname() failed\n"); + return 1; + } + + err = EVP_DigestInit(pctx, md); + if (!err) { + log_err("EVP_DigestInit() failed\n"); + return 1; + } + + 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"); + return -1; + } + + if (err) + return err; + + err = EVP_DigestFinal(pctx, hash, &mdlen); + if (!err) { + log_err("EVP_DigestFinal() failed\n"); + return 1; + } + + return mdlen; +} + +RSA *read_pub_key(const char *keyfile, int x509) +{ + FILE *fp; + RSA *key = NULL; + X509 *crt = NULL; + EVP_PKEY *pkey = NULL; + + fp = fopen(keyfile, "r"); + if (!fp) { + log_err("Failed to open keyfile: %s\n", keyfile); + return NULL; + } + + if (x509) { + crt = d2i_X509_fp(fp, NULL); + if (!crt) { + log_err("d2i_X509_fp() failed\n"); + goto out; + } + pkey = X509_extract_key(crt); + if (!pkey) { + log_err("X509_extract_key() failed\n"); + goto out; + } + key = EVP_PKEY_get1_RSA(pkey); + } else { + key = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL); + } + + if (!key) + log_err("PEM_read_RSA_PUBKEY() failed\n"); + +out: + if (pkey) + EVP_PKEY_free(pkey); + if (crt) + X509_free(crt); + fclose(fp); + return key; +} + +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); + 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]; + RSA *key; +}; +static struct public_key_entry *public_keys = NULL; + +static RSA *find_keyid(uint32_t keyid) +{ + struct public_key_entry *entry; + + for (entry = public_keys; entry != NULL; entry = entry->next) { + if (entry->keyid == keyid) + return entry->key; + } + return NULL; +} + +void init_public_keys(const char *keyfiles) +{ + struct public_key_entry *entry; + char *tmp_keyfiles; + char *keyfile; + int i = 1; + + tmp_keyfiles = strdup(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_key(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; + } +} + +int verify_hash_v2(const char *file, const unsigned char *hash, int size, + unsigned char *sig, int siglen, const char *keyfile) +{ + int err, len; + unsigned char out[1024]; + RSA *key; + struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig; + const struct RSA_ASN1_template *asn1; + + if (params.verbose > LOG_INFO) { + log_info("hash: "); + log_dump(hash, size); + } + + if (public_keys) { + key = find_keyid(hdr->keyid); + if (!key) { + log_err("%s: unknown keyid: %x\n", file, + __be32_to_cpup(&hdr->keyid)); + return -1; + } + } else { + key = read_pub_key(keyfile, 1); + if (!key) + return 1; + } + + + err = RSA_public_decrypt(siglen - sizeof(*hdr), sig + sizeof(*hdr), + out, key, RSA_PKCS1_PADDING); + if (err < 0) { + log_err("%s: RSA_public_decrypt() failed: %d\n", file, err); + return 1; + } + + len = err; + + asn1 = &RSA_ASN1_templates[hdr->hash_algo]; + + if (len < asn1->size || memcmp(out, asn1->data, asn1->size)) { + log_err("%s: verification failed: %d\n", file, err); + return -1; + } + + len -= asn1->size; + + if (len != size || memcmp(out + asn1->size, hash, len)) { + log_err("%s: verification failed: %d\n", file, err); + return -1; + } + + return 0; +} + +int get_hash_algo(const char *algo) +{ + int i; + + for (i = 0; i < PKEY_HASH__LAST; i++) + if (!strcmp(algo, pkey_hash_algo[i])) + return i; + + return PKEY_HASH_SHA1; +} + +static int get_hash_algo_from_sig(unsigned char *sig) +{ + uint8_t hashalgo; + + if (sig[0] == 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] == 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) +{ + const char *key; + int x509; + verify_hash_fn_t verify_hash; + + /* Get signature type from sig header */ + if (sig[0] == DIGSIG_VERSION_1) { + verify_hash = verify_hash_v1; + /* Read pubkey from RSA key */ + x509 = 0; + } else if (sig[0] == DIGSIG_VERSION_2) { + verify_hash = verify_hash_v2; + /* Read pubkey from x509 cert */ + x509 = 1; + } else + return -1; + + /* Determine what key to use for verification*/ + key = params.keyfile ? : x509 ? + "/etc/keys/x509_evm.der" : + "/etc/keys/pubkey_evm.pem"; + + return verify_hash(file, hash, size, sig, siglen, key); +} + +int ima_verify_signature(const char *file, unsigned char *sig, int siglen, + unsigned char *digest, int digestlen) +{ + unsigned char hash[64]; + int hashlen, sig_hash_algo; + + if (sig[0] != 0x03) { + log_err("security.ima has no signature\n"); + return -1; + } + + sig_hash_algo = get_hash_algo_from_sig(sig + 1); + if (sig_hash_algo < 0) { + log_err("Invalid signature\n"); + return -1; + } + /* Use hash algorithm as retrieved from signature */ + params.hash_algo = pkey_hash_algo[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; + + 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); + + if (params.verbose > LOG_INFO) { + id = __be64_to_cpup((__be64 *) keyid); + sprintf(str, "%llX", (unsigned long long)id); + log_info("keyid-v1: %s\n", str); + } +} + +void calc_keyid_v2(uint32_t *keyid, char *str, RSA *key) +{ + uint8_t sha1[SHA_DIGEST_LENGTH]; + unsigned char *pkey = NULL; + int len; + + len = i2d_RSAPublicKey(key, &pkey); + + SHA1(pkey, len, sha1); + + /* sha1[12 - 19] is exactly keyid from gpg file */ + memcpy(keyid, sha1 + 16, 4); + log_debug("keyid: "); + log_debug_dump(keyid, 4); + + if (params.verbose > LOG_INFO) { + sprintf(str, "%x", __be32_to_cpup(keyid)); + log_info("keyid: %s\n", str); + } + + free(pkey); +} + +static RSA *read_priv_key(const char *keyfile, const char *keypass) +{ + FILE *fp; + RSA *key; + + fp = fopen(keyfile, "r"); + if (!fp) { + log_err("Failed to open keyfile: %s\n", keyfile); + return NULL; + } + ERR_load_crypto_strings(); + key = PEM_read_RSAPrivateKey(fp, NULL, NULL, (void *)keypass); + if (!key) { + char str[256]; + + ERR_error_string(ERR_get_error(), str); + log_err("PEM_read_RSAPrivateKey() failed: %s\n", str); + } + + fclose(fp); + 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; +} + +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: "); + log_dump(hash, size); + + key = read_priv_key(keyfile, 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); + 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; +} + +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; + RSA *key; + char name[20]; + unsigned char *buf; + const struct RSA_ASN1_template *asn1; + + 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: "); + log_dump(hash, size); + + key = read_priv_key(keyfile, params.keypass); + if (!key) + return -1; + + hdr = (struct signature_v2_hdr *)sig; + hdr->version = (uint8_t) DIGSIG_VERSION_2; + + hdr->hash_algo = get_hash_algo(algo); + + calc_keyid_v2(&hdr->keyid, name, key); + + asn1 = &RSA_ASN1_templates[hdr->hash_algo]; + + buf = malloc(size + asn1->size); + if (!buf) + goto out; + + memcpy(buf, asn1->data, asn1->size); + memcpy(buf + asn1->size, hash, size); + len = RSA_private_encrypt(size + asn1->size, buf, hdr->sig, + key, RSA_PKCS1_PADDING); + if (len < 0) { + log_err("RSA_private_encrypt() failed: %d\n", len); + goto out; + } + + /* 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); +out: + if (buf) + free(buf); + RSA_free(key); + 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) + params.keypass = keypass; + + return params.x509 ? sign_hash_v2(hashalgo, hash, size, keyfile, sig) : + sign_hash_v1(hashalgo, hash, size, keyfile, sig); +} + +static void libinit() +{ + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +}