Merge pull request #16249 from bluca/root_verity_sig

Verity: add support for root hash signature
This commit is contained in:
Lennart Poettering 2020-06-25 13:53:17 +02:00 committed by GitHub
commit b7d81d19cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 399 additions and 33 deletions

6
README
View File

@ -35,6 +35,7 @@ LICENSE:
REQUIREMENTS:
Linux kernel >= 3.13
Linux kernel >= 4.2 for unified cgroup hierarchy support
Linux kernel >= 5.4 for signed Verity images support
Kernel Config Options:
CONFIG_DEVTMPFS
@ -102,6 +103,9 @@ REQUIREMENTS:
CONFIG_EFIVAR_FS
CONFIG_EFI_PARTITION
Required for signed Verity images support:
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG
We recommend to turn off Real-Time group scheduling in the
kernel when using systemd. RT group scheduling effectively
makes RT scheduling unavailable for most userspace, since it
@ -144,7 +148,7 @@ REQUIREMENTS:
libblkid >= 2.24 (from util-linux) (optional)
libkmod >= 15 (optional)
PAM >= 1.1.2 (optional)
libcryptsetup (optional)
libcryptsetup (optional), >= 2.3.0 required for signed Verity images support
libaudit (optional)
libacl (optional)
libselinux (optional)

View File

@ -304,7 +304,7 @@
<para>Single file system images (i.e. file systems without a surrounding partition table) can be opened using
dm-verity if the integrity data is passed using the <option>--root-hash=</option> and
<option>--verity-data=</option> options.</para>
<option>--verity-data=</option> (and optionally <option>--root-hash-sig=</option>) options.</para>
<para>Any other partitions, such as foreign partitions or swap partitions are not mounted. May not be specified
together with <option>--directory=</option>, <option>--template=</option>.</para></listitem>
@ -399,6 +399,18 @@
is read from it and automatically used, also as formatted hexadecimal characters.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--root-hash-sig=</option></term>
<listitem><para>Takes a PKCS7 formatted binary signature of the <option>--root-hash=</option> option as a path
to a DER encoded signature file or as an ASCII base64 string encoding of the DER encoded signature, prefixed
by <literal>base64:</literal>. The dm-verity volume will only be opened if the signature of the root hash hex
string is valid and done by a public key present in the kernel keyring. If this option is not specified, but a
file with the <filename>.roothash.p7s</filename> suffix is found next to the image file, bearing otherwise the
same name (except if the image has the <filename>.raw</filename> suffix, in which case the signature file must
not have it in its name), the signature is read from it and automatically used.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--verity-data=</option></term>

View File

@ -164,6 +164,20 @@
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RootHashSignature=</varname></term>
<listitem><para>Takes a PKCS7 formatted binary signature of the <varname>RootHash=</varname> option as a path
to a DER encoded signature file or as an ASCII base64 string encoding of the DER encoded signature, prefixed
by <literal>base64:</literal>. The dm-verity volume will only be opened if the signature of the root hash
signature is valid and created by a public key present in the kernel keyring. If this option is not specified,
but a file with the <filename>.roothash.p7s</filename> suffix is found next to the image file, bearing otherwise
the same name (except if the image has the <filename>.raw</filename> suffix, in which case the signature file
must not have it in its name), the signature is read from it and automatically used.</para>
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RootVerity=</varname></term>

View File

@ -1035,6 +1035,8 @@ if want_libcryptsetup != 'false' and not skip_deps
conf.set10('HAVE_CRYPT_SET_METADATA_SIZE',
have and cc.has_function('crypt_set_metadata_size', dependencies : libcryptsetup))
conf.set10('HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY',
have and cc.has_function('crypt_activate_by_signed_key', dependencies : libcryptsetup))
else
have = false
libcryptsetup = []

View File

