mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-23 17:34:00 +03:00
Merge pull request #24635 from DaanDeMeyer/repart-verity-sig
repart: Add support for generating verity sig partitions
This commit is contained in:
commit
354dc913c5
@ -280,8 +280,8 @@ size. Currently three fields are defined for the JSON object:
|
||||
in `rootHash` above.
|
||||
|
||||
3. The (optional) `certificateFingerprint` field should be a string containing
|
||||
a SHA256 fingerprint of the X.509 certificate for the key that signed the
|
||||
root hash, formatted as series of (lowercase) hex characters (no `:`
|
||||
a SHA256 fingerprint of the X.509 certificate in DER format for the key that
|
||||
signed the root hash, formatted as series of (lowercase) hex characters (no `:`
|
||||
separators or such).
|
||||
|
||||
More fields might be added in later revisions of this specification.
|
||||
|
@ -583,17 +583,21 @@
|
||||
<varlistentry>
|
||||
<term><varname>Verity=</varname></term>
|
||||
|
||||
<listitem><para>Takes one of <literal>off</literal>, <literal>data</literal> or
|
||||
<literal>hash</literal>. Defaults to <literal>off</literal>. If set to <literal>off</literal> or
|
||||
<literal>data</literal>, the partition is populated with content as specified by
|
||||
<varname>CopyBlocks=</varname> or <varname>CopyFiles=</varname>. If set to <literal>hash</literal>,
|
||||
the partition will be populated with verity hashes from a matching verity data partition. A matching
|
||||
data partition is a partition with <varname>Verity=</varname> set to <literal>data</literal> and the
|
||||
same verity match key (as configured with <varname>VerityMatchKey=</varname>). If not explicitly
|
||||
configured, the data partition's UUID will be set to the first 128 bits of the verity root hash.
|
||||
Similarly, if not configured, the hash partition's UUID will be set to the final 128 bits of the
|
||||
verity root hash. The verity root hash itself will be included in the output of
|
||||
<command>systemd-repart</command>.</para>
|
||||
<listitem><para>Takes one of <literal>off</literal>, <literal>data</literal>,
|
||||
<literal>hash</literal> or <literal>signature</literal>. Defaults to <literal>off</literal>. If set
|
||||
to <literal>off</literal> or <literal>data</literal>, the partition is populated with content as
|
||||
specified by <varname>CopyBlocks=</varname> or <varname>CopyFiles=</varname>. If set to
|
||||
<literal>hash</literal>, the partition will be populated with verity hashes from the matching verity
|
||||
data partition. If set to <literal>signature</literal>, The partition will be populated with a JSON
|
||||
object containing a signature of the verity root hash of the matching verity hash partition.</para>
|
||||
|
||||
<para>A matching verity partition is a partition with the same verity match key (as configured with
|
||||
<varname>VerityMatchKey=</varname>).</para>
|
||||
|
||||
<para>If not explicitly configured, the data partition's UUID will be set to the first 128
|
||||
bits of the verity root hash. Similarly, if not configured, the hash partition's UUID will be set to
|
||||
the final 128 bits of the verity root hash. The verity root hash itself will be included in the
|
||||
output of <command>systemd-repart</command>.</para>
|
||||
|
||||
<para>This option has no effect if the partition already exists.</para>
|
||||
|
||||
|
@ -3682,7 +3682,8 @@ if conf.get('ENABLE_REPART') == 1
|
||||
link_with : [libshared],
|
||||
dependencies : [threads,
|
||||
libblkid,
|
||||
libfdisk],
|
||||
libfdisk,
|
||||
libopenssl],
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
|
@ -521,6 +521,19 @@ char* strshorten(char *s, size_t l) {
|
||||
return s;
|
||||
}
|
||||
|
||||
int strgrowpad0(char **s, size_t l) {
|
||||
assert(s);
|
||||
|
||||
char *q = realloc(*s, l);
|
||||
if (!q)
|
||||
return -ENOMEM;
|
||||
*s = q;
|
||||
|
||||
size_t sz = strlen(*s);
|
||||
memzero(*s + sz, l - sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *strreplace(const char *text, const char *old_string, const char *new_string) {
|
||||
size_t l, old_len, new_len;
|
||||
char *t, *ret = NULL;
|
||||
|
@ -152,6 +152,8 @@ char *cellescape(char *buf, size_t len, const char *s);
|
||||
|
||||
char* strshorten(char *s, size_t l);
|
||||
|
||||
int strgrowpad0(char **s, size_t l);
|
||||
|
||||
char *strreplace(const char *text, const char *old_string, const char *new_string);
|
||||
|
||||
char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]);
|
||||
|
@ -948,7 +948,7 @@ static int run(int argc, char *argv[]) {
|
||||
d->fd,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to load verity signature partition: %m");
|
||||
|
||||
switch (arg_action) {
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "hexdecoct.h"
|
||||
#include "hmac.h"
|
||||
#include "id128-util.h"
|
||||
#include "io-util.h"
|
||||
#include "json.h"
|
||||
#include "list.h"
|
||||
#include "loop-util.h"
|
||||
@ -48,6 +49,7 @@
|
||||
#include "mkfs-util.h"
|
||||
#include "mount-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-helpers.h"
|
||||
#include "pretty-print.h"
|
||||
@ -76,6 +78,9 @@
|
||||
/* Hard lower limit for new partition sizes */
|
||||
#define HARD_MIN_SIZE 4096
|
||||
|
||||
/* We know up front we're never going to put more than this in a verity sig partition. */
|
||||
#define VERITY_SIG_SIZE (HARD_MIN_SIZE * 4)
|
||||
|
||||
/* libfdisk takes off slightly more than 1M of the disk size when creating a GPT disk label */
|
||||
#define GPT_METADATA_SIZE (1044*1024)
|
||||
|
||||
@ -113,6 +118,8 @@ static PagerFlags arg_pager_flags = 0;
|
||||
static bool arg_legend = true;
|
||||
static void *arg_key = NULL;
|
||||
static size_t arg_key_size = 0;
|
||||
static EVP_PKEY *arg_private_key = NULL;
|
||||
static X509 *arg_certificate = NULL;
|
||||
static char *arg_tpm2_device = NULL;
|
||||
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
|
||||
static char *arg_tpm2_public_key = NULL;
|
||||
@ -123,6 +130,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
|
||||
|
||||
@ -143,6 +152,7 @@ typedef enum VerityMode {
|
||||
VERITY_OFF,
|
||||
VERITY_DATA,
|
||||
VERITY_HASH,
|
||||
VERITY_SIG,
|
||||
_VERITY_MODE_MAX,
|
||||
_VERITY_MODE_INVALID = -EINVAL,
|
||||
} VerityMode;
|
||||
@ -240,6 +250,7 @@ static const char *verity_mode_table[_VERITY_MODE_MAX] = {
|
||||
[VERITY_OFF] = "off",
|
||||
[VERITY_DATA] = "data",
|
||||
[VERITY_HASH] = "hash",
|
||||
[VERITY_SIG] = "signature",
|
||||
};
|
||||
|
||||
#if HAVE_LIBCRYPTSETUP
|
||||
@ -515,6 +526,9 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) {
|
||||
return p->current_size;
|
||||
}
|
||||
|
||||
if (p->verity == VERITY_SIG)
|
||||
return VERITY_SIG_SIZE;
|
||||
|
||||
sz = p->current_size != UINT64_MAX ? p->current_size : HARD_MIN_SIZE;
|
||||
|
||||
if (!PARTITION_EXISTS(p)) {
|
||||
@ -556,6 +570,9 @@ static uint64_t partition_max_size(const Context *context, const Partition *p) {
|
||||
return p->current_size;
|
||||
}
|
||||
|
||||
if (p->verity == VERITY_SIG)
|
||||
return VERITY_SIG_SIZE;
|
||||
|
||||
if (p->size_max == UINT64_MAX)
|
||||
return UINT64_MAX;
|
||||
|
||||
@ -1548,7 +1565,8 @@ static int partition_read_definition(Partition *p, const char *path, const char
|
||||
"VerityMatchKey= can only be set if Verity= is not \"%s\"",
|
||||
verity_mode_to_string(p->verity));
|
||||
|
||||
if (p->verity == VERITY_HASH && (p->copy_files || p->copy_blocks_path || p->copy_blocks_auto || p->format || p->make_directories))
|
||||
if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) &&
|
||||
(p->copy_files || p->copy_blocks_path || p->copy_blocks_auto || p->format || p->make_directories))
|
||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s",
|
||||
verity_mode_to_string(p->verity));
|
||||
@ -1557,6 +1575,19 @@ static int partition_read_definition(Partition *p, const char *path, const char
|
||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Encrypting verity hash/data partitions is not supported");
|
||||
|
||||
if (p->verity == VERITY_SIG && !arg_private_key)
|
||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Verity signature partition requested but no private key provided (--private-key=)");
|
||||
|
||||
if (p->verity == VERITY_SIG && !arg_certificate)
|
||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Verity signature partition requested but no PEM certificate provided (--certificate-file=)");
|
||||
|
||||
if (p->verity == VERITY_SIG && (p->size_min != UINT64_MAX || p->size_max != UINT64_MAX))
|
||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s",
|
||||
verity_mode_to_string(p->verity));
|
||||
|
||||
/* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */
|
||||
if ((gpt_partition_type_is_root_verity(p->type_uuid) ||
|
||||
gpt_partition_type_is_usr_verity(p->type_uuid)) &&
|
||||
@ -1665,7 +1696,7 @@ static int context_read_definitions(
|
||||
continue;
|
||||
|
||||
for (VerityMode mode = VERITY_OFF + 1; mode < _VERITY_MODE_MAX; mode++) {
|
||||
Partition *q;
|
||||
Partition *q = NULL;
|
||||
|
||||
if (p->verity == mode)
|
||||
continue;
|
||||
@ -1674,7 +1705,7 @@ static int context_read_definitions(
|
||||
continue;
|
||||
|
||||
r = find_verity_sibling(context, p, mode, &q);
|
||||
if (r == -ENXIO)
|
||||
if (mode != VERITY_SIG && r == -ENXIO)
|
||||
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Missing verity %s partition for verity %s partition with VerityMatchKey=%s",
|
||||
verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key);
|
||||
@ -1685,12 +1716,14 @@ static int context_read_definitions(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (q->priority != p->priority)
|
||||
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s",
|
||||
p->priority, q->priority, p->verity_match_key);
|
||||
if (q) {
|
||||
if (q->priority != p->priority)
|
||||
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s",
|
||||
p->priority, q->priority, p->verity_match_key);
|
||||
|
||||
p->siblings[mode] = q;
|
||||
p->siblings[mode] = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3577,11 +3610,11 @@ static int do_verity_format(
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity: %m");
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int context_verity(Context *context) {
|
||||
static int context_verity_hash(Context *context) {
|
||||
int fd = -1, r;
|
||||
|
||||
assert(context);
|
||||
@ -3642,6 +3675,181 @@ static int context_verity(Context *context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_x509_certificate(const char *certificate, size_t certificate_size, X509 **ret) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(X509_freep) X509 *cert = NULL;
|
||||
_cleanup_(BIO_freep) BIO *cb = NULL;
|
||||
|
||||
assert(certificate);
|
||||
assert(certificate_size > 0);
|
||||
assert(ret);
|
||||
|
||||
cb = BIO_new_mem_buf(certificate, certificate_size);
|
||||
if (!cb)
|
||||
return log_oom();
|
||||
|
||||
cert = PEM_read_bio_X509(cb, NULL, NULL, NULL);
|
||||
if (!cert)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(cert);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot parse X509 certificate.");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int parse_private_key(const char *key, size_t key_size, EVP_PKEY **ret) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(BIO_freep) BIO *kb = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
|
||||
|
||||
assert(key);
|
||||
assert(key_size > 0);
|
||||
assert(ret);
|
||||
|
||||
kb = BIO_new_mem_buf(key, key_size);
|
||||
if (!kb)
|
||||
return log_oom();
|
||||
|
||||
pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
|
||||
if (!pk)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(pk);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot parse private key.");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int sign_verity_roothash(
|
||||
const uint8_t *roothash,
|
||||
size_t roothash_size,
|
||||
uint8_t **ret_signature,
|
||||
size_t *ret_signature_size) {
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(BIO_freep) BIO *rb = NULL;
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
_cleanup_free_ char *hex = NULL;
|
||||
_cleanup_free_ uint8_t *sig = NULL;
|
||||
int sigsz;
|
||||
|
||||
assert(roothash);
|
||||
assert(roothash_size > 0);
|
||||
assert(ret_signature);
|
||||
assert(ret_signature_size);
|
||||
|
||||
hex = hexmem(roothash, roothash_size);
|
||||
if (!hex)
|
||||
return log_oom();
|
||||
|
||||
rb = BIO_new_mem_buf(hex, -1);
|
||||
if (!rb)
|
||||
return log_oom();
|
||||
|
||||
p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
|
||||
if (!p7)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
sigsz = i2d_PKCS7(p7, &sig);
|
||||
if (sigsz < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
*ret_signature = TAKE_PTR(sig);
|
||||
*ret_signature_size = sigsz;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot setup verity signature: %m");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int context_verity_sig(Context *context) {
|
||||
int fd = -1, r;
|
||||
|
||||
assert(context);
|
||||
|
||||
LIST_FOREACH(partitions, p, context->partitions) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_free_ uint8_t *sig = NULL;
|
||||
_cleanup_free_ char *text = NULL;
|
||||
Partition *hp;
|
||||
uint8_t fp[X509_FINGERPRINT_SIZE];
|
||||
size_t sigsz, padsz;
|
||||
|
||||
if (p->dropped)
|
||||
continue;
|
||||
|
||||
if (PARTITION_EXISTS(p))
|
||||
continue;
|
||||
|
||||
if (p->verity != VERITY_SIG)
|
||||
continue;
|
||||
|
||||
assert_se(hp = p->siblings[VERITY_HASH]);
|
||||
assert(!hp->dropped);
|
||||
|
||||
assert(arg_certificate);
|
||||
|
||||
if (fd < 0)
|
||||
assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
|
||||
|
||||
r = sign_verity_roothash(hp->roothash, hp->roothash_size, &sig, &sigsz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = x509_fingerprint(arg_certificate, fp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m");
|
||||
|
||||
r = json_build(&v,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("rootHash", JSON_BUILD_HEX(hp->roothash, hp->roothash_size)),
|
||||
JSON_BUILD_PAIR(
|
||||
"certificateFingerprint",
|
||||
JSON_BUILD_HEX(fp, sizeof(fp))
|
||||
),
|
||||
JSON_BUILD_PAIR("signature", JSON_BUILD_BASE64(sig, sigsz))
|
||||
)
|
||||
);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build JSON object: %m");
|
||||
|
||||
r = json_variant_format(v, 0, &text);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format JSON object: %m");
|
||||
|
||||
padsz = round_up_size(strlen(text), 4096);
|
||||
assert_se(padsz <= p->new_size);
|
||||
|
||||
r = strgrowpad0(&text, padsz);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to pad string to %s", FORMAT_BYTES(padsz));
|
||||
|
||||
if (lseek(fd, p->offset, SEEK_SET) == (off_t) -1)
|
||||
return log_error_errno(errno, "Failed to seek to partition offset: %m");
|
||||
|
||||
r = loop_write(fd, text, padsz, /*do_poll=*/ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write verity signature to partition: %m");
|
||||
|
||||
if (fsync(fd) < 0)
|
||||
return log_error_errno(errno, "Failed to synchronize verity signature JSON: %m");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) {
|
||||
struct {
|
||||
sd_id128_t type_uuid;
|
||||
@ -3787,7 +3995,7 @@ static int context_acquire_partition_uuids_and_labels(Context *context) {
|
||||
|
||||
if (!sd_id128_is_null(p->current_uuid))
|
||||
p->new_uuid = p->current_uuid; /* Never change initialized UUIDs */
|
||||
else if (!p->new_uuid_is_set && p->verity == VERITY_OFF) {
|
||||
else if (!p->new_uuid_is_set && !IN_SET(p->verity, VERITY_DATA, VERITY_HASH)) {
|
||||
/* Not explicitly set by user! */
|
||||
r = partition_acquire_uuid(context, p, &p->new_uuid);
|
||||
if (r < 0)
|
||||
@ -4201,7 +4409,11 @@ static int context_write_partition_table(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = context_verity(context);
|
||||
r = context_verity_hash(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = context_verity_sig(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -4747,6 +4959,10 @@ static int help(void) {
|
||||
" --image=PATH Operate relative to image file\n"
|
||||
" --definitions=DIR Find partition definitions in specified directory\n"
|
||||
" --key-file=PATH Key to use when encrypting partitions\n"
|
||||
" --private-key=PATH Private key to use when generating verity roothash\n"
|
||||
" signatures\n"
|
||||
" --certificate=PATH PEM certificate to use when generating verity\n"
|
||||
" roothash signatures\n"
|
||||
" --tpm2-device=PATH Path to TPM2 device node to use\n"
|
||||
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
|
||||
" TPM2 PCR indexes to use for TPM2 enrollment\n"
|
||||
@ -4787,6 +5003,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_SIZE,
|
||||
ARG_JSON,
|
||||
ARG_KEY_FILE,
|
||||
ARG_PRIVATE_KEY,
|
||||
ARG_CERTIFICATE,
|
||||
ARG_TPM2_DEVICE,
|
||||
ARG_TPM2_PCRS,
|
||||
ARG_TPM2_PUBLIC_KEY,
|
||||
@ -4812,6 +5030,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "size", required_argument, NULL, ARG_SIZE },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
|
||||
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
|
||||
{ "certificate", required_argument, NULL, ARG_CERTIFICATE },
|
||||
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
|
||||
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
|
||||
@ -4985,6 +5205,46 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_PRIVATE_KEY: {
|
||||
_cleanup_(erase_and_freep) char *k = NULL;
|
||||
size_t n = 0;
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&k, &n);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read key file '%s': %m", optarg);
|
||||
|
||||
EVP_PKEY_free(arg_private_key);
|
||||
arg_private_key = NULL;
|
||||
r = parse_private_key(k, n, &arg_private_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_CERTIFICATE: {
|
||||
_cleanup_free_ char *cert = NULL;
|
||||
size_t n = 0;
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&cert, &n);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read certificate file '%s': %m", optarg);
|
||||
|
||||
X509_free(arg_certificate);
|
||||
arg_certificate = NULL;
|
||||
r = parse_x509_certificate(cert, n, &arg_certificate);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_TPM2_DEVICE: {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
|
@ -603,13 +603,10 @@ int dissect_image(
|
||||
|
||||
m->has_verity_sig = true;
|
||||
|
||||
/* If root hash is specified explicitly, then ignore any embedded signature */
|
||||
if (!verity)
|
||||
continue;
|
||||
if (verity->designator >= 0 && verity->designator != PARTITION_ROOT)
|
||||
continue;
|
||||
if (verity->root_hash)
|
||||
continue;
|
||||
|
||||
assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
|
||||
designator = PARTITION_VERITY_SIG_OF(PARTITION_ROOT_OF_ARCH(architecture));
|
||||
@ -667,13 +664,10 @@ int dissect_image(
|
||||
|
||||
m->has_verity_sig = true;
|
||||
|
||||
/* If usr hash is specified explicitly, then ignore any embedded signature */
|
||||
if (!verity)
|
||||
continue;
|
||||
if (verity->designator >= 0 && verity->designator != PARTITION_USR)
|
||||
continue;
|
||||
if (verity->root_hash)
|
||||
continue;
|
||||
|
||||
assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
|
||||
designator = PARTITION_VERITY_SIG_OF(PARTITION_USR_OF_ARCH(architecture));
|
||||
@ -1074,6 +1068,9 @@ int dissect_image(
|
||||
if (verity->designator >= 0 && !m->partitions[verity->designator].found)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
bool have_verity_sig_partition =
|
||||
m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR_VERITY_SIG : PARTITION_ROOT_VERITY_SIG].found;
|
||||
|
||||
if (verity->root_hash) {
|
||||
/* If we have an explicit root hash and found the partitions for it, then we are ready to use
|
||||
* Verity, set things up for it */
|
||||
@ -1097,9 +1094,9 @@ int dissect_image(
|
||||
}
|
||||
|
||||
if (m->verity_ready)
|
||||
m->verity_sig_ready = verity->root_hash_sig;
|
||||
m->verity_sig_ready = verity->root_hash_sig || have_verity_sig_partition;
|
||||
|
||||
} else if (m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR_VERITY_SIG : PARTITION_ROOT_VERITY_SIG].found) {
|
||||
} else if (have_verity_sig_partition) {
|
||||
|
||||
/* If we found an embedded signature partition, we are ready, too. */
|
||||
|
||||
|
@ -195,3 +195,22 @@ int string_hashsum(
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_free_ uint8_t *der = NULL;
|
||||
int dersz;
|
||||
|
||||
assert(cert);
|
||||
|
||||
dersz = i2d_X509(cert, &der);
|
||||
if (dersz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert PEM certificate to DER format: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
sha256_direct(der, dersz, buffer);
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot calculate X509 fingerprint: %m");
|
||||
#endif
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "macro.h"
|
||||
#include "sha256.h"
|
||||
|
||||
#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
# include <openssl/bio.h>
|
||||
@ -20,10 +23,8 @@
|
||||
# include <openssl/core_names.h>
|
||||
# endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_POINT*, EC_POINT_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_GROUP*, EC_GROUP_free, NULL);
|
||||
@ -50,8 +51,28 @@ int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size)
|
||||
|
||||
int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size);
|
||||
|
||||
#else
|
||||
|
||||
typedef struct X509 X509;
|
||||
typedef struct EVP_PKEY EVP_PKEY;
|
||||
|
||||
static inline void *X509_free(X509 *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void *EVP_PKEY_free(EVP_PKEY *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL);
|
||||
|
||||
int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]);
|
||||
|
||||
#if PREFER_OPENSSL
|
||||
/* The openssl definition */
|
||||
typedef const EVP_MD* hash_md_t;
|
||||
|
@ -10,6 +10,9 @@ TEST_DESCRIPTION="test systemd-repart"
|
||||
test_append_files() {
|
||||
if ! get_bool "${TEST_NO_QEMU:=}"; then
|
||||
install_dmevent
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
inst_binary openssl
|
||||
fi
|
||||
instmods dm_verity =md
|
||||
generate_module_dependencies
|
||||
fi
|
||||
|
@ -1267,6 +1267,15 @@ install_missing_libraries() {
|
||||
continue
|
||||
fi
|
||||
done
|
||||
|
||||
# Install extra openssl 3 stuff
|
||||
path="$(pkg-config --variable=libdir libcrypto)"
|
||||
inst_simple "${path}/ossl-modules/legacy.so" || true
|
||||
inst_simple "${path}/ossl-modules/fips.so" || true
|
||||
inst_simple "${path}/engines-3/afalg.so" || true
|
||||
inst_simple "${path}/engines-3/capi.so" || true
|
||||
inst_simple "${path}/engines-3/loader_attic.so" || true
|
||||
inst_simple "${path}/engines-3/padlock.so" || true
|
||||
}
|
||||
|
||||
cleanup_loopdev() {
|
||||
|
@ -214,8 +214,11 @@ losetup -d "${loop}"
|
||||
ROOT_UUID="$(systemd-id128 -u show "$(head -c 32 "${image}.roothash")" -u | tail -n 1 | cut -b 6-)"
|
||||
VERITY_UUID="$(systemd-id128 -u show "$(tail -c 32 "${image}.roothash")" -u | tail -n 1 | cut -b 6-)"
|
||||
|
||||
systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root","partition_uuid":"'"$ROOT_UUID"'","partition_label":"Root Partition","fstype":"squashfs","architecture":"'"$architecture"'","verity":"yes",'
|
||||
systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root","partition_uuid":"'"$ROOT_UUID"'","partition_label":"Root Partition","fstype":"squashfs","architecture":"'"$architecture"'","verity":"signed",'
|
||||
systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root-verity","partition_uuid":"'"$VERITY_UUID"'","partition_label":"Verity Partition","fstype":"DM_verity_hash","architecture":"'"$architecture"'","verity":null,'
|
||||
if [ "${HAVE_OPENSSL}" -eq 1 ]; then
|
||||
systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q -E '{"rw":"ro","designator":"root-verity-sig","partition_uuid":"'".*"'","partition_label":"Signature Partition","fstype":"verity_hash_signature","architecture":"'"$architecture"'","verity":null,'
|
||||
fi
|
||||
systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1"
|
||||
systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release")
|
||||
|
||||
|
@ -711,7 +711,7 @@ test_verity() {
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -rf '$defs' '$imgs'" RETURN
|
||||
|
||||
cat >"$defs/root.conf" <<EOF
|
||||
cat >"$defs/verity-data.conf" <<EOF
|
||||
[Partition]
|
||||
Type=root-${architecture}
|
||||
CopyFiles=${defs}
|
||||
@ -719,24 +719,54 @@ Verity=data
|
||||
VerityMatchKey=root
|
||||
EOF
|
||||
|
||||
cat >"$defs/verity.conf" <<EOF
|
||||
cat >"$defs/verity-hash.conf" <<EOF
|
||||
[Partition]
|
||||
Type=root-${architecture}-verity
|
||||
Verity=hash
|
||||
VerityMatchKey=root
|
||||
EOF
|
||||
|
||||
cat >"$defs/verity-sig.conf" <<EOF
|
||||
[Partition]
|
||||
Type=root-${architecture}-verity-sig
|
||||
Verity=signature
|
||||
VerityMatchKey=root
|
||||
EOF
|
||||
|
||||
# Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents
|
||||
cat >> "$defs/verity.openssl.cnf" <<EOF
|
||||
[ req ]
|
||||
prompt = no
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = DE
|
||||
ST = Test State
|
||||
L = Test Locality
|
||||
O = Org Name
|
||||
OU = Org Unit Name
|
||||
CN = Common Name
|
||||
emailAddress = test@email.com
|
||||
EOF
|
||||
|
||||
openssl req -config "$defs/verity.openssl.cnf" -new -x509 -newkey rsa:1024 -keyout "$defs/verity.key" -out "$defs/verity.crt" -days 365 -nodes
|
||||
|
||||
mkdir -p /run/verity.d
|
||||
ln -s "$defs/verity.crt" /run/verity.d/ok.crt
|
||||
|
||||
output=$(systemd-repart --definitions="$defs" \
|
||||
--seed="$seed" \
|
||||
--dry-run=no \
|
||||
--empty=create \
|
||||
--size=auto \
|
||||
--json=pretty \
|
||||
--private-key="$defs/verity.key" \
|
||||
--certificate="$defs/verity.crt" \
|
||||
"$imgs/verity")
|
||||
|
||||
roothash=$(jq -r ".[] | select(.type == \"root-${architecture}-verity\") | .roothash" <<< "$output")
|
||||
|
||||
# Check that we can dissect, mount and unmount a repart verity image.
|
||||
# Check that we can dissect, mount and unmount a repart verity image.
|
||||
|
||||
systemd-dissect "$imgs/verity" --root-hash "$roothash"
|
||||
systemd-dissect "$imgs/verity" --root-hash "$roothash" -M "$imgs/mnt"
|
||||
|
Loading…
Reference in New Issue
Block a user