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.c357
1 files changed, 330 insertions, 27 deletions
diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c
index b0a5563..2ab89c7 100644
--- a/src/shared/openssl-util.c
+++ b/src/shared/openssl-util.c
@@ -1,14 +1,28 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <endian.h>
+
#include "alloc-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
+#include "memory-util.h"
#include "openssl-util.h"
+#include "random-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
+# include <openssl/rsa.h>
+# include <openssl/ec.h>
+
+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+# include <openssl/engine.h>
+DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
+REENABLE_WARNING;
+# endif
+
+/* For each error in 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, ...) \
@@ -523,7 +537,6 @@ int rsa_encrypt_bytes(
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
-
return 0;
}
@@ -624,48 +637,55 @@ int rsa_pkey_to_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(). */
+/* Generate RSA public key from provided "n" and "e" values. Numbers "n" and "e" must be provided here
+ * in big-endian format, e.g. wrap it with htobe32() for uint32_t. */
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(n_size != 0);
assert(e);
+ assert(e_size != 0);
assert(ret);
- _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "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");
+ OSSL_PARAM params[3];
- 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 __BYTE_ORDER == __BIG_ENDIAN
+ params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size);
+ params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size);
+#else
+ _cleanup_free_ void *native_n = memdup_reverse(n, n_size);
+ if (!native_n)
+ return log_oom_debug();
- 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_free_ void *native_e = memdup_reverse(e, e_size);
+ if (!native_e)
+ return log_oom_debug();
- _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");
+ params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size);
+ params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size);
+#endif
+ params[2] = OSSL_PARAM_construct_end();
if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
return log_openssl_errors("Failed to create RSA EVP_PKEY");
#else
+ _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");
+
_cleanup_(RSA_freep) RSA *rsa_key = RSA_new();
if (!rsa_key)
return log_openssl_errors("Failed to create new RSA");
@@ -989,7 +1009,7 @@ int ecc_ecdh(const EVP_PKEY *private_pkey,
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);
+ _cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size);
if (!shared_secret)
return log_oom_debug();
@@ -1128,6 +1148,258 @@ int string_hashsum(
return 0;
}
# endif
+
+static int ecc_pkey_generate_volume_keys(
+ EVP_PKEY *pkey,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ void **ret_saved_key,
+ size_t *ret_saved_key_size) {
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_free_ unsigned char *saved_key = NULL;
+ size_t decrypted_key_size, saved_key_size;
+ int nid = NID_undef;
+ int r;
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_free_ char *curve_name = NULL;
+ size_t len = 0;
+
+ if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0)
+ return log_openssl_errors("Failed to determine PKEY group name length");
+
+ len++;
+ curve_name = new(char, len);
+ if (!curve_name)
+ return log_oom_debug();
+
+ if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1)
+ return log_openssl_errors("Failed to get PKEY group name");
+
+ nid = OBJ_sn2nid(curve_name);
+#else
+ EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey);
+ if (!ec_key)
+ return log_openssl_errors("PKEY doesn't have EC_KEY associated");
+
+ if (EC_KEY_check_key(ec_key) != 1)
+ return log_openssl_errors("EC_KEY associated with PKEY is not valid");
+
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key));
+#endif
+
+ r = ecc_pkey_new(nid, &pkey_new);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate a new EC keypair: %m");
+
+ r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to derive shared secret: %m");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points.
+ See https://github.com/openssl/openssl/discussions/22835 */
+ saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key);
+ if (saved_key_size == 0)
+ return log_openssl_errors("Failed to convert the generated public key to SEC1 format");
+#else
+ EC_KEY *ec_key_new = EVP_PKEY_get0_EC_KEY(pkey_new);
+ if (!ec_key_new)
+ return log_openssl_errors("The generated key doesn't have associated EC_KEY");
+
+ if (EC_KEY_check_key(ec_key_new) != 1)
+ return log_openssl_errors("EC_KEY associated with the generated key is not valid");
+
+ saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new),
+ EC_KEY_get0_public_key(ec_key_new),
+ POINT_CONVERSION_UNCOMPRESSED,
+ NULL, 0, NULL);
+ if (saved_key_size == 0)
+ return log_openssl_errors("Failed to determine size of the generated public key");
+
+ saved_key = malloc(saved_key_size);
+ if (!saved_key)
+ return log_oom_debug();
+
+ saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new),
+ EC_KEY_get0_public_key(ec_key_new),
+ POINT_CONVERSION_UNCOMPRESSED,
+ saved_key, saved_key_size, NULL);
+ if (saved_key_size == 0)
+ return log_openssl_errors("Failed to convert the generated public key to SEC1 format");
+#endif
+
+ *ret_decrypted_key = TAKE_PTR(decrypted_key);
+ *ret_decrypted_key_size = decrypted_key_size;
+ *ret_saved_key = TAKE_PTR(saved_key);
+ *ret_saved_key_size = saved_key_size;
+ return 0;
+}
+
+static int rsa_pkey_generate_volume_keys(
+ EVP_PKEY *pkey,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ void **ret_saved_key,
+ size_t *ret_saved_key_size) {
+
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_free_ void *saved_key = NULL;
+ size_t decrypted_key_size, saved_key_size;
+ int r;
+
+ r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to determine RSA public key size.");
+
+ log_debug("Generating %zu bytes random key.", decrypted_key_size);
+
+ decrypted_key = malloc(decrypted_key_size);
+ if (!decrypted_key)
+ return log_oom_debug();
+
+ r = crypto_random_bytes(decrypted_key, decrypted_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate random key: %m");
+
+ r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &saved_key, &saved_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to encrypt random key: %m");
+
+ *ret_decrypted_key = TAKE_PTR(decrypted_key);
+ *ret_decrypted_key_size = decrypted_key_size;
+ *ret_saved_key = TAKE_PTR(saved_key);
+ *ret_saved_key_size = saved_key_size;
+ return 0;
+}
+
+int pkey_generate_volume_keys(
+ EVP_PKEY *pkey,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ void **ret_saved_key,
+ size_t *ret_saved_key_size) {
+
+ assert(pkey);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+ assert(ret_saved_key);
+ assert(ret_saved_key_size);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ int type = EVP_PKEY_get_base_id(pkey);
+#else
+ int type = EVP_PKEY_base_id(pkey);
+#endif
+ switch (type) {
+
+ case EVP_PKEY_RSA:
+ return rsa_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
+
+ case EVP_PKEY_EC:
+ return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
+
+ case NID_undef:
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key.");
+
+ default:
+ 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);
+
+#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+ 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;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+int openssl_load_key_from_token(
+ KeySourceType private_key_source_type,
+ const char *private_key_source,
+ const char *private_key,
+ EVP_PKEY **ret) {
+
+ assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER));
+ assert(private_key_source);
+ assert(private_key);
+
+ switch (private_key_source_type) {
+
+ case OPENSSL_KEY_SOURCE_ENGINE:
+ return load_key_from_engine(private_key_source, private_key, ret);
+ case OPENSSL_KEY_SOURCE_PROVIDER:
+ return load_key_from_provider(private_key_source, private_key, ret);
+ default:
+ assert_not_reached();
+ }
+}
#endif
int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
@@ -1144,6 +1416,37 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
sha256_direct(der, dersz, buffer);
return 0;
#else
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint: %m");
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint.");
#endif
}
+
+int parse_openssl_key_source_argument(
+ const char *argument,
+ char **private_key_source,
+ KeySourceType *private_key_source_type) {
+
+ KeySourceType type;
+ const char *e = NULL;
+ int r;
+
+ assert(argument);
+ assert(private_key_source);
+ assert(private_key_source_type);
+
+ if (streq(argument, "file"))
+ type = OPENSSL_KEY_SOURCE_FILE;
+ else if ((e = startswith(argument, "engine:")))
+ type = OPENSSL_KEY_SOURCE_ENGINE;
+ else if ((e = startswith(argument, "provider:")))
+ type = OPENSSL_KEY_SOURCE_PROVIDER;
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument);
+
+ r = free_and_strdup_warn(private_key_source, e);
+ if (r < 0)
+ return r;
+
+ *private_key_source_type = type;
+
+ return 0;
+}