@ -71,7 +71,7 @@ _systemd_nspawn() {
--pivot-root --property --private-users --network-namespace-path --network-ipvlan
--network-veth-extra --network-zone -p --port --system-call-filter --overlay --overlay-ro
--settings --rlimit --hostname --no-new-privileges --oom-score-adjust --cpu-affinity
--resolv-conf --timezone'
--resolv-conf --timezone --root-hash-sig'
)
_init_completion || return
@ -183,6 +183,10 @@ _systemd_nspawn() {
--timezone)
comps=$( systemd-nspawn --timezone=help 2>/dev/null )
;;
--root-hash-sig)
compopt -o nospace
comps=$( compgen -A file -- "$cur" )
;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0

View File

@ -765,6 +765,25 @@ static int property_get_root_hash(
return sd_bus_message_append_array(reply, 'y', c->root_hash, c->root_hash_size);
}
static int property_get_root_hash_sig(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecContext *c = userdata;
assert(bus);
assert(c);
assert(property);
assert(reply);
return sd_bus_message_append_array(reply, 'y', c->root_hash_sig, c->root_hash_sig_size);
}
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@ -809,6 +828,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("RootImage", "s", NULL, offsetof(ExecContext, root_image), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHash", "ay", property_get_root_hash, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHashPath", "s", NULL, offsetof(ExecContext, root_hash_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHashSignature", "ay", property_get_root_hash_sig, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHashSignaturePath", "s", NULL, offsetof(ExecContext, root_hash_sig_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootVerity", "s", NULL, offsetof(ExecContext, root_verity), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CoredumpFilter", "t", property_get_coredump_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@ -1326,6 +1347,53 @@ int bus_exec_context_set_transient_property(
return bus_set_transient_path(u, "RootHash", &c->root_hash_path, message, flags, error);
}
if (streq(name, "RootHashSignature")) {
const void *roothash_sig_decoded;
size_t roothash_sig_decoded_size;
r = sd_bus_message_read_array(message, 'y', &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *encoded = NULL;
if (roothash_sig_decoded_size == 0) {
c->root_hash_sig_path = mfree(c->root_hash_sig_path);
c->root_hash_sig = mfree(c->root_hash_sig);
c->root_hash_sig_size = 0;
unit_write_settingf(u, flags, name, "RootHashSignature=");
} else {
_cleanup_free_ void *p;
ssize_t len;
len = base64mem(roothash_sig_decoded, roothash_sig_decoded_size, &encoded);
if (len < 0)
return -ENOMEM;
p = memdup(roothash_sig_decoded, roothash_sig_decoded_size);
if (!p)
return -ENOMEM;
free_and_replace(c->root_hash_sig, p);
c->root_hash_sig_size = roothash_sig_decoded_size;
c->root_hash_sig_path = mfree(c->root_hash_sig_path);
unit_write_settingf(u, flags, name, "RootHashSignature=base64:%s", encoded);
}
}
return 1;
}
if (streq(name, "RootHashSignaturePath")) {
c->root_hash_sig_size = 0;
c->root_hash_sig = mfree(c->root_hash_sig);
return bus_set_transient_path(u, "RootHashSignature", &c->root_hash_sig_path, message, flags, error);
}
if (streq(name, "RootVerity"))
return bus_set_transient_path(u, name, &c->root_verity, message, flags, error);

View File

@ -2667,7 +2667,9 @@ static int apply_mount_namespace(
needs_sandboxing ? context->protect_home : PROTECT_HOME_NO,
needs_sandboxing ? context->protect_system : PROTECT_SYSTEM_NO,
context->mount_flags,
context->root_hash, context->root_hash_size, context->root_hash_path, context->root_verity,
context->root_hash, context->root_hash_size, context->root_hash_path,
context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
context->root_verity,
DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK,
error_path);
@ -4200,6 +4202,9 @@ void exec_context_done(ExecContext *c) {
c->root_hash = mfree(c->root_hash);
c->root_hash_size = 0;
c->root_hash_path = mfree(c->root_hash_path);
c->root_hash_sig = mfree(c->root_hash_sig);
c->root_hash_sig_size = 0;
c->root_hash_sig_path = mfree(c->root_hash_sig_path);
c->root_verity = mfree(c->root_verity);
c->tty_path = mfree(c->tty_path);
c->syslog_identifier = mfree(c->syslog_identifier);
@ -4615,6 +4620,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
if (c->root_hash_path)
fprintf(f, "%sRootHash: %s\n", prefix, c->root_hash_path);
if (c->root_hash_sig) {
_cleanup_free_ char *encoded = NULL;
ssize_t len;
len = base64mem(c->root_hash_sig, c->root_hash_sig_size, &encoded);
if (len)
fprintf(f, "%sRootHashSignature: base64:%s\n", prefix, encoded);
}
if (c->root_hash_sig_path)
fprintf(f, "%sRootHashSignature: %s\n", prefix, c->root_hash_sig_path);
if (c->root_verity)
fprintf(f, "%sRootVerity: %s\n", prefix, c->root_verity);

View File

@ -155,9 +155,9 @@ struct ExecContext {
char **unset_environment;
struct rlimit *rlimit[_RLIMIT_MAX];
char *working_directory, *root_directory, *root_image, *root_verity, *root_hash_path;
void *root_hash;
size_t root_hash_size;
char *working_directory, *root_directory, *root_image, *root_verity, *root_hash_path, *root_hash_sig_path;
void *root_hash, *root_hash_sig;
size_t root_hash_size, root_hash_sig_size;
bool working_directory_missing_ok:1;
bool working_directory_home:1;

View File

@ -24,6 +24,7 @@ m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
$1.RootDirectory, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_directory)
$1.RootImage, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_image)
$1.RootHash, config_parse_exec_root_hash, 0, offsetof($1, exec_context)
$1.RootHashSignature, config_parse_exec_root_hash_sig, 0, offsetof($1, exec_context)
$1.RootVerity, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_verity)
$1.User, config_parse_user_group_compat, 0, offsetof($1, exec_context.user)
$1.Group, config_parse_user_group_compat, 0, offsetof($1, exec_context.group)

View File

@ -1472,6 +1472,66 @@ int config_parse_exec_root_hash(
return 0;
}
int config_parse_exec_root_hash_sig(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ void *roothash_sig_decoded = NULL;
char *value;
ExecContext *c = data;
size_t roothash_sig_decoded_size = 0;
int r;
assert(data);
assert(filename);
assert(line);
assert(rvalue);
if (isempty(rvalue)) {
/* Reset if the empty string is assigned */
c->root_hash_sig_path = mfree(c->root_hash_sig_path);
c->root_hash_sig = mfree(c->root_hash_sig);
c->root_hash_sig_size = 0;
return 0;
}
if (path_is_absolute(rvalue)) {
/* We have the path to a roothash signature to load and decode, eg: RootHashSignature=/foo/bar.roothash.p7s */
_cleanup_free_ char *p = NULL;
p = strdup(rvalue);
if (!p)
return -ENOMEM;
free_and_replace(c->root_hash_sig_path, p);
c->root_hash_sig = mfree(c->root_hash_sig);
c->root_hash_sig_size = 0;
return 0;
}
if (!(value = startswith(rvalue, "base64:")))
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature=, not a path but doesn't start with 'base64:', ignoring: %s", rvalue);
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode RootHashSignature=, ignoring: %s", rvalue);
free_and_replace(c->root_hash_sig, roothash_sig_decoded);
c->root_hash_sig_size = roothash_sig_decoded_size;
c->root_hash_sig_path = mfree(c->root_hash_sig_path);
return 0;
}
int config_parse_exec_cpu_affinity(const char *unit,
const char *filename,
unsigned line,

View File

@ -45,6 +45,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_sched_prio);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_affinity);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_secure_bits);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash_sig);
CONFIG_PARSER_PROTOTYPE(config_parse_capability_set);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_mount_flags);
CONFIG_PARSER_PROTOTYPE(config_parse_timer);

