summaryrefslogtreecommitdiffstats
path: root/src/shared/openssl-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/openssl-util.c')
-rw-r--r--src/shared/openssl-util.c1149
1 files changed, 1149 insertions, 0 deletions
diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c
new file mode 100644
index 0000000..b0a5563
--- /dev/null
+++ b/src/shared/openssl-util.c
@@ -0,0 +1,1149 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "openssl-util.h"
+#include "string-util.h"
+
+#if HAVE_OPENSSL
+/* For each error in the the OpenSSL thread error queue, log the provided message and the OpenSSL error
+ * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No openssl
+ * errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
+#define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__)
+#define _log_openssl_errors(u, fmt, ...) \
+ ({ \
+ size_t UNIQ_T(MAX, u) = 512 /* arbitrary, but openssl doc states it must be >= 256 */; \
+ _cleanup_free_ char *UNIQ_T(BUF, u) = malloc(UNIQ_T(MAX, u)); \
+ !UNIQ_T(BUF, u) \
+ ? log_oom_debug() \
+ : __log_openssl_errors(u, UNIQ_T(BUF, u), UNIQ_T(MAX, u), fmt, ##__VA_ARGS__) \
+ ?: log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": No OpenSSL errors.", ##__VA_ARGS__); \
+ })
+#define __log_openssl_errors(u, buf, max, fmt, ...) \
+ ({ \
+ int UNIQ_T(R, u) = 0; \
+ for (;;) { \
+ unsigned long UNIQ_T(E, u) = ERR_get_error(); \
+ if (UNIQ_T(E, u) == 0) \
+ break; \
+ ERR_error_string_n(UNIQ_T(E, u), buf, max); \
+ UNIQ_T(R, u) = log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": %s", ##__VA_ARGS__, buf); \
+ } \
+ UNIQ_T(R, u); \
+ })
+
+int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) {
+ assert(pem);
+ assert(ret);
+
+ _cleanup_fclose_ FILE *f = NULL;
+ f = fmemopen((void*) pem, pem_size, "r");
+ if (!f)
+ return log_oom_debug();
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL);
+ if (!pkey)
+ return log_openssl_errors("Failed to parse PEM");
+
+ *ret = TAKE_PTR(pkey);
+
+ return 0;
+}
+
+/* Returns the number of bytes generated by the specified digest algorithm. This can be used only for
+ * fixed-size algorithms, e.g. md5, sha1, sha256, etc. Do not use this for variable-sized digest algorithms,
+ * e.g. shake128. Returns 0 on success, -EOPNOTSUPP if the algorithm is not supported, or < 0 for any other
+ * error. */
+int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size) {
+ assert(digest_alg);
+ assert(ret_digest_size);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL);
+#else
+ const EVP_MD *md = EVP_get_digestbyname(digest_alg);
+#endif
+ if (!md)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Digest algorithm '%s' not supported.", digest_alg);
+
+ size_t digest_size;
+#if OPENSSL_VERSION_MAJOR >= 3
+ digest_size = EVP_MD_get_size(md);
+#else
+ digest_size = EVP_MD_size(md);
+#endif
+ if (digest_size == 0)
+ return log_openssl_errors("Failed to get Digest size");
+
+ *ret_digest_size = digest_size;
+
+ return 0;
+}
+
+/* Calculate the digest hash value for the provided data, using the specified digest algorithm. Returns 0 on
+ * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */
+int openssl_digest_many(
+ const char *digest_alg,
+ const struct iovec data[],
+ size_t n_data,
+ void **ret_digest,
+ size_t *ret_digest_size) {
+
+ int r;
+
+ assert(digest_alg);
+ assert(data || n_data == 0);
+ assert(ret_digest);
+ /* ret_digest_size is optional, as caller may already know the digest size */
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL);
+#else
+ const EVP_MD *md = EVP_get_digestbyname(digest_alg);
+#endif
+ if (!md)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Digest algorithm '%s' not supported.", digest_alg);
+
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_MD_CTX");
+
+ if (!EVP_DigestInit_ex(ctx, md, NULL))
+ return log_openssl_errors("Failed to initialize EVP_MD_CTX");
+
+ for (size_t i = 0; i < n_data; i++)
+ if (!EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len))
+ return log_openssl_errors("Failed to update Digest");
+
+ size_t digest_size;
+ r = openssl_digest_size(digest_alg, &digest_size);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ void *buf = malloc(digest_size);
+ if (!buf)
+ return log_oom_debug();
+
+ unsigned int size;
+ if (!EVP_DigestFinal_ex(ctx, buf, &size))
+ return log_openssl_errors("Failed to finalize Digest");
+
+ assert(size == digest_size);
+
+ *ret_digest = TAKE_PTR(buf);
+ if (ret_digest_size)
+ *ret_digest_size = size;
+
+ return 0;
+}
+
+/* Calculate the HMAC digest hash value for the provided data, using the provided key and specified digest
+ * algorithm. Returns 0 on success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any
+ * other error. */
+int openssl_hmac_many(
+ const char *digest_alg,
+ const void *key,
+ size_t key_size,
+ const struct iovec data[],
+ size_t n_data,
+ void **ret_digest,
+ size_t *ret_digest_size) {
+
+ assert(digest_alg);
+ assert(key);
+ assert(data || n_data == 0);
+ assert(ret_digest);
+ /* ret_digest_size is optional, as caller may already know the digest size */
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL);
+#else
+ const EVP_MD *md = EVP_get_digestbyname(digest_alg);
+#endif
+ if (!md)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Digest algorithm '%s' not supported.", digest_alg);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_MAC_freep) EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+ if (!mac)
+ return log_openssl_errors("Failed to create new EVP_MAC");
+
+ _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_MAC_CTX");
+
+ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
+ if (!bld)
+ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD");
+
+ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0))
+ return log_openssl_errors("Failed to set HMAC OSSL_MAC_PARAM_DIGEST");
+
+ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
+ if (!params)
+ return log_openssl_errors("Failed to build HMAC OSSL_PARAM");
+
+ if (!EVP_MAC_init(ctx, key, key_size, params))
+ return log_openssl_errors("Failed to initialize EVP_MAC_CTX");
+#else
+ _cleanup_(HMAC_CTX_freep) HMAC_CTX *ctx = HMAC_CTX_new();
+ if (!ctx)
+ return log_openssl_errors("Failed to create new HMAC_CTX");
+
+ if (!HMAC_Init_ex(ctx, key, key_size, md, NULL))
+ return log_openssl_errors("Failed to initialize HMAC_CTX");
+#endif
+
+ for (size_t i = 0; i < n_data; i++)
+#if OPENSSL_VERSION_MAJOR >= 3
+ if (!EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len))
+#else
+ if (!HMAC_Update(ctx, data[i].iov_base, data[i].iov_len))
+#endif
+ return log_openssl_errors("Failed to update HMAC");
+
+ size_t digest_size;
+#if OPENSSL_VERSION_MAJOR >= 3
+ digest_size = EVP_MAC_CTX_get_mac_size(ctx);
+#else
+ digest_size = HMAC_size(ctx);
+#endif
+ if (digest_size == 0)
+ return log_openssl_errors("Failed to get HMAC digest size");
+
+ _cleanup_free_ void *buf = malloc(digest_size);
+ if (!buf)
+ return log_oom_debug();
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ size_t size;
+ if (!EVP_MAC_final(ctx, buf, &size, digest_size))
+#else
+ unsigned int size;
+ if (!HMAC_Final(ctx, buf, &size))
+#endif
+ return log_openssl_errors("Failed to finalize HMAC");
+
+ assert(size == digest_size);
+
+ *ret_digest = TAKE_PTR(buf);
+ if (ret_digest_size)
+ *ret_digest_size = size;
+
+ return 0;
+}
+
+/* Symmetric Cipher encryption using the alg-bits-mode cipher, e.g. AES-128-CFB. The key is required and must
+ * be at least the minimum required key length for the cipher. The IV is optional but, if provided, it must
+ * be at least the minimum iv length for the cipher. If no IV is provided and the cipher requires one, a
+ * buffer of zeroes is used. Returns 0 on success, -EOPNOTSUPP if the cipher algorithm is not supported, or <
+ * 0 on any other error. */
+int openssl_cipher_many(
+ const char *alg,
+ size_t bits,
+ const char *mode,
+ const void *key,
+ size_t key_size,
+ const void *iv,
+ size_t iv_size,
+ const struct iovec data[],
+ size_t n_data,
+ void **ret,
+ size_t *ret_size) {
+
+ assert(alg);
+ assert(bits > 0);
+ assert(mode);
+ assert(key);
+ assert(iv || iv_size == 0);
+ assert(data || n_data == 0);
+ assert(ret);
+ assert(ret_size);
+
+ _cleanup_free_ char *cipher_alg = NULL;
+ if (asprintf(&cipher_alg, "%s-%zu-%s", alg, bits, mode) < 0)
+ return log_oom_debug();
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_CIPHER_freep) EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, cipher_alg, NULL);
+#else
+ const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_alg);
+#endif
+ if (!cipher)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Cipher algorithm '%s' not supported.", cipher_alg);
+
+ _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_CIPHER_CTX");
+
+ /* Verify enough key data was provided. */
+ int cipher_key_length = EVP_CIPHER_key_length(cipher);
+ assert(cipher_key_length >= 0);
+ if ((size_t) cipher_key_length > key_size)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Not enough key bytes provided, require %d", cipher_key_length);
+
+ /* Verify enough IV data was provided or, if no IV was provided, use a zeroed buffer for IV data. */
+ int cipher_iv_length = EVP_CIPHER_iv_length(cipher);
+ assert(cipher_iv_length >= 0);
+ _cleanup_free_ void *zero_iv = NULL;
+ if (iv_size == 0) {
+ zero_iv = malloc0(cipher_iv_length);
+ if (!zero_iv)
+ return log_oom_debug();
+
+ iv = zero_iv;
+ iv_size = (size_t) cipher_iv_length;
+ }
+ if ((size_t) cipher_iv_length > iv_size)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Not enough IV bytes provided, require %d", cipher_iv_length);
+
+ if (!EVP_EncryptInit(ctx, cipher, key, iv))
+ return log_openssl_errors("Failed to initialize EVP_CIPHER_CTX.");
+
+ int cipher_block_size = EVP_CIPHER_CTX_block_size(ctx);
+ assert(cipher_block_size > 0);
+
+ _cleanup_free_ uint8_t *buf = NULL;
+ size_t size = 0;
+
+ for (size_t i = 0; i < n_data; i++) {
+ /* Cipher may produce (up to) input length + cipher block size of output. */
+ if (!GREEDY_REALLOC(buf, size + data[i].iov_len + cipher_block_size))
+ return log_oom_debug();
+
+ int update_size;
+ if (!EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len))
+ return log_openssl_errors("Failed to update Cipher.");
+
+ size += update_size;
+ }
+
+ if (!GREEDY_REALLOC(buf, size + cipher_block_size))
+ return log_oom_debug();
+
+ int final_size;
+ if (!EVP_EncryptFinal_ex(ctx, &buf[size], &final_size))
+ return log_openssl_errors("Failed to finalize Cipher.");
+
+ *ret = TAKE_PTR(buf);
+ *ret_size = size + final_size;
+
+ return 0;
+}
+
+/* Perform Single-Step (aka "Concat") KDF. Currently, this only supports using the digest for the auxiliary
+ * function. The derive_size parameter specifies how many bytes are derived.
+ *
+ * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-SS.html */
+int kdf_ss_derive(
+ const char *digest,
+ const void *key,
+ size_t key_size,
+ const void *salt,
+ size_t salt_size,
+ const void *info,
+ size_t info_size,
+ size_t derive_size,
+ void **ret) {
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ assert(digest);
+ assert(key);
+ assert(derive_size > 0);
+ assert(ret);
+
+ _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSKDF", NULL);
+ if (!kdf)
+ return log_openssl_errors("Failed to create new EVP_KDF");
+
+ _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_KDF_CTX");
+
+ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
+ if (!bld)
+ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD");
+
+ _cleanup_free_ void *buf = malloc(derive_size);
+ if (!buf)
+ return log_oom_debug();
+
+ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0))
+ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_DIGEST");
+
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size))
+ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_KEY");
+
+ if (salt)
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size))
+ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_SALT");
+
+ if (info)
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size))
+ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_INFO");
+
+ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
+ if (!params)
+ return log_openssl_errors("Failed to build KDF-SS OSSL_PARAM");
+
+ if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0)
+ return log_openssl_errors("OpenSSL KDF-SS derive failed");
+
+ *ret = TAKE_PTR(buf);
+
+ return 0;
+#else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "KDF-SS requires OpenSSL >= 3.");
+#endif
+}
+
+/* Perform Key-Based HMAC KDF. The mode must be "COUNTER" or "FEEDBACK". The parameter naming is from the
+ * OpenSSL api, and maps to SP800-108 naming as "...key, salt, info, and seed correspond to KI, Label,
+ * Context, and IV (respectively)...". The derive_size parameter specifies how many bytes are derived.
+ *
+ * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-KB.html */
+int kdf_kb_hmac_derive(
+ const char *mode,
+ const char *digest,
+ const void *key,
+ size_t key_size,
+ const void *salt,
+ size_t salt_size,
+ const void *info,
+ size_t info_size,
+ const void *seed,
+ size_t seed_size,
+ size_t derive_size,
+ void **ret) {
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ assert(mode);
+ assert(strcaseeq(mode, "COUNTER") || strcaseeq(mode, "FEEDBACK"));
+ assert(digest);
+ assert(key || key_size == 0);
+ assert(salt || salt_size == 0);
+ assert(info || info_size == 0);
+ assert(seed || seed_size == 0);
+ assert(derive_size > 0);
+ assert(ret);
+
+ _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL);
+ if (!kdf)
+ return log_openssl_errors("Failed to create new EVP_KDF");
+
+ _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_KDF_CTX");
+
+ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
+ if (!bld)
+ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD");
+
+ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MAC");
+
+ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MODE");
+
+ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_DIGEST");
+
+ if (key)
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_KEY");
+
+ if (salt)
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SALT");
+
+ if (info)
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_INFO");
+
+ if (seed)
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size))
+ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SEED");
+
+ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
+ if (!params)
+ return log_openssl_errors("Failed to build KDF-KB OSSL_PARAM");
+
+ _cleanup_free_ void *buf = malloc(derive_size);
+ if (!buf)
+ return log_oom_debug();
+
+ if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0)
+ return log_openssl_errors("OpenSSL KDF-KB derive failed");
+
+ *ret = TAKE_PTR(buf);
+
+ return 0;
+#else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "KDF-KB requires OpenSSL >= 3.");
+#endif
+}
+
+int rsa_encrypt_bytes(
+ EVP_PKEY *pkey,
+ const void *decrypted_key,
+ size_t decrypted_key_size,
+ void **ret_encrypt_key,
+ size_t *ret_encrypt_key_size) {
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
+ _cleanup_free_ void *b = NULL;
+ size_t l;
+
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to allocate public key context");
+
+ if (EVP_PKEY_encrypt_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize public key context");
+
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
+ return log_openssl_errors("Failed to configure PKCS#1 padding");
+
+ if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
+ return log_openssl_errors("Failed to determine encrypted key size");
+
+ b = malloc(l);
+ if (!b)
+ return -ENOMEM;
+
+ if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
+ return log_openssl_errors("Failed to determine encrypted key size");
+
+ *ret_encrypt_key = TAKE_PTR(b);
+ *ret_encrypt_key_size = l;
+
+ return 0;
+}
+
+/* Encrypt the key data using RSA-OAEP with the provided label and specified digest algorithm. Returns 0 on
+ * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */
+int rsa_oaep_encrypt_bytes(
+ const EVP_PKEY *pkey,
+ const char *digest_alg,
+ const char *label,
+ const void *decrypted_key,
+ size_t decrypted_key_size,
+ void **ret_encrypt_key,
+ size_t *ret_encrypt_key_size) {
+
+ assert(pkey);
+ assert(digest_alg);
+ assert(label);
+ assert(decrypted_key);
+ assert(decrypted_key_size > 0);
+ assert(ret_encrypt_key);
+ assert(ret_encrypt_key_size);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL);
+#else
+ const EVP_MD *md = EVP_get_digestbyname(digest_alg);
+#endif
+ if (!md)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Digest algorithm '%s' not supported.", digest_alg);
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
+
+ if (EVP_PKEY_encrypt_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
+
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0)
+ return log_openssl_errors("Failed to configure RSA-OAEP padding");
+
+ if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0)
+ return log_openssl_errors("Failed to configure RSA-OAEP MD");
+
+ _cleanup_free_ char *duplabel = strdup(label);
+ if (!duplabel)
+ return log_oom_debug();
+
+ if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0)
+ return log_openssl_errors("Failed to configure RSA-OAEP label");
+ /* ctx owns this now, don't free */
+ TAKE_PTR(duplabel);
+
+ size_t size = 0;
+ if (EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0)
+ return log_openssl_errors("Failed to determine RSA-OAEP encrypted key size");
+
+ _cleanup_free_ void *buf = malloc(size);
+ if (!buf)
+ return log_oom_debug();
+
+ if (EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0)
+ return log_openssl_errors("Failed to RSA-OAEP encrypt");
+
+ *ret_encrypt_key = TAKE_PTR(buf);
+ *ret_encrypt_key_size = size;
+
+ return 0;
+}
+
+int rsa_pkey_to_suitable_key_size(
+ EVP_PKEY *pkey,
+ size_t *ret_suitable_key_size) {
+
+ size_t suitable_key_size;
+ int bits;
+
+ assert(pkey);
+ assert(ret_suitable_key_size);
+
+ /* Analyzes the specified public key and that it is RSA. If so, will return a suitable size for a
+ * disk encryption key to encrypt with RSA for use in PKCS#11 security token schemes. */
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
+
+ bits = EVP_PKEY_bits(pkey);
+ log_debug("Bits in RSA key: %i", bits);
+
+ /* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
+ * generate a random key half the size of the RSA length */
+ suitable_key_size = bits / 8 / 2;
+
+ if (suitable_key_size < 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
+
+ *ret_suitable_key_size = suitable_key_size;
+ return 0;
+}
+
+/* Generate RSA public key from provided "n" and "e" values. Note that if "e" is a number (e.g. uint32_t), it
+ * must be provided here big-endian, e.g. wrap it with htobe32(). */
+int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) {
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+
+ assert(n);
+ assert(e);
+ assert(ret);
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
+
+ _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL);
+ if (!bn_n)
+ return log_openssl_errors("Failed to create BIGNUM for RSA n");
+
+ _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL);
+ if (!bn_e)
+ return log_openssl_errors("Failed to create BIGNUM for RSA e");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ if (EVP_PKEY_fromdata_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
+
+ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
+ if (!bld)
+ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD");
+
+ if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n))
+ return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_N");
+
+ if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e))
+ return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_E");
+
+ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
+ if (!params)
+ return log_openssl_errors("Failed to build RSA OSSL_PARAM");
+
+ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
+ return log_openssl_errors("Failed to create RSA EVP_PKEY");
+#else
+ _cleanup_(RSA_freep) RSA *rsa_key = RSA_new();
+ if (!rsa_key)
+ return log_openssl_errors("Failed to create new RSA");
+
+ if (!RSA_set0_key(rsa_key, bn_n, bn_e, NULL))
+ return log_openssl_errors("Failed to set RSA n/e");
+ /* rsa_key owns these now, don't free */
+ TAKE_PTR(bn_n);
+ TAKE_PTR(bn_e);
+
+ pkey = EVP_PKEY_new();
+ if (!pkey)
+ return log_openssl_errors("Failed to create new EVP_PKEY");
+
+ if (!EVP_PKEY_assign_RSA(pkey, rsa_key))
+ return log_openssl_errors("Failed to assign RSA key");
+ /* pkey owns this now, don't free */
+ TAKE_PTR(rsa_key);
+#endif
+
+ *ret = TAKE_PTR(pkey);
+
+ return 0;
+}
+
+/* Get the "n" and "e" values from the pkey. The values are returned in "bin" format, i.e. BN_bn2bin(). */
+int rsa_pkey_to_n_e(
+ const EVP_PKEY *pkey,
+ void **ret_n,
+ size_t *ret_n_size,
+ void **ret_e,
+ size_t *ret_e_size) {
+
+ assert(pkey);
+ assert(ret_n);
+ assert(ret_n_size);
+ assert(ret_e);
+ assert(ret_e_size);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(BN_freep) BIGNUM *bn_n = NULL;
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n))
+ return log_openssl_errors("Failed to get RSA n");
+
+ _cleanup_(BN_freep) BIGNUM *bn_e = NULL;
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e))
+ return log_openssl_errors("Failed to get RSA e");
+#else
+ const RSA *rsa = EVP_PKEY_get0_RSA((EVP_PKEY*) pkey);
+ if (!rsa)
+ return log_openssl_errors("Failed to get RSA key from public key");
+
+ const BIGNUM *bn_n = RSA_get0_n(rsa);
+ if (!bn_n)
+ return log_openssl_errors("Failed to get RSA n");
+
+ const BIGNUM *bn_e = RSA_get0_e(rsa);
+ if (!bn_e)
+ return log_openssl_errors("Failed to get RSA e");
+#endif
+
+ size_t n_size = BN_num_bytes(bn_n), e_size = BN_num_bytes(bn_e);
+ _cleanup_free_ void *n = malloc(n_size), *e = malloc(e_size);
+ if (!n || !e)
+ return log_oom_debug();
+
+ assert(BN_bn2bin(bn_n, n) == (int) n_size);
+ assert(BN_bn2bin(bn_e, e) == (int) e_size);
+
+ *ret_n = TAKE_PTR(n);
+ *ret_n_size = n_size;
+ *ret_e = TAKE_PTR(e);
+ *ret_e_size = e_size;
+
+ return 0;
+}
+
+/* Generate a new RSA key with the specified number of bits. */
+int rsa_pkey_new(size_t bits, EVP_PKEY **ret) {
+ assert(ret);
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
+
+ if (EVP_PKEY_keygen_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
+
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int) bits) <= 0)
+ return log_openssl_errors("Failed to set RSA bits to %zu", bits);
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
+ return log_openssl_errors("Failed to generate ECC key");
+
+ *ret = TAKE_PTR(pkey);
+
+ return 0;
+}
+
+/* Generate ECC public key from provided curve ID and x/y points. */
+int ecc_pkey_from_curve_x_y(
+ int curve_id,
+ const void *x,
+ size_t x_size,
+ const void *y,
+ size_t y_size,
+ EVP_PKEY **ret) {
+
+ assert(x);
+ assert(y);
+ assert(ret);
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
+
+ _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL);
+ if (!bn_x)
+ return log_openssl_errors("Failed to create BIGNUM x");
+
+ _cleanup_(BN_freep) BIGNUM *bn_y = BN_bin2bn(y, y_size, NULL);
+ if (!bn_y)
+ return log_openssl_errors("Failed to create BIGNUM y");
+
+ _cleanup_(EC_GROUP_freep) EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_id);
+ if (!group)
+ return log_openssl_errors("ECC curve id %d not supported", curve_id);
+
+ _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group);
+ if (!point)
+ return log_openssl_errors("Failed to create new EC_POINT");
+
+ if (!EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL))
+ return log_openssl_errors("Failed to set ECC coordinates");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ if (EVP_PKEY_fromdata_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
+
+ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
+ if (!bld)
+ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD");
+
+ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) OSSL_EC_curve_nid2name(curve_id), 0))
+ return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME");
+
+ _cleanup_(OPENSSL_freep) void *pbuf = NULL;
+ size_t pbuf_len = 0;
+ pbuf_len = EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL);
+ if (pbuf_len == 0)
+ return log_openssl_errors("Failed to convert ECC point to buffer");
+
+ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len))
+ return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY");
+
+ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
+ if (!params)
+ return log_openssl_errors("Failed to build ECC OSSL_PARAM");
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
+ return log_openssl_errors("Failed to create ECC EVP_PKEY");
+#else
+ _cleanup_(EC_KEY_freep) EC_KEY *eckey = EC_KEY_new();
+ if (!eckey)
+ return log_openssl_errors("Failed to create new EC_KEY");
+
+ if (!EC_KEY_set_group(eckey, group))
+ return log_openssl_errors("Failed to set ECC group");
+
+ if (!EC_KEY_set_public_key(eckey, point))
+ return log_openssl_errors("Failed to set ECC point");
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = EVP_PKEY_new();
+ if (!pkey)
+ return log_openssl_errors("Failed to create new EVP_PKEY");
+
+ if (!EVP_PKEY_assign_EC_KEY(pkey, eckey))
+ return log_openssl_errors("Failed to assign ECC key");
+ /* pkey owns this now, don't free */
+ TAKE_PTR(eckey);
+#endif
+
+ *ret = TAKE_PTR(pkey);
+
+ return 0;
+}
+
+int ecc_pkey_to_curve_x_y(
+ const EVP_PKEY *pkey,
+ int *ret_curve_id,
+ void **ret_x,
+ size_t *ret_x_size,
+ void **ret_y,
+ size_t *ret_y_size) {
+
+ _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL;
+ int curve_id;
+
+ assert(pkey);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ size_t name_size;
+ if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size))
+ return log_openssl_errors("Failed to get ECC group name size");
+
+ _cleanup_free_ char *name = new(char, name_size + 1);
+ if (!name)
+ return log_oom_debug();
+
+ if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL))
+ return log_openssl_errors("Failed to get ECC group name");
+
+ curve_id = OBJ_sn2nid(name);
+ if (curve_id == NID_undef)
+ return log_openssl_errors("Failed to get ECC curve id");
+
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x))
+ return log_openssl_errors("Failed to get ECC point x");
+
+ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y))
+ return log_openssl_errors("Failed to get ECC point y");
+#else
+ const EC_KEY *eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY*) pkey);
+ if (!eckey)
+ return log_openssl_errors("Failed to get EC_KEY");
+
+ const EC_GROUP *group = EC_KEY_get0_group(eckey);
+ if (!group)
+ return log_openssl_errors("Failed to get EC_GROUP");
+
+ curve_id = EC_GROUP_get_curve_name(group);
+ if (curve_id == NID_undef)
+ return log_openssl_errors("Failed to get ECC curve id");
+
+ const EC_POINT *point = EC_KEY_get0_public_key(eckey);
+ if (!point)
+ return log_openssl_errors("Failed to get EC_POINT");
+
+ bn_x = BN_new();
+ bn_y = BN_new();
+ if (!bn_x || !bn_y)
+ return log_openssl_errors("Failed to create new BIGNUM");
+
+ if (!EC_POINT_get_affine_coordinates(group, point, bn_x, bn_y, NULL))
+ return log_openssl_errors("Failed to get ECC x/y.");
+#endif
+
+ size_t x_size = BN_num_bytes(bn_x), y_size = BN_num_bytes(bn_y);
+ _cleanup_free_ void *x = malloc(x_size), *y = malloc(y_size);
+ if (!x || !y)
+ return log_oom_debug();
+
+ assert(BN_bn2bin(bn_x, x) == (int) x_size);
+ assert(BN_bn2bin(bn_y, y) == (int) y_size);
+
+ if (ret_curve_id)
+ *ret_curve_id = curve_id;
+ if (ret_x)
+ *ret_x = TAKE_PTR(x);
+ if (ret_x_size)
+ *ret_x_size = x_size;
+ if (ret_y)
+ *ret_y = TAKE_PTR(y);
+ if (ret_y_size)
+ *ret_y_size = y_size;
+
+ return 0;
+}
+
+/* Generate a new ECC key for the specified ECC curve id. */
+int ecc_pkey_new(int curve_id, EVP_PKEY **ret) {
+ assert(ret);
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
+
+ if (EVP_PKEY_keygen_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
+
+ if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0)
+ return log_openssl_errors("Failed to set ECC curve %d", curve_id);
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
+ return log_openssl_errors("Failed to generate ECC key");
+
+ *ret = TAKE_PTR(pkey);
+
+ return 0;
+}
+
+/* Perform ECDH to derive an ECC shared secret between the provided private key and public peer key. For two
+ * keys, this will result in the same shared secret in either direction; ECDH using Alice's private key and
+ * Bob's public (peer) key will result in the same shared secret as ECDH using Bob's private key and Alice's
+ * public (peer) key. On success, this returns 0 and provides the shared secret; otherwise this returns an
+ * error. */
+int ecc_ecdh(const EVP_PKEY *private_pkey,
+ const EVP_PKEY *peer_pkey,
+ void **ret_shared_secret,
+ size_t *ret_shared_secret_size) {
+
+ assert(private_pkey);
+ assert(peer_pkey);
+ assert(ret_shared_secret);
+ assert(ret_shared_secret_size);
+
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) private_pkey, NULL);
+ if (!ctx)
+ return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
+
+ if (EVP_PKEY_derive_init(ctx) <= 0)
+ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
+
+ if (EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0)
+ return log_openssl_errors("Failed to set ECC derive peer");
+
+ size_t shared_secret_size;
+ if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0)
+ return log_openssl_errors("Failed to get ECC shared secret size");
+
+ _cleanup_free_ void *shared_secret = malloc(shared_secret_size);
+ if (!shared_secret)
+ return log_oom_debug();
+
+ if (EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0)
+ return log_openssl_errors("Failed to derive ECC shared secret");
+
+ *ret_shared_secret = TAKE_PTR(shared_secret);
+ *ret_shared_secret_size = shared_secret_size;
+
+ return 0;
+}
+
+int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) {
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL;
+ _cleanup_free_ void *d = NULL, *h = NULL;
+ int sz, lsz, msz;
+ unsigned umsz;
+ unsigned char *dd;
+
+ /* Calculates a message digest of the DER encoded public key */
+
+ assert(pk);
+ assert(md);
+ assert(ret);
+ assert(ret_size);
+
+ sz = i2d_PublicKey(pk, NULL);
+ if (sz < 0)
+ return log_openssl_errors("Unable to convert public key to DER format");
+
+ dd = d = malloc(sz);
+ if (!d)
+ return log_oom_debug();
+
+ lsz = i2d_PublicKey(pk, &dd);
+ if (lsz < 0)
+ return log_openssl_errors("Unable to convert public key to DER format");
+
+ m = EVP_MD_CTX_new();
+ if (!m)
+ return log_openssl_errors("Failed to create new EVP_MD_CTX");
+
+ if (EVP_DigestInit_ex(m, md, NULL) != 1)
+ return log_openssl_errors("Failed to initialize %s context", EVP_MD_name(md));
+
+ if (EVP_DigestUpdate(m, d, lsz) != 1)
+ return log_openssl_errors("Failed to run %s context", EVP_MD_name(md));
+
+ msz = EVP_MD_size(md);
+ assert(msz > 0);
+
+ h = malloc(msz);
+ if (!h)
+ return log_oom_debug();
+
+ umsz = msz;
+ if (EVP_DigestFinal_ex(m, h, &umsz) != 1)
+ return log_openssl_errors("Failed to finalize hash context");
+
+ assert(umsz == (unsigned) msz);
+
+ *ret = TAKE_PTR(h);
+ *ret_size = msz;
+
+ return 0;
+}
+
+int digest_and_sign(
+ const EVP_MD *md,
+ EVP_PKEY *privkey,
+ const void *data, size_t size,
+ void **ret, size_t *ret_size) {
+
+ assert(privkey);
+ assert(ret);
+ assert(ret_size);
+
+ if (size == 0)
+ data = ""; /* make sure to pass a valid pointer to OpenSSL */
+ else {
+ assert(data);
+
+ if (size == SIZE_MAX) /* If SIZE_MAX input is a string whose size we determine automatically */
+ size = strlen(data);
+ }
+
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
+ if (!mdctx)
+ return log_openssl_errors("Failed to create new EVP_MD_CTX");
+
+ if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1)
+ return log_openssl_errors("Failed to initialize signature context");
+
+ /* Determine signature size */
+ size_t ss;
+ if (EVP_DigestSign(mdctx, NULL, &ss, data, size) != 1)
+ return log_openssl_errors("Failed to determine size of signature");
+
+ _cleanup_free_ void *sig = malloc(ss);
+ if (!sig)
+ return log_oom_debug();
+
+ if (EVP_DigestSign(mdctx, sig, &ss, data, size) != 1)
+ return log_openssl_errors("Failed to sign data");
+
+ *ret = TAKE_PTR(sig);
+ *ret_size = ss;
+ return 0;
+}
+
+# if PREFER_OPENSSL
+int string_hashsum(
+ const char *s,
+ size_t len,
+ const char *md_algorithm,
+ char **ret) {
+
+ _cleanup_free_ void *hash = NULL;
+ size_t hash_size;
+ _cleanup_free_ char *enc = NULL;
+ int r;
+
+ assert(s || len == 0);
+ assert(md_algorithm);
+ assert(ret);
+
+ r = openssl_digest(md_algorithm, s, len, &hash, &hash_size);
+ if (r < 0)
+ return r;
+
+ enc = hexmem(hash, hash_size);
+ if (!enc)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(enc);
+ return 0;
+}
+# 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_openssl_errors("Unable to convert PEM certificate to DER format");
+
+ 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
+}