Merge pull request #31261 from bluca/repart_engine

repart: support engines and providers
This commit is contained in:
Luca Boccassi 2024-02-09 21:28:59 +00:00 committed by GitHub
commit 769ca9ab55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 195 additions and 12 deletions

View file

@ -129,6 +129,14 @@ All tools:
* `$SYSTEMD_VERITY_SHARING=0` — if set, sharing dm-verity devices by
using a stable `<ROOTHASH>-verity` device mapper name will be disabled.
* `$SYSTEMD_OPENSSL_KEY_LOADER`— when using OpenSSL to load a key via an engine
or a provider, can be used to force the usage of one or the other interface.
Set to 'engine' to force the usage of the old engine API, and to 'provider'
force the usage of the new provider API. If unset, the provider will be tried
first and the engine as a fallback if that fails. Providers are the new OpenSSL
3 API, but there are very few if any in a production-ready state, so engines
are still needed.
`systemctl`:
* `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID 1's private D-Bus

View file

@ -354,6 +354,16 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--private-key-uri=</option></term>
<listitem><para>Takes a URI-like string referring to a private key, that will be passed to OpenSSL's
"engine" or "provider" logic. Configures the signing key to use when creating verity signature
partitions with the <varname>Verity=signature</varname> setting in partition files.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--certificate=</option></term>
@ -616,6 +626,27 @@ systemd-confext refresh</programlisting>
<citerefentry><refentrytitle>systemd-confext</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
</example>
<example>
<title>Generate a system extension image and sign it via PKCS11</title>
<para>The following creates a system extension DDI (sysext) for an
<filename>/usr/foo</filename> update and signs it with a hardware token via PKCS11.</para>
<programlisting>mkdir tree tree/usr tree/usr/lib/extension-release.d
echo "Hello World" > tree/usr/foo
cat > tree/usr/lib/extension-release.d/extension-release.my-foo &lt;&lt;EOF
ID=fedora
VERSION_ID=38
IMAGE_ID=my-foo
IMAGE_VERSION=7
EOF
systemd-repart --make-ddi=sysext --private-key-uri="pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=0123456789abcdef;token=Some%20Cert" --certificate=cert.crt -s tree/ /var/lib/extensions/my-foo.sysext.raw
systemd-sysext refresh</programlisting>
<para>The DDI generated that way may be applied to the system with
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
</example>
</refsect1>
<refsect1>

View file

@ -6506,6 +6506,7 @@ static int help(void) {
}
static int parse_argv(int argc, char *argv[]) {
_cleanup_free_ char *private_key = NULL, *private_key_uri = NULL;
enum {
ARG_VERSION = 0x100,
@ -6526,6 +6527,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_JSON,
ARG_KEY_FILE,
ARG_PRIVATE_KEY,
ARG_PRIVATE_KEY_URI,
ARG_CERTIFICATE,
ARG_TPM2_DEVICE,
ARG_TPM2_DEVICE_KEY,
@ -6566,6 +6568,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "json", required_argument, NULL, ARG_JSON },
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
{ "private-key-uri", required_argument, NULL, ARG_PRIVATE_KEY_URI },
{ "certificate", required_argument, NULL, ARG_CERTIFICATE },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY },
@ -6751,20 +6754,14 @@ static int parse_argv(int argc, char *argv[]) {
}
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);
r = free_and_strdup_warn(&private_key, optarg);
if (r < 0)
return log_error_errno(r, "Failed to read key file '%s': %m", optarg);
return r;
break;
}
EVP_PKEY_free(arg_private_key);
arg_private_key = NULL;
r = parse_private_key(k, n, &arg_private_key);
case ARG_PRIVATE_KEY_URI: {
r = free_and_strdup_warn(&private_key_uri, optarg);
if (r < 0)
return r;
break;
@ -7101,6 +7098,38 @@ static int parse_argv(int argc, char *argv[]) {
*p = gpt_partition_type_override_architecture(*p, arg_architecture);
}
if (private_key && private_key_uri)
return log_error_errno(
SYNTHETIC_ERRNO(EINVAL),
"Cannot specify both --private-key= and --private-key-uri=.");
if (private_key) {
_cleanup_(erase_and_freep) char *k = NULL;
size_t n = 0;
r = read_full_file_full(
AT_FDCWD, private_key, 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", private_key);
r = parse_private_key(k, n, &arg_private_key);
if (r < 0)
return r;
} else if (private_key_uri) {
/* This must happen after parse_x509_certificate() is called above, otherwise
* signing later will get stuck as the parsed private key won't have the
* certificate, so this block cannot be inline in ARG_PRIVATE_KEY. */
r = openssl_load_key_from_token(private_key_uri, &arg_private_key);
if (r < 0)
return log_error_errno(
r,
"Failed to load key '%s' from OpenSSL provider: %m",
private_key);
}
return 1;
}

View file

@ -1298,6 +1298,107 @@ int pkey_generate_volume_keys(
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type));
}
}
static int load_key_from_provider(const char *provider, const char *private_key_uri, EVP_PKEY **ret) {
assert(provider);
assert(private_key_uri);
assert(ret);
#if OPENSSL_VERSION_MAJOR >= 3
/* Load the provider so that this can work without any custom written configuration in /etc/.
* Also load the 'default' as that seems to be the recommendation. */
if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true))
return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider);
if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true))
return log_openssl_errors("Failed to load OpenSSL provider 'default'");
_cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open(
private_key_uri,
/* ui_method= */ NULL,
/* ui_data= */ NULL,
/* post_process= */ NULL,
/* post_process_data= */ NULL);
if (!store)
return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri);
_cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store);
if (!info)
return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri);
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = OSSL_STORE_INFO_get1_PKEY(info);
if (!private_key)
return log_openssl_errors("Failed to load private key via '%s'", private_key_uri);
*ret = TAKE_PTR(private_key);
return 0;
#else
return -EOPNOTSUPP;
#endif
}
static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) {
assert(engine);
assert(private_key_uri);
assert(ret);
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
_cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine);
if (!e)
return log_openssl_errors("Failed to load signing engine '%s'", engine);
if (ENGINE_init(e) == 0)
return log_openssl_errors("Failed to initialize signing engine '%s'", engine);
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(
e,
private_key_uri,
/* ui_method= */ NULL,
/* callback_data= */ NULL);
if (!private_key)
return log_openssl_errors("Failed to load private key from '%s'", private_key_uri);
REENABLE_WARNING;
*ret = TAKE_PTR(private_key);
return 0;
}
int openssl_load_key_from_token(const char *private_key_uri, EVP_PKEY **ret) {
_cleanup_free_ char *provider = NULL;
const char *colon, *e;
int r;
assert(private_key_uri);
colon = strchr(private_key_uri, ':');
if (!colon)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URI '%s'", private_key_uri);
provider = strndup(private_key_uri, colon - private_key_uri);
if (!provider)
return log_oom_debug();
e = secure_getenv("SYSTEMD_OPENSSL_KEY_LOADER");
if (e) {
if (streq(e, "provider"))
r = load_key_from_provider(provider, private_key_uri, ret);
else if (streq(e, "engine"))
r = load_key_from_engine(provider, private_key_uri, ret);
else
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for SYSTEMD_OPENSSL_KEY_LOADER: %s", e);
} else {
r = load_key_from_provider(provider, private_key_uri, ret);
if (r < 0) {
log_debug_errno(r, "Failed to load key from provider '%s', falling back to engine", provider);
r = load_key_from_engine(provider, private_key_uri, ret);
}
}
return r;
}
#endif
int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {

View file

@ -11,6 +11,7 @@
# include <openssl/bio.h>
# include <openssl/bn.h>
# include <openssl/crypto.h>
# include <openssl/engine.h>
# include <openssl/err.h>
# include <openssl/evp.h>
# include <openssl/opensslv.h>
@ -25,6 +26,8 @@
# include <openssl/core_names.h>
# include <openssl/kdf.h>
# include <openssl/param_build.h>
# include <openssl/provider.h>
# include <openssl/store.h>
# endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL);
@ -41,6 +44,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL);
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
REENABLE_WARNING;
#if OPENSSL_VERSION_MAJOR >= 3
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL);
@ -50,6 +56,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_CTX*, OSSL_STORE_close, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_INFO*, OSSL_STORE_INFO_free, NULL);
#else
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(HMAC_CTX*, HMAC_CTX_free, NULL);
@ -115,6 +123,8 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
int openssl_load_key_from_token(const char *private_key_uri, EVP_PKEY **ret);
#else
typedef struct X509 X509;
@ -130,6 +140,10 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) {
return NULL;
}
static inline int openssl_load_key_from_token(const char *private_key_uri, EVP_PKEY **ret) {
return -EOPNOTSUPP;
}
#endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);