View File

@ -1260,6 +1260,9 @@ int setup_namespace(
const void *root_hash,
size_t root_hash_size,
const char *root_hash_path,
const void *root_hash_sig,
size_t root_hash_sig_size,
const char *root_hash_sig_path,
const char *root_verity,
DissectImageFlags dissect_image_flags,
char **error_path) {
@ -1268,7 +1271,7 @@ int setup_namespace(
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_free_ void *root_hash_decoded = NULL;
_cleanup_free_ char *verity_data = NULL;
_cleanup_free_ char *verity_data = NULL, *hash_sig_path = NULL;
MountEntry *m = NULL, *mounts = NULL;
size_t n_mounts;
bool require_prefix = false;
@ -1299,7 +1302,7 @@ int setup_namespace(
if (r < 0)
return log_debug_errno(r, "Failed to create loop device for root image: %m");
r = verity_metadata_load(root_image, root_hash_path, root_hash ? NULL : &root_hash_decoded, root_hash ? NULL : &root_hash_size, root_verity ? NULL : &verity_data);
r = verity_metadata_load(root_image, root_hash_path, root_hash ? NULL : &root_hash_decoded, root_hash ? NULL : &root_hash_size, root_verity ? NULL : &verity_data, root_hash_sig || root_hash_sig_path ? NULL : &hash_sig_path);
if (r < 0)
return log_debug_errno(r, "Failed to load root hash: %m");
dissect_image_flags |= root_verity || verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
@ -1308,7 +1311,7 @@ int setup_namespace(
if (r < 0)
return log_debug_errno(r, "Failed to dissect image: %m");
r = dissected_image_decrypt(dissected_image, NULL, root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, dissect_image_flags, &decrypted_image);
r = dissected_image_decrypt(dissected_image, NULL, root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, root_hash_sig_path ?: hash_sig_path, root_hash_sig, root_hash_sig_size, dissect_image_flags, &decrypted_image);
if (r < 0)
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
}

View File

@ -91,6 +91,9 @@ int setup_namespace(
const void *root_hash,
size_t root_hash_size,
const char *root_hash_path,
const void *root_hash_sig,
size_t root_hash_sig_size,
const char *root_hash_sig_path,
const char *root_verity,
DissectImageFlags dissected_image_flags,
char **error_path);

View File

@ -28,9 +28,14 @@ static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DI
static void *arg_root_hash = NULL;
static char *arg_verity_data = NULL;
static size_t arg_root_hash_size = 0;
static char *arg_root_hash_sig_path = NULL;
static void *arg_root_hash_sig = NULL;
static size_t arg_root_hash_sig_size = 0;
STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep);
STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig, freep);
static void help(void) {
printf("%s [OPTIONS...] IMAGE\n"
@ -43,6 +48,10 @@ static void help(void) {
" --fsck=BOOL Run fsck before mounting\n"
" --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
" --root-hash=HASH Specify root hash for verity\n"
" --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
" as a DER encoded PKCS7, either as a path to a file\n"
" or as an ASCII base64 encoded string prefixed by\n"
" 'base64:'\n"
" --verity-data=PATH Specify data file with hash tree for verity if it is\n"
" not embedded in IMAGE\n",
program_invocation_short_name,
@ -57,17 +66,19 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ROOT_HASH,
ARG_FSCK,
ARG_VERITY_DATA,
ARG_ROOT_HASH_SIG,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "mount", no_argument, NULL, 'm' },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "root-hash", required_argument, NULL, ARG_ROOT_HASH },
{ "fsck", required_argument, NULL, ARG_FSCK },
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "mount", no_argument, NULL, 'm' },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "root-hash", required_argument, NULL, ARG_ROOT_HASH },
{ "fsck", required_argument, NULL, ARG_FSCK },
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
{ "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
{}
};
@ -140,6 +151,31 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_ROOT_HASH_SIG: {
char *value;
if ((value = startswith(optarg, "base64:"))) {
void *p;
size_t l;
r = unbase64mem(value, strlen(value), &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
free_and_replace(arg_root_hash_sig, p);
arg_root_hash_sig_size = l;
arg_root_hash_sig_path = mfree(arg_root_hash_sig_path);
} else {
r = parse_path_argument_and_warn(optarg, false, &arg_root_hash_sig_path);
if (r < 0)
return r;
arg_root_hash_sig = mfree(arg_root_hash_sig);
arg_root_hash_sig_size = 0;
}
break;
}
case ARG_FSCK:
r = parse_boolean(optarg);
if (r < 0)
@ -202,7 +238,8 @@ static int run(int argc, char *argv[]) {
return log_error_errno(r, "Failed to set up loopback device: %m");
r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
arg_verity_data ? NULL : &arg_verity_data);
arg_verity_data ? NULL : &arg_verity_data,
arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path);
if (r < 0)
return log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image);
arg_flags |= arg_verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
@ -279,7 +316,7 @@ static int run(int argc, char *argv[]) {
}
case ACTION_MOUNT:
r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_flags, &di);
r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, arg_flags, &di);
if (r < 0)
return r;

