aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 06:55:39 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 06:55:39 +0000
commita94b62680ad9bcaeee71186fa4be3d2af12f51eb (patch)
treebf153cc41ae7aeecb590993a582eca8ca07f988b
parent9af7e8dc4f84dddba7864da972db300e510e5281 (diff)
parent84ff393b112706a82c491b8c8e6062635b503670 (diff)
downloadfsverity-utils-android13-mainline-sdkext-release.tar.gz
Change-Id: I6d4014c36afaa9f4f80fcf7ed7cee79e3eaae793
-rw-r--r--.github/workflows/ci.yml169
-rw-r--r--.gitignore4
-rw-r--r--Android.bp6
-rw-r--r--METADATA10
-rw-r--r--Makefile56
-rw-r--r--NEWS.md22
-rw-r--r--README.md66
-rw-r--r--common/fsverity_uapi.h14
-rw-r--r--include/libfsverity.h106
-rw-r--r--lib/compute_digest.c130
-rw-r--r--lib/lib_private.h5
-rw-r--r--lib/libfsverity.pc.in2
-rw-r--r--lib/sign_digest.c96
-rw-r--r--lib/utils.c34
-rw-r--r--man/fsverity.1.md215
-rw-r--r--programs/cmd_digest.c7
-rw-r--r--programs/cmd_dump_metadata.c163
-rw-r--r--programs/cmd_sign.c57
-rw-r--r--programs/fsverity.c101
-rw-r--r--programs/fsverity.h13
-rw-r--r--programs/test_compute_digest.c133
-rw-r--r--programs/utils.c59
-rw-r--r--programs/utils.h3
-rwxr-xr-xscripts/do-release.sh95
-rwxr-xr-xscripts/run-sparse.sh2
-rwxr-xr-xscripts/run-tests.sh364
26 files changed, 1655 insertions, 277 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..309013a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,169 @@
+# SPDX-License-Identifier: MIT
+# Copyright 2021 Google LLC
+#
+# Use of this source code is governed by an MIT-style
+# license that can be found in the LICENSE file or at
+# https://opensource.org/licenses/MIT.
+
+name: CI
+on: [pull_request]
+
+jobs:
+ static-linking-test:
+ name: Test building static library
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh static_linking
+
+ dynamic-linking-test:
+ name: Test building dynamic library
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh dynamic_linking
+
+ cplusplus-test:
+ name: Test using library from C++ program
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh cplusplus
+
+ uninstall-test:
+ name: Test uninstalling
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh uninstall
+
+ dash-test:
+ name: Test building using the dash shell
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh dash
+
+ license-test:
+ name: Test for correct license info
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh license
+
+ gcc-test:
+ name: Test with gcc
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh gcc
+
+ clang-test:
+ name: Test with clang
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang
+ - run: scripts/run-tests.sh clang
+
+ _32bit-test:
+ name: Test building 32-bit binaries
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo dpkg --add-architecture i386
+ sudo apt-get update
+ sudo apt-get install -y gcc-multilib libssl-dev:i386
+ - run: scripts/run-tests.sh 32bit
+
+ sanitizers-test:
+ name: Test with sanitizers enabled
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang llvm
+ - run: scripts/run-tests.sh sanitizers
+
+ valgrind-test:
+ name: Test with valgrind enabled
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y valgrind
+ - run: scripts/run-tests.sh valgrind
+
+ boringssl-test:
+ name: Test with BoringSSL
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Cache BoringSSL build
+ uses: actions/cache@v2
+ with:
+ key: boringssl
+ path: boringssl
+ - run: make boringssl
+ - run: scripts/run-tests.sh boringssl
+
+ char-test:
+ name: Test with unsigned/signed char
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - run: scripts/run-tests.sh unsigned_char signed_char
+
+ # FIXME: need a Windows build of libcrypto for this to work
+ #windows-build-test:
+ #name: Windows build tests
+ #runs-on: ubuntu-latest
+ #steps:
+ #- uses: actions/checkout@v2
+ #- name: Install dependencies
+ #run: |
+ #sudo apt-get update
+ #sudo apt-get install -y gcc-mingw-w64-i686 gcc-mingw-w64-x86-64
+ # - run: scripts/run-tests.sh windows_build
+
+ sparse-test:
+ name: Run sparse
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y sparse
+ - run: scripts/run-tests.sh sparse
+
+ clang-analyzer-test:
+ name: Run clang static analyzer
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang-tools
+ - run: scripts/run-tests.sh clang_analyzer
+
+ shellcheck-test:
+ name: Run shellcheck
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y shellcheck
+ - run: scripts/run-tests.sh shellcheck
diff --git a/.gitignore b/.gitignore
index 04f9a6f..3ea5ca6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,13 @@
+*.[1-9]
*.a
+*.exe
*.o
*.patch
*.so
*.so.*
/.build-config
+/boringssl
+/boringssl.tar.gz
/fsverity
/fsverity.sig
/run-tests.log
diff --git a/Android.bp b/Android.bp
index 025de57..d7abc42 100644
--- a/Android.bp
+++ b/Android.bp
@@ -63,6 +63,12 @@ cc_library {
export_include_dirs: ["include"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.compos",
+ ],
+ recovery_available: true,
+
srcs: [
"lib/*.c",
],
diff --git a/METADATA b/METADATA
index 6fb345f..14e8928 100644
--- a/METADATA
+++ b/METADATA
@@ -5,12 +5,12 @@ third_party {
type: GIT
value: "https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git"
}
- version: "v1.3"
- # would be NOTICE save for common/fsverity_uapi.h
+ version: "20e87c13075a8e5660a8d69fd6c93d4f7c5f01a5"
+ license_note: "would be NOTICE save for common/fsverity_uapi.h"
license_type: RESTRICTED
last_upgrade_date {
- year: 2021
- month: 1
- day: 19
+ year: 2022
+ month: 2
+ day: 7
}
}
diff --git a/Makefile b/Makefile
index 0354f62..2304a21 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,9 @@
# Define LIBDIR to override where to install libraries, like './configure
# --libdir' in autotools-based projects (default: PREFIX/lib)
#
+# Define MANDIR to override where to install man pages, like './configure
+# --mandir' in autotools-based projects (default: PREFIX/share/man)
+#
# Define DESTDIR to override the installation destination directory
# (default: empty string)
#
@@ -41,8 +44,13 @@ ifneq ($(findstring -mingw,$(shell $(CC) -dumpmachine 2>/dev/null)),)
MINGW = 1
endif
+# Set the CFLAGS. First give the warning-related flags (unconditionally, though
+# the user can override any of them by specifying the opposite flag); then give
+# the user-specified CFLAGS, defaulting to -O2 if none were specified.
+#
+# Use -Wno-deprecated-declarations to avoid warnings about the Engine API having
+# been deprecated in OpenSSL 3.0; the replacement isn't ready yet.
CFLAGS ?= -O2
-
override CFLAGS := -Wall -Wundef \
$(call cc-option,-Wdeclaration-after-statement) \
$(call cc-option,-Wimplicit-fallthrough) \
@@ -51,6 +59,7 @@ override CFLAGS := -Wall -Wundef \
$(call cc-option,-Wstrict-prototypes) \
$(call cc-option,-Wunused-parameter) \
$(call cc-option,-Wvla) \
+ $(call cc-option,-Wno-deprecated-declarations) \
$(CFLAGS)
override CPPFLAGS := -Iinclude -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(CPPFLAGS)
@@ -61,12 +70,14 @@ QUIET_CCLD = @echo ' CCLD ' $@;
QUIET_AR = @echo ' AR ' $@;
QUIET_LN = @echo ' LN ' $@;
QUIET_GEN = @echo ' GEN ' $@;
+QUIET_PANDOC = @echo ' PANDOC ' $@;
endif
USE_SHARED_LIB ?=
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
INCDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib
+MANDIR ?= $(PREFIX)/share/man
DESTDIR ?=
ifneq ($(MINGW),1)
PKGCONF ?= pkg-config
@@ -92,6 +103,7 @@ FSVERITY := fsverity$(EXEEXT)
fi
DEFAULT_TARGETS :=
+EXTRA_TARGETS :=
COMMON_HEADERS := $(wildcard common/*.h)
LDLIBS := $(shell "$(PKGCONF)" libcrypto --libs 2>/dev/null || echo -lcrypto)
CFLAGS += $(shell "$(PKGCONF)" libcrypto --cflags 2>/dev/null || echo)
@@ -160,6 +172,7 @@ FSVERITY_PROG_OBJ := $(PROG_COMMON_OBJ) \
programs/fsverity.o
ifneq ($(MINGW),1)
FSVERITY_PROG_OBJ += \
+ programs/cmd_dump_metadata.o \
programs/cmd_enable.o \
programs/cmd_measure.o
endif
@@ -186,9 +199,37 @@ DEFAULT_TARGETS += $(FSVERITY)
$(TEST_PROGRAMS): %$(EXEEXT): programs/%.o $(PROG_COMMON_OBJ) libfsverity.a
$(QUIET_CCLD) $(CC) -o $@ $+ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
+EXTRA_TARGETS += $(TEST_PROGRAMS)
+
##############################################################################
-SPECIAL_TARGETS := all test_programs check install uninstall help clean
+#### Manual pages
+
+man/fsverity.1:man/fsverity.1.md
+ $(QUIET_PANDOC) pandoc $+ -s -t man > $@
+
+MAN_PAGES := man/fsverity.1
+EXTRA_TARGETS += $(MAN_PAGES)
+
+##############################################################################
+
+# Support for downloading and building BoringSSL. The purpose of this is to
+# allow testing builds of fsverity-utils that link to BoringSSL instead of
+# OpenSSL, without having to use a system that uses BoringSSL natively.
+
+boringssl:
+ rm -rf boringssl boringssl.tar.gz
+ curl -s -o boringssl.tar.gz \
+ https://boringssl.googlesource.com/boringssl/+archive/refs/heads/master.tar.gz
+ mkdir boringssl
+ tar xf boringssl.tar.gz -C boringssl
+ cmake -B boringssl/build boringssl
+ $(MAKE) -C boringssl/build $(MAKEFLAGS)
+
+##############################################################################
+
+SPECIAL_TARGETS := all test_programs check install install-man uninstall \
+ help clean
FORCE:
@@ -232,6 +273,10 @@ install:all
> $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
chmod 644 $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
+install-man:$(MAN_PAGES)
+ install -d $(DESTDIR)$(MANDIR)/man1
+ install -m644 $+ $(DESTDIR)$(MANDIR)/man1/
+
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(FSVERITY)
rm -f $(DESTDIR)$(LIBDIR)/libfsverity.a
@@ -239,15 +284,18 @@ uninstall:
rm -f $(DESTDIR)$(LIBDIR)/libfsverity.so
rm -f $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
rm -f $(DESTDIR)$(INCDIR)/libfsverity.h
+ for page in $(notdir $(MAN_PAGES)); do \
+ rm -f $(DESTDIR)$(MANDIR)/man1/$$page; \
+ done
help:
@echo "Available targets:"
@echo "------------------"
- @for target in $(DEFAULT_TARGETS) $(TEST_PROGRAMS) $(SPECIAL_TARGETS); \
+ @for target in $(DEFAULT_TARGETS) $(EXTRA_TARGETS) $(SPECIAL_TARGETS); \
do \
echo $$target; \
done
clean:
- rm -f $(DEFAULT_TARGETS) $(TEST_PROGRAMS) \
+ rm -f $(DEFAULT_TARGETS) $(EXTRA_TARGETS) \
lib/*.o programs/*.o .build-config fsverity.sig
diff --git a/NEWS.md b/NEWS.md
index 8745745..c63dff9 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,27 @@
# fsverity-utils release notes
+## Version 1.5
+
+* Made the `fsverity sign` command and the `libfsverity_sign_digest()` function
+ support PKCS#11 tokens.
+
+* Avoided a compiler error when building with musl libc.
+
+* Avoided compiler warnings when building with OpenSSL 3.0.
+
+* Improved documentation and test scripts.
+
+## Version 1.4
+
+* Added a manual page for the `fsverity` utility.
+
+* Added the `fsverity dump_metadata` subcommand.
+
+* Added the `--out-merkle-tree` and `--out-descriptor` options to
+ `fsverity digest` and `fsverity sign`.
+
+* Added metadata callbacks support to `libfsverity_compute_digest()`.
+
## Version 1.3
* Added a `fsverity digest` subcommand.
diff --git a/README.md b/README.md
index 2b63488..ffa25fd 100644
--- a/README.md
+++ b/README.md
@@ -24,20 +24,22 @@ See `libfsverity.h` for the API of this library.
## Building and installing
-fsverity-utils uses the OpenSSL library, so you first must install the
-needed development files. For example, on Debian-based systems, run:
+To build fsverity-utils, first install the needed build dependencies. For
+example, on Debian-based systems, run:
```bash
sudo apt-get install libssl-dev
+ sudo apt-get install pandoc # optional
```
-OpenSSL must be version 1.0.0 or later.
+OpenSSL must be version 1.0.0 or later. This is the only runtime dependency.
Then, to build and install fsverity-utils:
```bash
make
sudo make install
+ sudo make install-man # optional
```
By default, the following targets are built and installed: the program
@@ -45,6 +47,9 @@ By default, the following targets are built and installed: the program
`libfsverity.so`. You can also run `make check` to build and run the
tests, or `make help` to display all available build targets.
+`make install-man` installs the `fsverity.1` manual page. This step requires
+that `pandoc` be installed.
+
By default, `fsverity` is statically linked to `libfsverity`. You can
use `make USE_SHARED_LIB=1` to use dynamic linking instead.
@@ -63,6 +68,9 @@ A Windows build of OpenSSL/libcrypto needs to be available.
## Examples
+Full usage information for `fsverity` can be found in the manual page
+(`man fsverity`). Here, we just show some typical examples.
+
### Basic use
```bash
@@ -94,11 +102,44 @@ against a trusted value.
### Using builtin signatures
-With `CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y`, the filesystem supports
-automatically verifying a signed file digest that has been included in
-the verity metadata. The signature is verified against the set of
-X.509 certificates that have been loaded into the ".fs-verity" kernel
-keyring. Here's an example:
+First, note that fs-verity is essentially just a way of hashing a
+file; it doesn't mandate a specific way of handling signatures.
+There are several possible ways that signatures could be handled:
+
+* Do it entirely in userspace
+* Use IMA appraisal (work-in-progress)
+* Use fs-verity built-in signatures
+
+Any such solution needs two parts: (a) a policy that determines which
+files are required to have fs-verity enabled and have a valid
+signature, and (b) enforcement of the policy. Each part could happen
+either in a trusted userspace program(s) or in the kernel.
+
+fs-verity built-in signatures (which are supported when the kernel was
+built with `CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y`) are a hybrid
+solution where the policy of which files are required to be signed is
+determined and enforced by a trusted userspace program, but the actual
+signature verification happens in the kernel. Specifically, with
+built-in signatures, the filesystem supports storing a signed file
+digest in each file's verity metadata. Before allowing access to the
+file, the filesystem will automatically verify the signature against
+the set of X.509 certificates in the ".fs-verity" kernel keyring. If
+set, the sysctl `fs.verity.require_signatures=1` will make the kernel
+enforce that every verity file has a valid built-in signature.
+
+fs-verity built-in signatures are primarily intended as a
+proof-of-concept; they reuse the kernel code that verifies the
+signatures of loadable kernel modules. This solution still requires a
+trusted userspace program to enforce that particular files have
+fs-verity enabled. Also, this solution uses PKCS#7 signatures, which
+are complex and prone to security bugs.
+
+Thus, if possible one of the other solutions should be used instead.
+For example, the trusted userspace program could verify signatures
+itself, using a simple signature format using a modern algorithm such
+as Ed25519.
+
+That being said, here are some examples of using built-in signatures:
```bash
# Generate a new certificate and private key:
@@ -129,15 +170,6 @@ keyring. Here's an example:
fsverity digest file --compact --for-builtin-sig | tr -d '\n' | xxd -p -r | openssl smime -sign -in /dev/stdin ...
```
-By default, it's not required that verity files have a signature.
-This can be changed with `sysctl fs.verity.require_signatures=1`.
-When set, it's guaranteed that the contents of every verity file has
-been signed by one of the certificates in the keyring.
-
-Note: applications generally still need to check whether the file
-they're accessing really is a verity file, since an attacker could
-replace a verity file with a regular one.
-
### With IMA
IMA support for fs-verity is planned.
diff --git a/common/fsverity_uapi.h b/common/fsverity_uapi.h
index a739c9a..c59a897 100644
--- a/common/fsverity_uapi.h
+++ b/common/fsverity_uapi.h
@@ -85,7 +85,21 @@ struct fsverity_formatted_digest {
__u8 digest[];
};
+#define FS_VERITY_METADATA_TYPE_MERKLE_TREE 1
+#define FS_VERITY_METADATA_TYPE_DESCRIPTOR 2
+#define FS_VERITY_METADATA_TYPE_SIGNATURE 3
+
+struct fsverity_read_metadata_arg {
+ __u64 metadata_type;
+ __u64 offset;
+ __u64 length;
+ __u64 buf_ptr;
+ __u64 __reserved;
+};
+
#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg)
#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)
+#define FS_IOC_READ_VERITY_METADATA \
+ _IOWR('f', 135, struct fsverity_read_metadata_arg)
#endif /* _UAPI_LINUX_FSVERITY_H */
diff --git a/include/libfsverity.h b/include/libfsverity.h
index 6c42e5e..a0a1527 100644
--- a/include/libfsverity.h
+++ b/include/libfsverity.h
@@ -22,7 +22,7 @@ extern "C" {
#include <stdint.h>
#define FSVERITY_UTILS_MAJOR_VERSION 1
-#define FSVERITY_UTILS_MINOR_VERSION 3
+#define FSVERITY_UTILS_MINOR_VERSION 5
#define FS_VERITY_HASH_ALG_SHA256 1
#define FS_VERITY_HASH_ALG_SHA512 2
@@ -61,8 +61,18 @@ struct libfsverity_merkle_tree_params {
/** @reserved1: must be 0 */
uint64_t reserved1[8];
+ /**
+ * @metadata_callbacks: if non-NULL, this gives a set of callback
+ * functions to which libfsverity_compute_digest() will pass the Merkle
+ * tree blocks and fs-verity descriptor after they are computed.
+ * Normally this isn't useful, but this can be needed in rare cases
+ * where the metadata needs to be consumed by something other than one
+ * of the native Linux kernel implementations of fs-verity.
+ */
+ const struct libfsverity_metadata_callbacks *metadata_callbacks;
+
/** @reserved2: must be 0 */
- uintptr_t reserved2[8];
+ uintptr_t reserved2[7];
};
struct libfsverity_digest {
@@ -71,11 +81,75 @@ struct libfsverity_digest {
uint8_t digest[]; /* the actual digest */
};
+/**
+ * struct libfsverity_signature_params - certificate and private key information
+ *
+ * Zero this, then set @certfile. Then, to specify the private key by key file,
+ * set @keyfile. Alternatively, to specify the private key by PKCS#11 token,
+ * set @pkcs11_engine, @pkcs11_module, and optionally @pkcs11_keyid.
+ *
+ * Support for PKCS#11 tokens is unavailable when libfsverity was linked to
+ * BoringSSL rather than OpenSSL.
+ */
struct libfsverity_signature_params {
- const char *keyfile; /* path to key file (PEM format) */
- const char *certfile; /* path to certificate (PEM format) */
- uint64_t reserved1[8]; /* must be 0 */
- uintptr_t reserved2[8]; /* must be 0 */
+
+ /** @keyfile: the path to the key file in PEM format, when applicable */
+ const char *keyfile;
+
+ /** @certfile: the path to the certificate file in PEM format */
+ const char *certfile;
+
+ /** @reserved1: must be 0 */
+ uint64_t reserved1[8];
+
+ /**
+ * @pkcs11_engine: the path to the PKCS#11 engine .so file, when
+ * applicable
+ */
+ const char *pkcs11_engine;
+
+ /**
+ * @pkcs11_module: the path to the PKCS#11 module .so file, when
+ * applicable
+ */
+ const char *pkcs11_module;
+
+ /** @pkcs11_keyid: the PKCS#11 key identifier, when applicable */
+ const char *pkcs11_keyid;
+
+ /** @reserved2: must be 0 */
+ uintptr_t reserved2[5];
+};
+
+struct libfsverity_metadata_callbacks {
+
+ /** @ctx: context passed to the below callbacks (opaque to library) */
+ void *ctx;
+
+ /**
+ * @merkle_tree_size: if non-NULL, called with the total size of the
+ * Merkle tree in bytes, prior to any call to @merkle_tree_block. Must
+ * return 0 on success, or a negative errno value on failure.
+ */
+ int (*merkle_tree_size)(void *ctx, uint64_t size);
+
+ /**
+ * @merkle_tree_block: if non-NULL, called with each block of the
+ * Merkle tree after it is computed. The offset is the offset in bytes
+ * to the block within the Merkle tree, using the Merkle tree layout
+ * used by FS_IOC_READ_VERITY_METADATA. The offsets won't necessarily
+ * be in increasing order. Must return 0 on success, or a negative
+ * errno value on failure.
+ */
+ int (*merkle_tree_block)(void *ctx, const void *block, size_t size,
+ uint64_t offset);
+
+ /**
+ * @descriptor: if non-NULL, called with the fs-verity descriptor after
+ * it is computed. Must return 0 on success, or a negative errno value
+ * on failure.
+ */
+ int (*descriptor)(void *ctx, const void *descriptor, size_t size);
};
/*
@@ -101,7 +175,8 @@ typedef int (*libfsverity_read_fn_t)(void *fd, void *buf, size_t count);
*
* Returns:
* * 0 for success, -EINVAL for invalid input arguments, -ENOMEM if libfsverity
- * failed to allocate memory, or an error returned by @read_fn.
+ * failed to allocate memory, or an error returned by @read_fn or by one of
+ * the @params->metadata_callbacks.
* * digest_ret returns a pointer to the digest on success. The digest object
* is allocated by libfsverity and must be freed by the caller using free().
*/
@@ -111,16 +186,15 @@ libfsverity_compute_digest(void *fd, libfsverity_read_fn_t read_fn,
struct libfsverity_digest **digest_ret);
/**
- * libfsverity_sign_digest() - Sign previously computed digest of a file
- * This signature is used by the filesystem to validate the signed file
- * digest against a public key loaded into the .fs-verity kernel
- * keyring, when CONFIG_FS_VERITY_BUILTIN_SIGNATURES is enabled. The
- * signature is formatted as PKCS#7 stored in DER format. See
- * Documentation/filesystems/fsverity.rst in the kernel source tree for
- * further details.
+ * libfsverity_sign_digest() - Sign a file for built-in signature verification
+ * Sign a file digest in a way that is compatible with the Linux
+ * kernel's fs-verity built-in signature verification support. The
+ * resulting signature will be a PKCS#7 message in DER format. Note
+ * that this is not the only way to do signatures with fs-verity. For
+ * more details, refer to the fsverity-utils README and to
+ * Documentation/filesystems/fsverity.rst in the kernel source tree.
* @digest: pointer to previously computed digest
- * @sig_params: struct libfsverity_signature_params providing filenames of
- * the keyfile and certificate file. Reserved fields must be zero.
+ * @sig_params: pointer to the certificate and private key information
* @sig_ret: Pointer to pointer for signed digest
* @sig_size_ret: Pointer to size of signed return digest
*
diff --git a/lib/compute_digest.c b/lib/compute_digest.c
index a4f649c..c5b0100 100644
--- a/lib/compute_digest.c
+++ b/lib/compute_digest.c
@@ -24,9 +24,8 @@ struct block_buffer {
/*
* Hash a block, writing the result to the next level's pending block buffer.
- * Returns true if the next level's block became full, else false.
*/
-static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
+static void hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
u32 block_size, const u8 *salt, u32 salt_size)
{
struct block_buffer *next = cur + 1;
@@ -41,8 +40,60 @@ static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
next->filled += hash->alg->digest_size;
cur->filled = 0;
+}
+
+static bool block_is_full(const struct block_buffer *block, u32 block_size,
+ struct hash_ctx *hash)
+{
+ /* Would the next hash put us over the limit? */
+ return block->filled + hash->alg->digest_size > block_size;
+}
+
+static int report_merkle_tree_size(const struct libfsverity_metadata_callbacks *cbs,
+ u64 size)
+{
+ if (cbs && cbs->merkle_tree_size) {
+ int err = cbs->merkle_tree_size(cbs->ctx, size);
- return next->filled + hash->alg->digest_size > block_size;
+ if (err) {
+ libfsverity_error_msg("error processing Merkle tree size");
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int report_merkle_tree_block(const struct libfsverity_metadata_callbacks *cbs,
+ const struct block_buffer *block,
+ u32 block_size, u64 *level_offset)
+{
+
+ if (cbs && cbs->merkle_tree_block) {
+ int err = cbs->merkle_tree_block(cbs->ctx, block->data,
+ block_size,
+ *level_offset * block_size);
+
+ if (err) {
+ libfsverity_error_msg("error processing Merkle tree block");
+ return err;
+ }
+ (*level_offset)++;
+ }
+ return 0;
+}
+
+static int report_descriptor(const struct libfsverity_metadata_callbacks *cbs,
+ const void *descriptor, size_t size)
+{
+ if (cbs && cbs->descriptor) {
+ int err = cbs->descriptor(cbs->ctx, descriptor, size);
+
+ if (err) {
+ libfsverity_error_msg("error processing fs-verity descriptor");
+ return err;
+ }
+ }
+ return 0;
}
/*
@@ -52,6 +103,7 @@ static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
u64 file_size, struct hash_ctx *hash,
u32 block_size, const u8 *salt, u32 salt_size,
+ const struct libfsverity_metadata_callbacks *metadata_cbs,
u8 *root_hash)
{
const u32 hashes_per_block = block_size / hash->alg->digest_size;
@@ -60,6 +112,7 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
u64 blocks;
int num_levels = 0;
int level;
+ u64 level_offset[FS_VERITY_MAX_LEVELS];
struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {};
struct block_buffer *buffers = &_buffers[1];
u64 offset;
@@ -68,7 +121,7 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
/* Root hash of empty file is all 0's */
if (file_size == 0) {
memset(root_hash, 0, hash->alg->digest_size);
- return 0;
+ return report_merkle_tree_size(metadata_cbs, 0);
}
if (salt_size != 0) {
@@ -78,15 +131,39 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
memcpy(padded_salt, salt, salt_size);
}
- /* Compute number of levels */
- for (blocks = DIV_ROUND_UP(file_size, block_size); blocks > 1;
- blocks = DIV_ROUND_UP(blocks, hashes_per_block)) {
+ /* Compute number of levels and the number of blocks in each level. */
+ blocks = DIV_ROUND_UP(file_size, block_size);
+ while (blocks > 1) {
if (WARN_ON(num_levels >= FS_VERITY_MAX_LEVELS)) {
err = -EINVAL;
goto out;
}
- num_levels++;
+ blocks = DIV_ROUND_UP(blocks, hashes_per_block);
+ /*
+ * Temporarily use level_offset[] to store the number of blocks
+ * in each level. It will be overwritten later.
+ */
+ level_offset[num_levels++] = blocks;
+ }
+
+ /*
+ * Compute the starting block of each level, using the convention where
+ * the root level is first, i.e. the convention used by
+ * FS_IOC_READ_VERITY_METADATA. At the same time, compute the total
+ * size of the Merkle tree. These values are only needed for the
+ * metadata callbacks (if they were given), as the hash computation
+ * itself doesn't prescribe an ordering of the levels and doesn't
+ * prescribe any special meaning to the total size of the Merkle tree.
+ */
+ offset = 0;
+ for (level = num_levels - 1; level >= 0; level--) {
+ blocks = level_offset[level];
+ level_offset[level] = offset;
+ offset += blocks;
}
+ err = report_merkle_tree_size(metadata_cbs, offset * block_size);
+ if (err)
+ goto out;
/*
* Allocate the block buffers. Buffer "-1" is for data blocks.
@@ -112,21 +189,33 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
goto out;
}
- level = -1;
- while (hash_one_block(hash, &buffers[level], block_size,
- padded_salt, padded_salt_size)) {
- level++;
- if (WARN_ON(level >= num_levels)) {
- err = -EINVAL;
+ hash_one_block(hash, &buffers[-1], block_size,
+ padded_salt, padded_salt_size);
+ for (level = 0; level < num_levels; level++) {
+ if (!block_is_full(&buffers[level], block_size, hash))
+ break;
+ hash_one_block(hash, &buffers[level], block_size,
+ padded_salt, padded_salt_size);
+ err = report_merkle_tree_block(metadata_cbs,
+ &buffers[level],
+ block_size,
+ &level_offset[level]);
+ if (err)
goto out;
- }
}
}
/* Finish all nonempty pending tree blocks */
for (level = 0; level < num_levels; level++) {
- if (buffers[level].filled != 0)
+ if (buffers[level].filled != 0) {
hash_one_block(hash, &buffers[level], block_size,
padded_salt, padded_salt_size);
+ err = report_merkle_tree_block(metadata_cbs,
+ &buffers[level],
+ block_size,
+ &level_offset[level]);
+ if (err)
+ goto out;
+ }
}
/* Root hash was filled by the last call to hash_one_block() */
@@ -217,8 +306,13 @@ libfsverity_compute_digest(void *fd, libfsverity_read_fn_t read_fn,
}
err = compute_root_hash(fd, read_fn, params->file_size, hash,
- block_size, params->salt,
- params->salt_size, desc.root_hash);
+ block_size, params->salt, params->salt_size,
+ params->metadata_callbacks, desc.root_hash);
+ if (err)
+ goto out;
+
+ err = report_descriptor(params->metadata_callbacks,
+ &desc, sizeof(desc));
if (err)
goto out;
diff --git a/lib/lib_private.h b/lib/lib_private.h
index 7768eea..8532636 100644
--- a/lib/lib_private.h
+++ b/lib/lib_private.h
@@ -58,14 +58,11 @@ void *libfsverity_zalloc(size_t size);
void *libfsverity_memdup(const void *mem, size_t size);
__cold void
-libfsverity_do_error_msg(const char *format, va_list va, int err);
+libfsverity_do_error_msg(const char *format, va_list va);
__printf(1, 2) __cold void
libfsverity_error_msg(const char *format, ...);
-__printf(1, 2) __cold void
-libfsverity_error_msg_errno(const char *format, ...);
-
__cold void
libfsverity_warn_on(const char *condition, const char *file, int line);
diff --git a/lib/libfsverity.pc.in b/lib/libfsverity.pc.in
index be3ef44..4c9bb20 100644
--- a/lib/libfsverity.pc.in
+++ b/lib/libfsverity.pc.in
@@ -4,7 +4,7 @@ includedir=@INCDIR@
Name: libfsverity
Description: fs-verity library
-Version: 1.3
+Version: 1.5
Libs: -L${libdir} -lfsverity
Requires.private: libcrypto
Cflags: -I${includedir}
diff --git a/lib/sign_digest.c b/lib/sign_digest.c
index 9a35256..d726772 100644
--- a/lib/sign_digest.c
+++ b/lib/sign_digest.c
@@ -19,6 +19,10 @@
#include <openssl/pkcs7.h>
#include <string.h>
+#ifndef OPENSSL_IS_BORINGSSL
+#include <openssl/engine.h>
+#endif
+
static int print_openssl_err_cb(const char *str,
size_t len __attribute__((unused)),
void *u __attribute__((unused)))
@@ -34,7 +38,7 @@ error_msg_openssl(const char *format, ...)
va_list va;
va_start(va, format);
- libfsverity_do_error_msg(format, va, 0);
+ libfsverity_do_error_msg(format, va);
va_end(va);
if (ERR_peek_error() == 0)
@@ -81,6 +85,11 @@ static int read_certificate(const char *certfile, X509 **cert_ret)
X509 *cert;
int err;
+ if (!certfile) {
+ libfsverity_error_msg("no certificate specified");
+ return -EINVAL;
+ }
+
errno = 0;
bio = BIO_new_file(certfile, "r");
if (!bio) {
@@ -212,6 +221,15 @@ out:
return err;
}
+static int
+load_pkcs11_private_key(const struct libfsverity_signature_params *sig_params
+ __attribute__((unused)),
+ EVP_PKEY **pkey_ret __attribute__((unused)))
+{
+ libfsverity_error_msg("BoringSSL doesn't support PKCS#11 tokens");
+ return -EINVAL;
+}
+
#else /* OPENSSL_IS_BORINGSSL */
static BIO *new_mem_buf(const void *buf, size_t size)
@@ -315,16 +333,79 @@ out:
return err;
}
+static int
+load_pkcs11_private_key(const struct libfsverity_signature_params *sig_params,
+ EVP_PKEY **pkey_ret)
+{
+ ENGINE *engine;
+
+ if (!sig_params->pkcs11_engine) {
+ libfsverity_error_msg("no PKCS#11 engine specified");
+ return -EINVAL;
+ }
+ if (!sig_params->pkcs11_module) {
+ libfsverity_error_msg("no PKCS#11 module specified");
+ return -EINVAL;
+ }
+ ENGINE_load_dynamic();
+ engine = ENGINE_by_id("dynamic");
+ if (!engine) {
+ error_msg_openssl("failed to initialize OpenSSL PKCS#11 engine");
+ return -EINVAL;
+ }
+ if (!ENGINE_ctrl_cmd_string(engine, "SO_PATH",
+ sig_params->pkcs11_engine, 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "ID", "pkcs11", 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "LOAD", NULL, 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "MODULE_PATH",
+ sig_params->pkcs11_module, 0) ||
+ !ENGINE_init(engine)) {
+ error_msg_openssl("failed to initialize OpenSSL PKCS#11 engine");
+ ENGINE_free(engine);
+ return -EINVAL;
+ }
+ *pkey_ret = ENGINE_load_private_key(engine, sig_params->pkcs11_keyid,
+ NULL, NULL);
+ ENGINE_finish(engine);
+ ENGINE_free(engine);
+ if (!*pkey_ret) {
+ error_msg_openssl("failed to load private key from PKCS#11 token");
+ return -EINVAL;
+ }
+ return 0;
+}
+
#endif /* !OPENSSL_IS_BORINGSSL */
+/* Get a private key, either from disk or from a PKCS#11 token. */
+static int
+get_private_key(const struct libfsverity_signature_params *sig_params,
+ EVP_PKEY **pkey_ret)
+{
+ if (sig_params->pkcs11_engine || sig_params->pkcs11_module ||
+ sig_params->pkcs11_keyid) {
+ if (sig_params->keyfile) {
+ libfsverity_error_msg("private key must be specified either by file or by PKCS#11 token, not both");
+ return -EINVAL;
+ }
+ return load_pkcs11_private_key(sig_params, pkey_ret);
+ }
+ if (!sig_params->keyfile) {
+ libfsverity_error_msg("no private key specified");
+ return -EINVAL;
+ }
+ return read_private_key(sig_params->keyfile, pkey_ret);
+}
+
LIBEXPORT int
libfsverity_sign_digest(const struct libfsverity_digest *digest,
const struct libfsverity_signature_params *sig_params,
u8 **sig_ret, size_t *sig_size_ret)
{
const struct fsverity_hash_alg *hash_alg;
- EVP_PKEY *pkey = NULL;
X509 *cert = NULL;
+ EVP_PKEY *pkey = NULL;
const EVP_MD *md;
struct fsverity_formatted_digest *d = NULL;
int err;
@@ -334,11 +415,6 @@ libfsverity_sign_digest(const struct libfsverity_digest *digest,
return -EINVAL;
}
- if (!sig_params->keyfile || !sig_params->certfile) {
- libfsverity_error_msg("keyfile and certfile must be specified");
- return -EINVAL;
- }
-
if (!libfsverity_mem_is_zeroed(sig_params->reserved1,
sizeof(sig_params->reserved1)) ||
!libfsverity_mem_is_zeroed(sig_params->reserved2,
@@ -353,11 +429,11 @@ libfsverity_sign_digest(const struct libfsverity_digest *digest,
return -EINVAL;
}
- err = read_private_key(sig_params->keyfile, &pkey);
+ err = read_certificate(sig_params->certfile, &cert);
if (err)
goto out;
- err = read_certificate(sig_params->certfile, &cert);
+ err = get_private_key(sig_params, &pkey);
if (err)
goto out;
@@ -383,8 +459,8 @@ libfsverity_sign_digest(const struct libfsverity_digest *digest,
err = sign_pkcs7(d, sizeof(*d) + digest->digest_size,
pkey, cert, md, sig_ret, sig_size_ret);
out:
- EVP_PKEY_free(pkey);
X509_free(cert);
+ EVP_PKEY_free(pkey);
free(d);
return err;
}
diff --git a/lib/utils.c b/lib/utils.c
index 036dd60..d506ef1 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -51,16 +51,7 @@ libfsverity_set_error_callback(void (*cb)(const char *msg))
libfsverity_error_cb = cb;
}
-#ifdef _WIN32
-static char *strerror_r(int errnum, char *buf, size_t buflen)
-{
- strerror_s(buf, buflen, errnum);
-
- return buf;
-}
-#endif
-
-void libfsverity_do_error_msg(const char *format, va_list va, int err)
+void libfsverity_do_error_msg(const char *format, va_list va)
{
int saved_errno = errno;
char *msg = NULL;
@@ -71,18 +62,8 @@ void libfsverity_do_error_msg(const char *format, va_list va, int err)
if (vasprintf(&msg, format, va) < 0)
goto out;
- if (err) {
- char *msg2 = NULL;
- char errbuf[64];
-
- if (asprintf(&msg2, "%s: %s", msg,
- strerror_r(err, errbuf, sizeof(errbuf))) < 0)
- goto out2;
- free(msg);
- msg = msg2;
- }
(*libfsverity_error_cb)(msg);
-out2:
+
free(msg);
out:
errno = saved_errno;
@@ -93,16 +74,7 @@ void libfsverity_error_msg(const char *format, ...)
va_list va;
va_start(va, format);
- libfsverity_do_error_msg(format, va, 0);
- va_end(va);
-}
-
-void libfsverity_error_msg_errno(const char *format, ...)
-{
- va_list va;
-
- va_start(va, format);
- libfsverity_do_error_msg(format, va, errno);
+ libfsverity_do_error_msg(format, va);
va_end(va);
}
diff --git a/man/fsverity.1.md b/man/fsverity.1.md
new file mode 100644
index 0000000..dd54964
--- /dev/null
+++ b/man/fsverity.1.md
@@ -0,0 +1,215 @@
+% FSVERITY(1) fsverity-utils v1.5 | User Commands
+%
+% February 2022
+
+# NAME
+
+fsverity - userspace utility for fs-verity
+
+# SYNOPSIS
+**fsverity digest** [*OPTION*...] *FILE*... \
+**fsverity dump_metadata** [*OPTION*...] *TYPE* *FILE* \
+**fsverity enable** [*OPTION*...] *FILE* \
+**fsverity measure** *FILE*... \
+**fsverity sign** [*OPTION*...] *FILE* *OUT_SIGFILE*
+
+# DESCRIPTION
+
+**fsverity** is a userspace utility for fs-verity. fs-verity is a Linux kernel
+filesystem feature that does transparent on-demand verification of the contents
+of read-only files using Merkle trees.
+
+**fsverity** can enable fs-verity on files, retrieve the digests of fs-verity
+files, and sign files for use with fs-verity (among other things).
+**fsverity**'s functionality is divided among various subcommands.
+
+This manual page focuses on documenting all **fsverity** subcommands and
+options. For examples and more information about the fs-verity kernel feature,
+see the references at the end of this page.
+
+# OPTIONS
+
+**fsverity** always accepts the following options:
+
+**\-\-help**
+: Show the help, for either one subcommand or for all subcommands.
+
+**\-\-version**
+: Show the version of fsverity-utils.
+
+# SUBCOMMANDS
+
+## **fsverity digest** [*OPTION*...] *FILE*...
+
+Compute the fs-verity digest of the given file(s). This is mainly intended to
+used in preparation for signing the digest. In some cases **fsverity sign**
+can be used instead to digest and sign the file in one step.
+
+Options accepted by **fsverity digest**:
+
+**\-\-block-size**=*BLOCK_SIZE*
+: The Merkle tree block size (in bytes) to use. This must be a power of 2 and
+ at least twice the size of the hash values. However, note that currently
+ (as of Linux kernel v5.13), the Linux kernel implementations of fs-verity
+ only support the case where the Merkle tree block size is equal to the
+ system page size, usually 4096 bytes. The default value of this option is
+ 4096.
+
+**\-\-compact**
+: When printing the file digest, only print the actual digest hex string;
+ don't print the algorithm name and filename.
+
+**\-\-for-builtin-sig**
+: Format the file digest in a way that is compatible with the Linux kernel's
+ fs-verity built-in signature verification support. This means formatting it
+ as a `struct fsverity_formatted_digest`. Use this option if you are using
+ built-in signatures but are not using **fsverity sign** to do the signing.
+
+**\-\-hash-alg**=*HASH_ALG*
+: The hash algorithm to use to build the Merkle tree. Valid options are
+ sha256 and sha512. Default is sha256.
+
+**\-\-out-merkle-tree**=*FILE*
+: Write the computed Merkle tree to the given file. The Merkle tree layout
+ will be the same as that used by the Linux kernel's
+ `FS_IOC_READ_VERITY_METADATA` ioctl.
+
+ Normally this option isn't useful, but it can be needed in cases where the
+ fs-verity metadata needs to be consumed by something other than one of the
+ native Linux kernel implementations of fs-verity. This is not needed for
+ file signing.
+
+**\-\-out-descriptor**=*FILE*
+: Write the computed fs-verity descriptor to the given file.
+
+ Normally this option isn't useful, but it can be needed in cases where the
+ fs-verity metadata needs to be consumed by something other than one of the
+ native Linux kernel implementations of fs-verity. This is not needed for
+ file signing.
+
+**\-\-salt**=*SALT*
+: The salt to use in the Merkle tree, as a hex string. The salt is a value
+ that is prepended to every hashed block; it can be used to personalize the
+ hashing for a particular file or device. The default is no salt.
+
+## **fsverity dump_metadata** [*OPTION*...] *TYPE* *FILE*
+
+Dump the fs-verity metadata of the given file. The file must have fs-verity
+enabled, and the filesystem must support the `FS_IOC_READ_VERITY_METADATA` ioctl
+(it was added in Linux v5.12). This subcommand normally isn't useful, but it
+can be useful in cases where a userspace server program is serving a verity file
+to a client which implements fs-verity compatible verification.
+
+*TYPE* may be "merkle\_tree", "descriptor", or "signature", indicating the type
+of metadata to dump. "signature" refers to the built-in signature, if present;
+userspace-managed signatures will not be included.
+
+Options accepted by **fsverity dump_metadata**:
+
+**\-\-length**=*LENGTH*
+: Length in bytes to dump from the specified metadata item. Only accepted in
+ combination with **\-\-offset**.
+
+**\-\-offset**=*offset*
+: Offset in bytes into the specified metadata item at which to start dumping.
+ Only accepted in combination with **\-\-length**.
+
+## **fsverity enable** [*OPTION*...] *FILE*
+
+Enable fs-verity on the specified file. This will only work if the filesystem
+supports fs-verity.
+
+Options accepted by **fsverity enable**:
+
+**\-\-block-size**=*BLOCK_SIZE*
+: Same as for **fsverity digest**.
+
+**\-\-hash-alg**=*HASH_ALG*
+: Same as for **fsverity digest**.
+
+**\-\-salt**=*SALT*
+: Same as for **fsverity digest**.
+
+**\-\-signature**=*SIGFILE*
+: Specifies the built-in signature to apply to the file. *SIGFILE* must be a
+ file that contains the signature in PKCS#7 DER format, e.g. as produced by
+ the **fsverity sign** command.
+
+ Note that this option is only needed if the Linux kernel's fs-verity
+ built-in signature verification support is being used. It is not needed if
+ the signatures will be verified in userspace, as in that case the signatures
+ should be stored separately.
+
+## **fsverity measure** *FILE*...
+
+Display the fs-verity digest of the given file(s). The files must have
+fs-verity enabled. The output will be the same as **fsverity digest** with
+the appropriate parameters, but **fsverity measure** will take constant time
+for each file regardless of the size of the file.
+
+**fsverity measure** does not accept any options.
+
+## **fsverity sign** [*OPTION*...] *FILE* *OUT_SIGFILE*
+
+Sign the given file for fs-verity, in a way that is compatible with the Linux
+kernel's fs-verity built-in signature verification support. The signature will
+be written to *OUT_SIGFILE* in PKCS#7 DER format.
+
+The private key can be specified either by key file or by PKCS#11 token. To use
+a key file, provide **\-\-key** and optionally **\-\-cert**. To use a PKCS#11
+token, provide **\-\-pkcs11-engine**, **\-\-pkcs11-module**, **\-\-cert**, and
+optionally **\-\-pkcs11-keyid**. PKCS#11 token support is unavailable when
+fsverity-utils was built with BoringSSL rather than OpenSSL.
+
+**fsverity sign** should only be used if you need compatibility with fs-verity
+built-in signatures. It is not the only way to do signatures with fs-verity.
+For more information, see the fsverity-utils README.
+
+Options accepted by **fsverity sign**:
+
+**\-\-block-size**=*BLOCK_SIZE*
+: Same as for **fsverity digest**.
+
+**\-\-cert**=*CERTFILE*
+: Specifies the file that contains the certificate, in PEM format. This
+ option is required if *KEYFILE* contains only the private key and not also
+ the certificate, or if a PKCS#11 token is used.
+
+**\-\-hash-alg**=*HASH_ALG*
+: Same as for **fsverity digest**.
+
+**\-\-key**=*KEYFILE*
+: Specifies the file that contains the private key, in PEM format. This
+ option is required when not using a PKCS#11 token.
+
+**\-\-out-descriptor**=*FILE*
+: Same as for **fsverity digest**.
+
+**\-\-out-merkle-tree**=*FILE*
+: Same as for **fsverity digest**.
+
+**\-\-pkcs11-engine**=*SOFILE*
+: Specifies the path to the OpenSSL PKCS#11 engine file. This typically will
+ be a path to the libp11 .so file. This option is required when using a
+ PKCS#11 token.
+
+**\-\-pkcs11-keyid**=*KEYID*
+: Specifies the key identifier in the form of a PKCS#11 URI. If not provided,
+ the default key associated with the token is used. This option is only
+ applicable when using a PKCS#11 token.
+
+**\-\-pkcs11-module**=*SOFILE*
+: Specifies the path to the PKCS#11 token-specific module library. This
+ option is required when using a PKCS#11 token.
+
+**\-\-salt**=*SALT*
+: Same as for **fsverity digest**.
+
+# SEE ALSO
+
+For example commands and more information, see the
+[README file for
+fsverity-utils](https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git/tree/README.md).
+
+Also see the [kernel documentation for
+fs-verity](https://www.kernel.org/doc/html/latest/filesystems/fsverity.html).
diff --git a/programs/cmd_digest.c b/programs/cmd_digest.c
index 1a3c769..fd9f4de 100644
--- a/programs/cmd_digest.c
+++ b/programs/cmd_digest.c
@@ -18,6 +18,8 @@ static const struct option longopts[] = {
{"hash-alg", required_argument, NULL, OPT_HASH_ALG},
{"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
{"salt", required_argument, NULL, OPT_SALT},
+ {"out-merkle-tree", required_argument, NULL, OPT_OUT_MERKLE_TREE},
+ {"out-descriptor", required_argument, NULL, OPT_OUT_DESCRIPTOR},
{"compact", no_argument, NULL, OPT_COMPACT},
{"for-builtin-sig", no_argument, NULL, OPT_FOR_BUILTIN_SIG},
{NULL, 0, NULL, 0}
@@ -40,6 +42,8 @@ int fsverity_cmd_digest(const struct fsverity_command *cmd,
case OPT_HASH_ALG:
case OPT_BLOCK_SIZE:
case OPT_SALT:
+ case OPT_OUT_MERKLE_TREE:
+ case OPT_OUT_DESCRIPTOR:
if (!parse_tree_param(c, optarg, &tree_params))
goto out_usage;
break;
@@ -114,7 +118,8 @@ int fsverity_cmd_digest(const struct fsverity_command *cmd,
}
status = 0;
out:
- destroy_tree_params(&tree_params);
+ if (!destroy_tree_params(&tree_params) && status == 0)
+ status = 1;
return status;
out_err:
diff --git a/programs/cmd_dump_metadata.c b/programs/cmd_dump_metadata.c
new file mode 100644
index 0000000..94cc8ec
--- /dev/null
+++ b/programs/cmd_dump_metadata.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: MIT
+/*
+ * The 'fsverity dump_metadata' command
+ *
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+static const struct option longopts[] = {
+ {"offset", required_argument, NULL, OPT_OFFSET},
+ {"length", required_argument, NULL, OPT_LENGTH},
+ {NULL, 0, NULL, 0}
+};
+
+static const struct {
+ const char *name;
+ int val;
+} metadata_types[] = {
+ {"merkle_tree", FS_VERITY_METADATA_TYPE_MERKLE_TREE},
+ {"descriptor", FS_VERITY_METADATA_TYPE_DESCRIPTOR},
+ {"signature", FS_VERITY_METADATA_TYPE_SIGNATURE},
+};
+
+static bool parse_metadata_type(const char *name, __u64 *val_ret)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(metadata_types); i++) {
+ if (strcmp(name, metadata_types[i].name) == 0) {
+ *val_ret = metadata_types[i].val;
+ return true;
+ }
+ }
+ error_msg("unknown metadata type: %s", name);
+ fputs(" Expected", stderr);
+ for (i = 0; i < ARRAY_SIZE(metadata_types); i++) {
+ if (i != 0 && ARRAY_SIZE(metadata_types) > 2)
+ putc(',', stderr);
+ putc(' ', stderr);
+ if (i != 0 && i == ARRAY_SIZE(metadata_types) - 1)
+ fputs("or ", stderr);
+ fprintf(stderr, "\"%s\"", metadata_types[i].name);
+ }
+ fprintf(stderr, "\n");
+ return false;
+}
+
+/* Dump the fs-verity metadata of the given file. */
+int fsverity_cmd_dump_metadata(const struct fsverity_command *cmd,
+ int argc, char *argv[])
+{
+ bool offset_specified = false;
+ bool length_specified = false;
+ struct filedes file = { .fd = -1 };
+ struct filedes stdout_filedes = { .fd = STDOUT_FILENO,
+ .name = "stdout" };
+ struct fsverity_read_metadata_arg arg = { .length = 32768 };
+ void *buf = NULL;
+ char *tmp;
+ int c;
+ int status;
+ int bytes_read;
+
+ while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
+ switch (c) {
+ case OPT_OFFSET:
+ if (offset_specified) {
+ error_msg("--offset can only be specified once");
+ goto out_usage;
+ }
+ errno = 0;
+ arg.offset = strtoull(optarg, &tmp, 10);
+ if (errno || *tmp) {
+ error_msg("invalid value for --offset");
+ goto out_usage;
+ }
+ offset_specified = true;
+ break;
+ case OPT_LENGTH:
+ if (length_specified) {
+ error_msg("--length can only be specified once");
+ goto out_usage;
+ }
+ errno = 0;
+ arg.length = strtoull(optarg, &tmp, 10);
+ if (errno || *tmp || arg.length > SIZE_MAX) {
+ error_msg("invalid value for --length");
+ goto out_usage;
+ }
+ length_specified = true;
+ break;
+ default:
+ goto out_usage;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 2)
+ goto out_usage;
+
+ if (!parse_metadata_type(argv[0], &arg.metadata_type))
+ goto out_usage;
+
+ if (length_specified && !offset_specified) {
+ error_msg("--length specified without --offset");
+ goto out_usage;
+ }
+ if (offset_specified && !length_specified) {
+ error_msg("--offset specified without --length");
+ goto out_usage;
+ }
+
+ buf = xzalloc(arg.length);
+ arg.buf_ptr = (uintptr_t)buf;
+
+ if (!open_file(&file, argv[1], O_RDONLY, 0))
+ goto out_err;
+
+ /*
+ * If --offset and --length were specified, then do only the single read
+ * requested. Otherwise read until EOF.
+ */
+ do {
+ bytes_read = ioctl(file.fd, FS_IOC_READ_VERITY_METADATA, &arg);
+ if (bytes_read < 0) {
+ error_msg_errno("FS_IOC_READ_VERITY_METADATA failed on '%s'",
+ file.name);
+ goto out_err;
+ }
+ if (bytes_read == 0)
+ break;
+ if (!full_write(&stdout_filedes, buf, bytes_read))
+ goto out_err;
+ arg.offset += bytes_read;
+ } while (!length_specified);
+
+ status = 0;
+out:
+ free(buf);
+ filedes_close(&file);
+ return status;
+
+out_err:
+ status = 1;
+ goto out;
+
+out_usage:
+ usage(cmd, stderr);
+ status = 2;
+ goto out;
+}
diff --git a/programs/cmd_sign.c b/programs/cmd_sign.c
index 47ba6a2..aab8f00 100644
--- a/programs/cmd_sign.c
+++ b/programs/cmd_sign.c
@@ -27,11 +27,16 @@ static bool write_signature(const char *filename, const u8 *sig, u32 sig_size)
}
static const struct option longopts[] = {
- {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
- {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
- {"salt", required_argument, NULL, OPT_SALT},
- {"key", required_argument, NULL, OPT_KEY},
- {"cert", required_argument, NULL, OPT_CERT},
+ {"key", required_argument, NULL, OPT_KEY},
+ {"cert", required_argument, NULL, OPT_CERT},
+ {"pkcs11-engine", required_argument, NULL, OPT_PKCS11_ENGINE},
+ {"pkcs11-module", required_argument, NULL, OPT_PKCS11_MODULE},
+ {"pkcs11-keyid", required_argument, NULL, OPT_PKCS11_KEYID},
+ {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
+ {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
+ {"salt", required_argument, NULL, OPT_SALT},
+ {"out-merkle-tree", required_argument, NULL, OPT_OUT_MERKLE_TREE},
+ {"out-descriptor", required_argument, NULL, OPT_OUT_DESCRIPTOR},
{NULL, 0, NULL, 0}
};
@@ -51,12 +56,6 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
switch (c) {
- case OPT_HASH_ALG:
- case OPT_BLOCK_SIZE:
- case OPT_SALT:
- if (!parse_tree_param(c, optarg, &tree_params))
- goto out_usage;
- break;
case OPT_KEY:
if (sig_params.keyfile != NULL) {
error_msg("--key can only be specified once");
@@ -71,6 +70,35 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
}
sig_params.certfile = optarg;
break;
+ case OPT_PKCS11_ENGINE:
+ if (sig_params.pkcs11_engine != NULL) {
+ error_msg("--pkcs11-engine can only be specified once");
+ goto out_usage;
+ }
+ sig_params.pkcs11_engine = optarg;
+ break;
+ case OPT_PKCS11_MODULE:
+ if (sig_params.pkcs11_module != NULL) {
+ error_msg("--pkcs11-module can only be specified once");
+ goto out_usage;
+ }
+ sig_params.pkcs11_module = optarg;
+ break;
+ case OPT_PKCS11_KEYID:
+ if (sig_params.pkcs11_keyid != NULL) {
+ error_msg("--pkcs11-keyid can only be specified once");
+ goto out_usage;
+ }
+ sig_params.pkcs11_keyid = optarg;
+ break;
+ case OPT_HASH_ALG:
+ case OPT_BLOCK_SIZE:
+ case OPT_SALT:
+ case OPT_OUT_MERKLE_TREE:
+ case OPT_OUT_DESCRIPTOR:
+ if (!parse_tree_param(c, optarg, &tree_params))
+ goto out_usage;
+ break;
default:
goto out_usage;
}
@@ -82,10 +110,6 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
if (argc != 2)
goto out_usage;
- if (sig_params.keyfile == NULL) {
- error_msg("Missing --key argument");
- goto out_usage;
- }
if (sig_params.certfile == NULL)
sig_params.certfile = sig_params.keyfile;
@@ -117,7 +141,8 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
status = 0;
out:
filedes_close(&file);
- destroy_tree_params(&tree_params);
+ if (!destroy_tree_params(&tree_params) && status == 0)
+ status = 1;
free(digest);
free(sig);
return status;
diff --git a/programs/fsverity.c b/programs/fsverity.c
index b911b2e..e4e348b 100644
--- a/programs/fsverity.c
+++ b/programs/fsverity.c
@@ -11,6 +11,7 @@
#include "fsverity.h"
+#include <fcntl.h>
#include <limits.h>
static const struct fsverity_command {
@@ -27,9 +28,16 @@ static const struct fsverity_command {
.usage_str =
" fsverity digest FILE...\n"
" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
+" [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
" [--compact] [--for-builtin-sig]\n"
#ifndef _WIN32
}, {
+ .name = "dump_metadata",
+ .func = fsverity_cmd_dump_metadata,
+ .short_desc = "Dump the fs-verity metadata of the given file",
+ .usage_str =
+" fsverity dump_metadata TYPE FILE [--offset=OFFSET] [--length=LENGTH]\n"
+ }, {
.name = "enable",
.func = fsverity_cmd_enable,
.short_desc = "Enable fs-verity on a file",
@@ -48,11 +56,13 @@ static const struct fsverity_command {
}, {
.name = "sign",
.func = fsverity_cmd_sign,
- .short_desc = "Sign a file for fs-verity",
+ .short_desc = "Sign a file for fs-verity built-in signature verification",
.usage_str =
-" fsverity sign FILE OUT_SIGFILE --key=KEYFILE\n"
+" fsverity sign FILE OUT_SIGFILE\n"
+" [--key=KEYFILE] [--cert=CERTFILE] [--pkcs11-engine=SOFILE]\n"
+" [--pkcs11-module=SOFILE] [--pkcs11-keyid=KEYID]\n"
" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
-" [--cert=CERTFILE]\n"
+" [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
}
};
@@ -194,6 +204,74 @@ static bool parse_salt_option(const char *arg, u8 **salt_ptr,
return true;
}
+struct metadata_callback_ctx {
+ struct filedes merkle_tree_file;
+ struct filedes descriptor_file;
+ struct libfsverity_metadata_callbacks callbacks;
+};
+
+static int handle_merkle_tree_size(void *_ctx, u64 size)
+{
+ struct metadata_callback_ctx *ctx = _ctx;
+
+ if (!preallocate_file(&ctx->merkle_tree_file, size))
+ return -EIO;
+ return 0;
+}
+
+static int handle_merkle_tree_block(void *_ctx, const void *block, size_t size,
+ u64 offset)
+{
+ struct metadata_callback_ctx *ctx = _ctx;
+
+ if (!full_pwrite(&ctx->merkle_tree_file, block, size, offset))
+ return -EIO;
+ return 0;
+}
+
+static int handle_descriptor(void *_ctx, const void *descriptor, size_t size)
+{
+ struct metadata_callback_ctx *ctx = _ctx;
+
+ if (!full_write(&ctx->descriptor_file, descriptor, size))
+ return -EIO;
+ return 0;
+}
+
+static bool parse_out_metadata_option(int opt_char, const char *arg,
+ const struct libfsverity_metadata_callbacks **cbs)
+{
+ struct metadata_callback_ctx *ctx;
+ struct filedes *file;
+ const char *opt_name;
+
+ if (*cbs) {
+ ctx = (*cbs)->ctx;
+ } else {
+ ctx = xzalloc(sizeof(*ctx));
+ ctx->merkle_tree_file.fd = -1;
+ ctx->descriptor_file.fd = -1;
+ ctx->callbacks.ctx = ctx;
+ *cbs = &ctx->callbacks;
+ }
+
+ if (opt_char == OPT_OUT_MERKLE_TREE) {
+ file = &ctx->merkle_tree_file;
+ opt_name = "--out-merkle-tree";
+ ctx->callbacks.merkle_tree_size = handle_merkle_tree_size;
+ ctx->callbacks.merkle_tree_block = handle_merkle_tree_block;
+ } else {
+ file = &ctx->descriptor_file;
+ opt_name = "--out-descriptor";
+ ctx->callbacks.descriptor = handle_descriptor;
+ }
+ if (file->fd >= 0) {
+ error_msg("%s can only be specified once", opt_name);
+ return false;
+ }
+ return open_file(file, arg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+}
+
bool parse_tree_param(int opt_char, const char *arg,
struct libfsverity_merkle_tree_params *params)
{
@@ -205,15 +283,30 @@ bool parse_tree_param(int opt_char, const char *arg,
case OPT_SALT:
return parse_salt_option(arg, (u8 **)&params->salt,
&params->salt_size);
+ case OPT_OUT_MERKLE_TREE:
+ case OPT_OUT_DESCRIPTOR:
+ return parse_out_metadata_option(opt_char, arg,
+ &params->metadata_callbacks);
default:
ASSERT(0);
}
}
-void destroy_tree_params(struct libfsverity_merkle_tree_params *params)
+bool destroy_tree_params(struct libfsverity_merkle_tree_params *params)
{
+ bool ok = true;
+
free((u8 *)params->salt);
+ if (params->metadata_callbacks) {
+ struct metadata_callback_ctx *ctx =
+ params->metadata_callbacks->ctx;
+
+ ok &= filedes_close(&ctx->merkle_tree_file);
+ ok &= filedes_close(&ctx->descriptor_file);
+ free(ctx);
+ }
memset(params, 0, sizeof(*params));
+ return ok;
}
int main(int argc, char *argv[])
diff --git a/programs/fsverity.h b/programs/fsverity.h
index 45c4fe1..ad54cc2 100644
--- a/programs/fsverity.h
+++ b/programs/fsverity.h
@@ -27,6 +27,13 @@ enum {
OPT_FOR_BUILTIN_SIG,
OPT_HASH_ALG,
OPT_KEY,
+ OPT_LENGTH,
+ OPT_OFFSET,
+ OPT_OUT_DESCRIPTOR,
+ OPT_OUT_MERKLE_TREE,
+ OPT_PKCS11_ENGINE,
+ OPT_PKCS11_KEYID,
+ OPT_PKCS11_MODULE,
OPT_SALT,
OPT_SIGNATURE,
};
@@ -37,6 +44,10 @@ struct fsverity_command;
int fsverity_cmd_digest(const struct fsverity_command *cmd,
int argc, char *argv[]);
+/* cmd_dump_metadata.c */
+int fsverity_cmd_dump_metadata(const struct fsverity_command *cmd,
+ int argc, char *argv[]);
+
/* cmd_enable.c */
int fsverity_cmd_enable(const struct fsverity_command *cmd,
int argc, char *argv[]);
@@ -53,6 +64,6 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
void usage(const struct fsverity_command *cmd, FILE *fp);
bool parse_tree_param(int opt_char, const char *arg,
struct libfsverity_merkle_tree_params *params);
-void destroy_tree_params(struct libfsverity_merkle_tree_params *params);
+bool destroy_tree_params(struct libfsverity_merkle_tree_params *params);
#endif /* PROGRAMS_FSVERITY_H */
diff --git a/programs/test_compute_digest.c b/programs/test_compute_digest.c
index e7f2645..67266fa 100644
--- a/programs/test_compute_digest.c
+++ b/programs/test_compute_digest.c
@@ -13,6 +13,7 @@
#include <ctype.h>
#include <inttypes.h>
+#include <openssl/sha.h>
struct mem_file {
u8 *data;
@@ -37,6 +38,13 @@ static int error_read_fn(void *fd __attribute__((unused)),
return -EIO;
}
+static int zeroes_read_fn(void *fd __attribute__((unused)),
+ void *buf, size_t count)
+{
+ memset(buf, 0, count);
+ return 0;
+}
+
static const struct test_case {
u32 hash_algorithm;
u32 block_size;
@@ -249,6 +257,130 @@ static void test_invalid_params(void)
ASSERT(d == NULL);
}
+static struct {
+ u64 merkle_tree_size;
+ u64 merkle_tree_block;
+ u64 descriptor;
+} metadata_callback_counts;
+
+static int handle_merkle_tree_size(void *ctx, u64 size)
+{
+ metadata_callback_counts.merkle_tree_size++;
+
+ /* Test that the ctx argument is passed through correctly. */
+ ASSERT(ctx == (void *)1);
+
+ /* Test that the expected Merkle tree size is reported. */
+ ASSERT(size == 5 * 1024);
+ return 0;
+}
+
+static int handle_merkle_tree_block(void *ctx, const void *block, size_t size,
+ u64 offset)
+{
+ u8 digest[SHA256_DIGEST_LENGTH];
+ u64 count = metadata_callback_counts.merkle_tree_block++;
+ const char *expected_digest;
+
+ /* Test that ->merkle_tree_size() was called first. */
+ ASSERT(metadata_callback_counts.merkle_tree_size == 1);
+
+ /* Test that the ctx argument is passed through correctly. */
+ ASSERT(ctx == (void *)1);
+
+ /*
+ * Test that this Merkle tree block has the expected size, offset, and
+ * contents. The 4 blocks at "level 0" should be reported first, in
+ * order; then the 1 block at "level 1" should be reported last (but the
+ * level 1 block should have the smallest offset).
+ */
+ ASSERT(size == 1024);
+ SHA256(block, size, digest);
+ if (count == 4) {
+ /* 1 block at level 1 */
+ ASSERT(offset == 0);
+ expected_digest = "\x68\xc5\x38\xe1\x19\x58\xd6\x5d"
+ "\x68\xb6\xfe\x8e\x9f\xb8\xcc\xab"
+ "\xec\xfd\x92\x8b\x01\xd0\x63\x44"
+ "\xe2\x23\xed\x41\xdd\xc4\x54\x4a";
+ } else {
+ /* 4 blocks at level 0 */
+ ASSERT(offset == 1024 + (count * 1024));
+ if (count < 3) {
+ expected_digest = "\xf7\x89\xba\xab\x53\x85\x9f\xaf"
+ "\x36\xd6\xd7\x5d\x10\x42\x06\x42"
+ "\x94\x20\x2d\x6e\x13\xe7\x71\x6f"
+ "\x39\x4f\xba\x43\x4c\xcc\x49\x86";
+ } else {
+ expected_digest = "\x00\xfe\xd0\x3c\x5d\x6e\xab\x21"
+ "\x31\x43\xf3\xd9\x6a\x5c\xa3\x1c"
+ "\x2b\x89\xf5\x68\x4e\x6c\x8e\x07"
+ "\x87\x3e\x5e\x97\x65\x17\xb4\x8f";
+ }
+ }
+ ASSERT(!memcmp(digest, expected_digest, SHA256_DIGEST_LENGTH));
+ return 0;
+}
+
+static const u8 expected_file_digest[SHA256_DIGEST_LENGTH] =
+ "\x09\xcb\xba\xee\xd2\xa0\x4c\x2d\xa2\x42\xc1\x0e\x15\x68\xd9\x6f"
+ "\x35\x8a\x16\xaa\x1e\xbe\x8c\xf0\x28\x61\x20\xc1\x3c\x93\x66\xd1";
+
+static int handle_descriptor(void *ctx, const void *descriptor, size_t size)
+{
+ u8 digest[SHA256_DIGEST_LENGTH];
+
+ metadata_callback_counts.descriptor++;
+ /* Test that the ctx argument is passed through correctly. */
+ ASSERT(ctx == (void *)1);
+
+ /* Test that the fs-verity descriptor is reported correctly. */
+ ASSERT(size == 256);
+ SHA256(descriptor, size, digest);
+ ASSERT(!memcmp(digest, expected_file_digest, SHA256_DIGEST_LENGTH));
+ return 0;
+}
+
+static const struct libfsverity_metadata_callbacks metadata_callbacks = {
+ .ctx = (void *)1, /* arbitrary value for testing purposes */
+ .merkle_tree_size = handle_merkle_tree_size,
+ .merkle_tree_block = handle_merkle_tree_block,
+ .descriptor = handle_descriptor,
+};
+
+/* Test that the libfsverity_metadata_callbacks work correctly. */
+static void test_metadata_callbacks(void)
+{
+ /*
+ * For a useful test, we want a file whose Merkle tree will have at
+ * least 2 levels (this one will have exactly 2). The contents of the
+ * file aren't too important.
+ */
+ struct libfsverity_merkle_tree_params params = {
+ .version = 1,
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .block_size = 1024,
+ .file_size = 100000,
+ .metadata_callbacks = &metadata_callbacks,
+ };
+ struct libfsverity_digest *d;
+
+ ASSERT(libfsverity_compute_digest(NULL, zeroes_read_fn,
+ &params, &d) == 0);
+
+ /* Test that the callbacks were called the correct number of times. */
+ ASSERT(metadata_callback_counts.merkle_tree_size == 1);
+ ASSERT(metadata_callback_counts.merkle_tree_block == 5);
+ ASSERT(metadata_callback_counts.descriptor == 1);
+
+ /* Test that the computed file digest is as expected. */
+ ASSERT(d->digest_algorithm == FS_VERITY_HASH_ALG_SHA256);
+ ASSERT(d->digest_size == SHA256_DIGEST_LENGTH);
+ ASSERT(!memcmp(d->digest, expected_file_digest, SHA256_DIGEST_LENGTH));
+
+ free(d);
+}
+
int main(int argc, char *argv[])
{
const bool update = (argc == 2 && !strcmp(argv[1], "--update"));
@@ -305,6 +437,7 @@ int main(int argc, char *argv[])
}
test_invalid_params();
+ test_metadata_callbacks();
printf("test_compute_digest passed\n");
return 0;
}
diff --git a/programs/utils.c b/programs/utils.c
index ce19b57..116eb95 100644
--- a/programs/utils.c
+++ b/programs/utils.c
@@ -13,10 +13,14 @@
#include <errno.h>
#include <fcntl.h>
+#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <unistd.h>
+#ifdef _WIN32
+# include <windows.h>
+#endif
/* ========== Memory allocation ========== */
@@ -126,6 +130,26 @@ bool get_file_size(struct filedes *file, u64 *size_ret)
return true;
}
+bool preallocate_file(struct filedes *file, u64 size)
+{
+ int res;
+
+ if (size == 0)
+ return true;
+#ifdef _WIN32
+ /* Not exactly the same as posix_fallocate(), but good enough... */
+ res = _chsize_s(file->fd, size);
+#else
+ res = posix_fallocate(file->fd, 0, size);
+#endif
+ if (res != 0) {
+ error_msg_errno("preallocating %" PRIu64 "-byte file '%s'",
+ size, file->name);
+ return false;
+ }
+ return true;
+}
+
bool full_read(struct filedes *file, void *buf, size_t count)
{
while (count) {
@@ -160,6 +184,41 @@ bool full_write(struct filedes *file, const void *buf, size_t count)
return true;
}
+static int raw_pwrite(int fd, const void *buf, int count, u64 offset)
+{
+#ifdef _WIN32
+ HANDLE h = (HANDLE)_get_osfhandle(fd);
+ OVERLAPPED pos = { .Offset = offset, .OffsetHigh = offset >> 32 };
+ DWORD written = 0;
+
+ /* Not exactly the same as pwrite(), but good enough... */
+ if (!WriteFile(h, buf, count, &written, &pos)) {
+ errno = EIO;
+ return -1;
+ }
+ return written;
+#else
+ return pwrite(fd, buf, count, offset);
+#endif
+}
+
+bool full_pwrite(struct filedes *file, const void *buf, size_t count,
+ u64 offset)
+{
+ while (count) {
+ int n = raw_pwrite(file->fd, buf, min(count, INT_MAX), offset);
+
+ if (n < 0) {
+ error_msg_errno("writing to '%s'", file->name);
+ return false;
+ }
+ buf += n;
+ count -= n;
+ offset += n;
+ }
+ return true;
+}
+
bool filedes_close(struct filedes *file)
{
int res;
diff --git a/programs/utils.h b/programs/utils.h
index ab5005f..9a5c97a 100644
--- a/programs/utils.h
+++ b/programs/utils.h
@@ -40,8 +40,11 @@ struct filedes {
bool open_file(struct filedes *file, const char *filename, int flags, int mode);
bool get_file_size(struct filedes *file, u64 *size_ret);
+bool preallocate_file(struct filedes *file, u64 size);
bool full_read(struct filedes *file, void *buf, size_t count);
bool full_write(struct filedes *file, const void *buf, size_t count);
+bool full_pwrite(struct filedes *file, const void *buf, size_t count,
+ u64 offset);
bool filedes_close(struct filedes *file);
int read_callback(void *file, void *buf, size_t count);
diff --git a/scripts/do-release.sh b/scripts/do-release.sh
index 352fcf1..3f68497 100755
--- a/scripts/do-release.sh
+++ b/scripts/do-release.sh
@@ -9,38 +9,73 @@
set -e -u -o pipefail
cd "$(dirname "$0")/.."
-if [ $# != 1 ]; then
- echo "Usage: $0 VERS" 1>&2
- echo " e.g. $0 1.0" 1>&2
+usage()
+{
+ echo "Usage: $0 prepare|publish VERS" 1>&2
+ echo " e.g. $0 prepare 1.0" 1>&2
+ echo " $0 publish 1.0" 1>&2
exit 2
+}
+
+if [ $# != 2 ]; then
+ usage
fi
-VERS=$1
+PUBLISH=false
+case $1 in
+publish)
+ PUBLISH=true
+ ;;
+prepare)
+ ;;
+*)
+ usage
+ ;;
+esac
+VERS=$2
PKG=fsverity-utils-$VERS
-git checkout -f
-git clean -fdx
-./scripts/run-tests.sh
-git clean -fdx
-
-major=$(echo "$VERS" | cut -d. -f1)
-minor=$(echo "$VERS" | cut -d. -f2)
-sed -E -i -e "/FSVERITY_UTILS_MAJOR_VERSION/s/[0-9]+/$major/" \
- -e "/FSVERITY_UTILS_MINOR_VERSION/s/[0-9]+/$minor/" \
- include/libfsverity.h
-sed -E -i "/Version:/s/[0-9]+\.[0-9]+/$VERS/" \
- lib/libfsverity.pc.in
-git commit -a --signoff --message="v$VERS"
-git tag --sign "v$VERS" --message="$PKG"
-
-git archive "v$VERS" --prefix="$PKG/" > "$PKG.tar"
-tar xf "$PKG.tar"
-( cd "$PKG" && make check )
-rm -r "$PKG"
-
-gpg --detach-sign --armor "$PKG.tar"
-DESTDIR=/pub/linux/kernel/people/ebiggers/fsverity-utils/v$VERS
-kup mkdir "$DESTDIR"
-kup put "$PKG.tar" "$PKG.tar.asc" "$DESTDIR/$PKG.tar.gz"
-git push
-git push --tags
+prepare_release()
+{
+ git checkout -f
+ git clean -fdx
+ ./scripts/run-tests.sh
+ git clean -fdx
+
+ major=$(echo "$VERS" | cut -d. -f1)
+ minor=$(echo "$VERS" | cut -d. -f2)
+ month=$(LC_ALL=C date +%B)
+ year=$(LC_ALL=C date +%Y)
+
+ sed -E -i -e "/FSVERITY_UTILS_MAJOR_VERSION/s/[0-9]+/$major/" \
+ -e "/FSVERITY_UTILS_MINOR_VERSION/s/[0-9]+/$minor/" \
+ include/libfsverity.h
+ sed -E -i "/Version:/s/[0-9]+\.[0-9]+/$VERS/" \
+ lib/libfsverity.pc.in
+ sed -E -i -e "/^% /s/fsverity-utils v[0-9]+(\.[0-9]+)+/fsverity-utils v$VERS/" \
+ -e "/^% /s/[a-zA-Z]+ 2[0-9]{3}/$month $year/" \
+ man/*.[1-9].md
+ git commit -a --signoff --message="v$VERS"
+ git tag --sign "v$VERS" --message="$PKG"
+
+ git archive "v$VERS" --prefix="$PKG/" > "$PKG.tar"
+ tar xf "$PKG.tar"
+ ( cd "$PKG" && make check )
+ rm -r "$PKG"
+}
+
+publish_release()
+{
+ gpg --detach-sign --armor "$PKG.tar"
+ DESTDIR=/pub/linux/kernel/people/ebiggers/fsverity-utils/v$VERS
+ kup mkdir "$DESTDIR"
+ kup put "$PKG.tar" "$PKG.tar.asc" "$DESTDIR/$PKG.tar.gz"
+ git push
+ git push --tags
+}
+
+if $PUBLISH; then
+ publish_release
+else
+ prepare_release
+fi
diff --git a/scripts/run-sparse.sh b/scripts/run-sparse.sh
index f75b837..b8d37c1 100755
--- a/scripts/run-sparse.sh
+++ b/scripts/run-sparse.sh
@@ -8,7 +8,7 @@
set -e -u -o pipefail
-find . -name '*.c' | while read -r file; do
+find programs lib -name '*.c' | while read -r file; do
sparse "$file" -gcc-base-dir "$(gcc --print-file-name=)" \
-Iinclude -D_FILE_OFFSET_BITS=64 -Wbitwise -D_GNU_SOURCE
done
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 530fe34..e2a4e38 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -17,11 +17,13 @@
set -e -u -o pipefail
cd "$(dirname "$0")/.."
-log() {
+log()
+{
echo "[$(date)] $*" 1>&2
}
-fail() {
+fail()
+{
echo "FAIL: $*" 1>&2
exit 1
}
@@ -38,31 +40,44 @@ exec 2> >(tee -ia run-tests.log 1>&2)
MAKE="make -j$(getconf _NPROCESSORS_ONLN)"
-log "Build and test with statically linking"
-$MAKE CFLAGS="-Werror"
-if ldd fsverity | grep libfsverity.so; then
- fail "fsverity binary should be statically linked to libfsverity by default"
-fi
-./fsverity --version
-
-log "Check that all global symbols are prefixed with \"libfsverity_\""
-if nm libfsverity.a | grep ' T ' | grep -v " libfsverity_"; then
- fail "Some global symbols are not prefixed with \"libfsverity_\""
-fi
+TEST_FUNCS=()
-log "Build and test with dynamic linking"
-$MAKE CFLAGS="-Werror" USE_SHARED_LIB=1 check
-if ! ldd fsverity | grep libfsverity.so; then
- fail "fsverity binary should be dynamically linked to libfsverity when USE_SHARED_LIB=1"
-fi
+static_linking_test()
+{
+ log "Build and test with statically linking"
+ $MAKE CFLAGS="-Werror"
+ if ldd fsverity | grep libfsverity.so; then
+ fail "fsverity binary should be statically linked to libfsverity by default"
+ fi
+ ./fsverity --version
+
+ log "Check that all global symbols are prefixed with \"libfsverity_\""
+ if nm libfsverity.a | grep ' T ' | grep -v " libfsverity_"; then
+ fail "Some global symbols are not prefixed with \"libfsverity_\""
+ fi
+}
+TEST_FUNCS+=(static_linking_test)
-log "Check that all exported symbols are prefixed with \"libfsverity_\""
-if nm libfsverity.so | grep ' T ' | grep -v " libfsverity_"; then
- fail "Some exported symbols are not prefixed with \"libfsverity_\""
-fi
+dynamic_linking_test()
+{
+ log "Build and test with dynamic linking"
+ $MAKE CFLAGS="-Werror" USE_SHARED_LIB=1 check
+ if ! ldd fsverity | grep libfsverity.so; then
+ fail "fsverity binary should be dynamically linked to libfsverity when USE_SHARED_LIB=1"
+ fi
+
+ log "Check that all exported symbols are prefixed with \"libfsverity_\""
+ if nm libfsverity.so | grep ' T ' | grep -v " libfsverity_"; then
+ fail "Some exported symbols are not prefixed with \"libfsverity_\""
+ fi
+}
+TEST_FUNCS+=(dynamic_linking_test)
-log "Test using libfsverity from C++ program"
-cat > "$TMPDIR/test.cc" <<EOF
+cplusplus_test()
+{
+ $MAKE CFLAGS="-Werror" libfsverity.so
+ log "Test using libfsverity from C++ program"
+ cat > "$TMPDIR/test.cc" <<EOF
#include <libfsverity.h>
#include <iostream>
int main()
@@ -70,118 +85,231 @@ int main()
std::cout << libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256) << std::endl;
}
EOF
-c++ -Wall -Werror "$TMPDIR/test.cc" -Iinclude -L. -lfsverity -o "$TMPDIR/test"
-[ "$(LD_LIBRARY_PATH=. "$TMPDIR/test")" = "32" ]
-rm "${TMPDIR:?}"/*
-
-log "Check that build doesn't produce untracked files"
-$MAKE CFLAGS="-Werror" all test_programs
-if git status --short | grep -q '^??'; then
- git status
- fail "Build produced untracked files (check 'git status'). Missing gitignore entry?"
-fi
+ c++ -Wall -Werror "$TMPDIR/test.cc" -Iinclude -L. -lfsverity -o "$TMPDIR/test"
+ [ "$(LD_LIBRARY_PATH=. "$TMPDIR/test")" = "32" ]
+ rm "${TMPDIR:?}"/*
+}
+TEST_FUNCS+=(cplusplus_test)
-log "Test that 'make uninstall' uninstalls all files"
-make DESTDIR="$TMPDIR" install
-if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" = 0 ]; then
- fail "'make install' didn't install any files"
-fi
-make DESTDIR="$TMPDIR" uninstall
-if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" != 0 ]; then
- fail "'make uninstall' didn't uninstall all files"
-fi
-rm -r "${TMPDIR:?}"/*
-
-log "Build, install, and uninstall with dash"
-make clean SHELL=/bin/dash
-make DESTDIR="$TMPDIR" SHELL=/bin/dash install
-make DESTDIR="$TMPDIR" SHELL=/bin/dash uninstall
-
-log "Check that all files have license and copyright info"
-list="$TMPDIR/filelist"
-filter_license_info() {
- # files to exclude from license and copyright info checks
- grep -E -v '(\.gitignore|LICENSE|NEWS|README|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)'
-}
-git grep -L 'SPDX-License-Identifier: MIT' \
- | filter_license_info > "$list" || true
-if [ -s "$list" ]; then
- fail "The following files are missing an appropriate SPDX license identifier: $(<"$list")"
-fi
-# For now some people still prefer a free-form license statement, not just SPDX.
-git grep -L 'Use of this source code is governed by an MIT-style' \
- | filter_license_info > "$list" || true
-if [ -s "$list" ]; then
- fail "The following files are missing an appropriate license statement: $(<"$list")"
-fi
-git grep -L '\<Copyright\>' | filter_license_info > "$list" || true
-if [ -s "$list" ]; then
- fail "The following files are missing a copyright statement: $(<"$list")"
-fi
-rm "$list"
+untracked_files_test()
+{
+ log "Check that build doesn't produce untracked files"
+ $MAKE CFLAGS="-Werror" all test_programs
+ if git status --short | grep -q '^??'; then
+ git status
+ fail "Build produced untracked files (check 'git status'). Missing gitignore entry?"
+ fi
+}
+TEST_FUNCS+=(untracked_files_test)
+
+uninstall_test()
+{
+ log "Test that 'make uninstall' uninstalls all files"
+ make DESTDIR="$TMPDIR" install
+ if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" = 0 ]; then
+ fail "'make install' didn't install any files"
+ fi
+ make DESTDIR="$TMPDIR" uninstall
+ if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" != 0 ]; then
+ fail "'make uninstall' didn't uninstall all files"
+ fi
+ rm -r "${TMPDIR:?}"/*
+}
+TEST_FUNCS+=(uninstall_test)
+
+dash_test()
+{
+ log "Build, install, and uninstall with dash"
+ make clean SHELL=/bin/dash
+ make DESTDIR="$TMPDIR" SHELL=/bin/dash install
+ make DESTDIR="$TMPDIR" SHELL=/bin/dash uninstall
+}
+TEST_FUNCS+=(dash_test)
+
+license_test()
+{
+ log "Check that all files have license and copyright info"
+ list="$TMPDIR/filelist"
+ filter_license_info() {
+ # files to exclude from license and copyright info checks
+ grep -E -v '(\.gitignore|LICENSE|.*\.md|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)'
+ }
+ git grep -L 'SPDX-License-Identifier: MIT' \
+ | filter_license_info > "$list" || true
+ if [ -s "$list" ]; then
+ fail "The following files are missing an appropriate SPDX license identifier: $(<"$list")"
+ fi
+ # For now some people still prefer a free-form license statement, not just SPDX.
+ git grep -L 'Use of this source code is governed by an MIT-style' \
+ | filter_license_info > "$list" || true
+ if [ -s "$list" ]; then
+ fail "The following files are missing an appropriate license statement: $(<"$list")"
+ fi
+ git grep -L '\<Copyright\>' | filter_license_info > "$list" || true
+ if [ -s "$list" ]; then
+ fail "The following files are missing a copyright statement: $(<"$list")"
+ fi
+ rm "$list"
+}
+TEST_FUNCS+=(license_test)
+
+gcc_test()
+{
+ log "Build and test with gcc (-O2)"
+ $MAKE CC=gcc CFLAGS="-O2 -Werror" check
-log "Build and test with gcc (-O2)"
-$MAKE CC=gcc CFLAGS="-O2 -Werror" check
+ log "Build and test with gcc (-O3)"
+ $MAKE CC=gcc CFLAGS="-O3 -Werror" check
+}
+TEST_FUNCS+=(gcc_test)
-log "Build and test with gcc (-O3)"
-$MAKE CC=gcc CFLAGS="-O3 -Werror" check
+clang_test()
+{
+ log "Build and test with clang (-O2)"
+ $MAKE CC=clang CFLAGS="-O2 -Werror" check
-log "Build and test with gcc (32-bit)"
-$MAKE CC=gcc CFLAGS="-O2 -Werror -m32" check
+ log "Build and test with clang (-O3)"
+ $MAKE CC=clang CFLAGS="-O3 -Werror" check
+}
+TEST_FUNCS+=(clang_test)
-log "Build and test with clang (-O2)"
-$MAKE CC=clang CFLAGS="-O2 -Werror" check
+32bit_test()
+{
+ log "Build and test with gcc (32-bit)"
+ $MAKE CC=gcc CFLAGS="-O2 -Werror -m32" check
+}
+TEST_FUNCS+=(32bit_test)
-log "Build and test with clang (-O3)"
-$MAKE CC=clang CFLAGS="-O3 -Werror" check
+sanitizers_test()
+{
+ log "Build and test with clang + UBSAN"
+ $MAKE CC=clang \
+ CFLAGS="-O2 -Werror -fsanitize=undefined -fno-sanitize-recover=undefined" \
+ check
+
+ log "Build and test with clang + ASAN"
+ $MAKE CC=clang \
+ CFLAGS="-O2 -Werror -fsanitize=address -fno-sanitize-recover=address" \
+ check
+
+ log "Build and test with clang + unsigned integer overflow sanitizer"
+ $MAKE CC=clang \
+ CFLAGS="-O2 -Werror -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" \
+ check
+
+ log "Build and test with clang + CFI"
+ $MAKE CC=clang CFLAGS="-O2 -Werror -fsanitize=cfi -flto -fvisibility=hidden" \
+ AR=llvm-ar check
+}
+TEST_FUNCS+=(sanitizers_test)
-log "Build and test with clang + UBSAN"
-$MAKE CC=clang \
- CFLAGS="-O2 -Werror -fsanitize=undefined -fno-sanitize-recover=undefined" \
- check
+valgrind_test()
+{
+ log "Build and test with valgrind"
+ $MAKE TEST_WRAPPER_PROG="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all" \
+ CFLAGS="-O2 -Werror" check
+}
+TEST_FUNCS+=(valgrind_test)
-log "Build and test with clang + ASAN"
-$MAKE CC=clang \
- CFLAGS="-O2 -Werror -fsanitize=address -fno-sanitize-recover=address" \
- check
+boringssl_test()
+{
+ log "Build and test using BoringSSL instead of OpenSSL"
+ log "-> Building BoringSSL"
+ $MAKE boringssl
+ log "-> Building fsverity-utils linked to BoringSSL"
+ $MAKE CFLAGS="-O2 -Werror" LDFLAGS="-Lboringssl/build/crypto" \
+ CPPFLAGS="-Iboringssl/include" LDLIBS="-lcrypto -lpthread" check
+}
+TEST_FUNCS+=(boringssl_test)
-log "Build and test with clang + unsigned integer overflow sanitizer"
-$MAKE CC=clang \
- CFLAGS="-O2 -Werror -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" \
- check
+openssl1_test()
+{
+ log "Build and test using OpenSSL 1.0"
+ $MAKE CFLAGS="-O2 -Werror" LDFLAGS="-L/usr/lib/openssl-1.0" \
+ CPPFLAGS="-I/usr/include/openssl-1.0" check
+}
+TEST_FUNCS+=(openssl1_test)
-log "Build and test with clang + CFI"
-$MAKE CC=clang CFLAGS="-O2 -Werror -fsanitize=cfi -flto -fvisibility=hidden" \
- check
+openssl3_test()
+{
+ log "Build and test using OpenSSL 3.0"
+ OSSL3=$HOME/src/openssl/inst/usr/local
+ LD_LIBRARY_PATH="$OSSL3/lib64" $MAKE CFLAGS="-O2 -Werror" \
+ LDFLAGS="-L$OSSL3/lib64" CPPFLAGS="-I$OSSL3/include" check
+}
+TEST_FUNCS+=(openssl3_test)
-log "Build and test with valgrind"
-$MAKE TEST_WRAPPER_PROG="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all" \
- CFLAGS="-O2 -Werror" check
+unsigned_char_test()
+{
+ log "Build and test using -funsigned-char"
+ $MAKE CFLAGS="-O2 -Werror -funsigned-char" check
+}
+TEST_FUNCS+=(unsigned_char_test)
-log "Build and test using BoringSSL instead of OpenSSL"
-BSSL=$HOME/src/boringssl
-$MAKE CFLAGS="-O2 -Werror" LDFLAGS="-L$BSSL/build/crypto" \
- CPPFLAGS="-I$BSSL/include" LDLIBS="-lcrypto -lpthread" check
+signed_char_test()
+{
+ log "Build and test using -fsigned-char"
+ $MAKE CFLAGS="-O2 -Werror -fsigned-char" check
+}
+TEST_FUNCS+=(signed_char_test)
-log "Build and test using -funsigned-char"
-$MAKE CFLAGS="-O2 -Werror -funsigned-char" check
+windows_build_test()
+{
+ log "Cross-compile for Windows (32-bit)"
+ $MAKE CC=i686-w64-mingw32-gcc CFLAGS="-O2 -Werror"
-log "Build and test using -fsigned-char"
-$MAKE CFLAGS="-O2 -Werror -fsigned-char" check
+ log "Cross-compile for Windows (64-bit)"
+ $MAKE CC=x86_64-w64-mingw32-gcc CFLAGS="-O2 -Werror"
+}
+TEST_FUNCS+=(windows_build_test)
-log "Cross-compile for Windows (32-bit)"
-$MAKE CC=i686-w64-mingw32-gcc CFLAGS="-O2 -Werror"
+sparse_test()
+{
+ log "Run sparse"
+ ./scripts/run-sparse.sh
+}
+TEST_FUNCS+=(sparse_test)
-log "Cross-compile for Windows (64-bit)"
-$MAKE CC=x86_64-w64-mingw32-gcc CFLAGS="-O2 -Werror"
+clang_analyzer_test()
+{
+ log "Run clang static analyzer"
+ scan-build --status-bugs make CFLAGS="-O2 -Werror" all test_programs
+}
+TEST_FUNCS+=(clang_analyzer_test)
-log "Run sparse"
-./scripts/run-sparse.sh
+shellcheck_test()
+{
+ log "Run shellcheck"
+ shellcheck scripts/*.sh 1>&2
+}
+TEST_FUNCS+=(shellcheck_test)
-log "Run clang static analyzer"
-scan-build --status-bugs make CFLAGS="-O2 -Werror" all test_programs
+test_exists()
+{
+ local tst=$1
+ local func
+ for func in "${TEST_FUNCS[@]}"; do
+ if [ "${tst}_test" = "$func" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
-log "Run shellcheck"
-shellcheck scripts/*.sh 1>&2
+if [[ $# == 0 ]]; then
+ for func in "${TEST_FUNCS[@]}"; do
+ eval "$func"
+ done
+else
+ for tst; do
+ if ! test_exists "$tst"; then
+ echo 1>&2 "Unknown test: $tst"
+ exit 2
+ fi
+ done
+ for tst; do
+ eval "${tst}_test"
+ done
+fi
log "All tests passed!"