View File

@ -200,6 +200,9 @@ static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS
static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_TMPFS_TMP;
static void *arg_root_hash = NULL;
static char *arg_verity_data = NULL;
static char *arg_root_hash_sig_path = NULL;
static void *arg_root_hash_sig = NULL;
static size_t arg_root_hash_sig_size = 0;
static size_t arg_root_hash_size = 0;
static char **arg_syscall_allow_list = NULL;
static char **arg_syscall_deny_list = NULL;
@ -244,6 +247,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_property_message, sd_bus_message_unrefp);
STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep);
STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig, freep);
STATIC_DESTRUCTOR_REGISTER(arg_syscall_allow_list, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_syscall_deny_list, strv_freep);
#if HAVE_SECCOMP
@ -305,6 +310,10 @@ static int help(void) {
" --read-only Mount the root directory read-only\n"
" --volatile[=MODE] Run the system in volatile mode\n"
" --root-hash=HASH Specify verity root hash for root disk image\n"
" --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
" as a DER encoded PKCS7, either as a path to a file\n"
" or as an ASCII base64 encoded string prefixed by\n"
" 'base64:'\n"
" --verity-data=PATH Specify hash device for verity\n"
" --pivot-root=PATH[:PATH]\n"
" Pivot root to given directory in the container\n\n"
@ -667,6 +676,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_OCI_BUNDLE,
ARG_NO_PAGER,
ARG_VERITY_DATA,
ARG_ROOT_HASH_SIG,
};
static const struct option options[] = {
@ -733,6 +743,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "oci-bundle", required_argument, NULL, ARG_OCI_BUNDLE },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
{ "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
{}
};
@ -1327,6 +1338,31 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_ROOT_HASH_SIG: {
char *value;
if ((value = startswith(optarg, "base64:"))) {
void *p;
size_t l;
r = unbase64mem(value, strlen(value), &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
free_and_replace(arg_root_hash_sig, p);
arg_root_hash_sig_size = l;
arg_root_hash_sig_path = mfree(arg_root_hash_sig_path);
} else {
r = parse_path_argument_and_warn(optarg, false, &arg_root_hash_sig_path);
if (r < 0)
return r;
arg_root_hash_sig = mfree(arg_root_hash_sig);
arg_root_hash_sig_size = 0;
}
break;
}
case ARG_SYSTEM_CALL_FILTER: {
bool negative;
const char *items;
@ -5143,7 +5179,8 @@ static int run(int argc, char *argv[]) {
}
r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
arg_verity_data ? NULL : &arg_verity_data);
arg_verity_data ? NULL : &arg_verity_data,
arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path);
if (r < 0) {
log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image);
goto finish;
@ -5193,7 +5230,7 @@ static int run(int argc, char *argv[]) {
if (!arg_root_hash && dissected_image->can_verity)
log_notice("Note: image %s contains verity information, but no root hash specified! Proceeding without integrity checking.", arg_image);
r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, 0, &decrypted_image);
r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, 0, &decrypted_image);
if (r < 0)
goto finish;

View File

@ -1434,6 +1434,26 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return bus_append_byte_array(m, field, roothash_decoded, roothash_decoded_size);
}
if (streq(field, "RootHashSignature")) {
_cleanup_free_ void *roothash_sig_decoded = NULL;
char *value;
size_t roothash_sig_decoded_size = 0;
/* We have the path to a roothash signature to load and decode, eg: RootHash=/foo/bar.roothash.p7s */
if (path_is_absolute(eq))
return bus_append_string(m, "RootHashSignaturePath", eq);
if (!(value = startswith(eq, "base64:")))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq);
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq);
return bus_append_byte_array(m, field, roothash_sig_decoded, roothash_sig_decoded_size);
}
return 0;
}

View File

@ -1223,6 +1223,9 @@ static int verity_partition(
const void *root_hash,
size_t root_hash_size,
const char *verity_data,
const char *root_hash_sig_path,
const void *root_hash_sig,
size_t root_hash_sig_size,
DissectImageFlags flags,
DecryptedImage *d) {
@ -1267,7 +1270,25 @@ static int verity_partition(
if (r < 0)
return r;
r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY);
if (root_hash_sig || root_hash_sig_path) {
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
if (root_hash_sig)
r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, root_hash_sig, root_hash_sig_size, CRYPT_ACTIVATE_READONLY);
else {
_cleanup_free_ char *hash_sig = NULL;
size_t hash_sig_size;
r = read_full_file_full(AT_FDCWD, root_hash_sig_path, 0, &hash_sig, &hash_sig_size);
if (r < 0)
return r;
r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY);
}
#else
r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "activation of verity device with signature requested, but not supported by cryptsetup due to missing crypt_activate_by_signed_key()");
#endif
} else
r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY);
if (r < 0)
return r;
@ -1287,6 +1308,9 @@ int dissected_image_decrypt(
const void *root_hash,
size_t root_hash_size,
const char *verity_data,
const char *root_hash_sig_path,
const void *root_hash_sig,
size_t root_hash_sig_size,
DissectImageFlags flags,
DecryptedImage **ret) {
@ -1333,7 +1357,7 @@ int dissected_image_decrypt(
k = PARTITION_VERITY_OF(i);
if (k >= 0) {
r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, flags, d);
r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, root_hash_sig_path, root_hash_sig, root_hash_sig_size, flags, d);
if (r < 0)
return r;
}
@ -1359,6 +1383,9 @@ int dissected_image_decrypt_interactively(
const void *root_hash,
size_t root_hash_size,
const char *verity_data,
const char *root_hash_sig_path,
const void *root_hash_sig,
size_t root_hash_sig_size,
DissectImageFlags flags,
DecryptedImage **ret) {
@ -1369,7 +1396,7 @@ int dissected_image_decrypt_interactively(
n--;
for (;;) {
r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, verity_data, flags, ret);
r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, verity_data, root_hash_sig_path, root_hash_sig, root_hash_sig_size, flags, ret);
if (r >= 0)
return r;
if (r == -EKEYREJECTED)
@ -1421,8 +1448,8 @@ int decrypted_image_relinquish(DecryptedImage *d) {
return 0;
}
int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data) {
_cleanup_free_ char *verity_filename = NULL;
int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data, char **ret_roothashsig) {
_cleanup_free_ char *verity_filename = NULL, *roothashsig_filename = NULL;
_cleanup_free_ void *roothash_decoded = NULL;
size_t roothash_decoded_size = 0;
int r;
@ -1437,6 +1464,8 @@ int verity_metadata_load(const char *image, const char *root_hash_path, void **r
*ret_roothash_size = 0;
if (ret_verity_data)
*ret_verity_data = NULL;
if (ret_roothashsig)
*ret_roothashsig = NULL;
return 0;
}
@ -1461,6 +1490,29 @@ int verity_metadata_load(const char *image, const char *root_hash_path, void **r
}
}
if (ret_roothashsig) {
char *e;
/* Follow naming convention recommended by the relevant RFC:
* https://tools.ietf.org/html/rfc5751#section-3.2.1 */
roothashsig_filename = new(char, strlen(image) + STRLEN(".roothash.p7s") + 1);
if (!roothashsig_filename)
return -ENOMEM;
strcpy(roothashsig_filename, image);
e = endswith(roothashsig_filename, ".raw");
if (e)
strcpy(e, ".roothash.p7s");
else
strcat(roothashsig_filename, ".roothash.p7s");
r = access(roothashsig_filename, R_OK);
if (r < 0) {
if (errno != ENOENT)
return -errno;
roothashsig_filename = mfree(roothashsig_filename);
}
}
if (ret_roothash) {
_cleanup_free_ char *text = NULL;
assert(ret_roothash_size);
@ -1507,6 +1559,8 @@ int verity_metadata_load(const char *image, const char *root_hash_path, void **r
}
if (ret_verity_data)
*ret_verity_data = TAKE_PTR(verity_filename);
if (roothashsig_filename)
*ret_roothashsig = TAKE_PTR(roothashsig_filename);
return 1;
}

View File

@ -87,8 +87,8 @@ int dissect_image_and_warn(int fd, const char *name, const void *root_hash, size
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, DissectImageFlags flags);
int dissected_image_acquire_metadata(DissectedImage *m);
@ -100,6 +100,6 @@ int decrypted_image_relinquish(DecryptedImage *d);
const char* partition_designator_to_string(int i) _const_;
int partition_designator_from_string(const char *name) _pure_;
int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data);
int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data, char **ret_roothashsig);
bool dissected_image_can_do_verity(const DissectedImage *image, unsigned partition_designator);
bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_designator);

View File

@ -5188,7 +5188,7 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
return 1;
} else if (contents[0] == SD_BUS_TYPE_BYTE && streq(name, "StandardInputData")) {
} else if (contents[0] == SD_BUS_TYPE_BYTE && STR_IN_SET(name, "StandardInputData", "RootHashSignature")) {
_cleanup_free_ char *h = NULL;
const void *p;
size_t sz;

View File

@ -157,6 +157,9 @@ static void test_protect_kernel_logs(void) {
NULL,
NULL,
0,
NULL,
NULL,
0,
NULL);
assert_se(r == 0);

View File

@ -81,6 +81,9 @@ int main(int argc, char *argv[]) {
NULL,
NULL,
0,
NULL,
NULL,
0,
NULL);
if (r < 0) {
log_error_errno(r, "Failed to set up namespace: %m");

View File

@ -6,9 +6,11 @@
#include "alloc-util.h"
#include "crypt-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "log.h"
#include "main-func.h"
#include "path-util.h"
#include "pretty-print.h"
#include "string-util.h"
#include "terminal-util.h"
@ -29,7 +31,7 @@ static int help(void) {
if (r < 0)
return log_oom();
printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH\n"
printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [ROOTHASHSIG]\n"
"%s detach VOLUME\n\n"
"Attaches or detaches an integrity protected block device.\n"
"\nSee the %s for details.\n"
@ -87,7 +89,28 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to configure data device: %m");
r = crypt_activate_by_volume_key(cd, argv[2], m, l, CRYPT_ACTIVATE_READONLY);
if (argc > 6) {
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
_cleanup_free_ char *hash_sig = NULL;
size_t hash_sig_size;
char *value;
if ((value = startswith(argv[6], "base64:"))) {
r = unbase64mem(value, strlen(value), (void *)&hash_sig, &hash_sig_size);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", argv[6]);
} else {
r = read_full_file_full(AT_FDCWD, argv[6], 0, &hash_sig, &hash_sig_size);
if (r < 0)
return log_error_errno(r, "Failed to read root hash signature: %m");
}
r = crypt_activate_by_signed_key(cd, argv[2], m, l, hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY);
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "activation of verity device with signature %s requested, but not supported by cryptsetup due to missing crypt_activate_by_signed_key()", argv[6]);
#endif
} else
r = crypt_activate_by_volume_key(cd, argv[2], m, l, CRYPT_ACTIVATE_READONLY);
if (r < 0)
return log_error_errno(r, "Failed to set up verity device: %m");

View File

@ -197,6 +197,7 @@ RootDirectory=
RootDirectoryStartOnly=
RootImage=
RootHash=
RootHashSignature=
RootVerity=
RuntimeMaxSec=
SELinuxContextFromNet=