mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 16:37:19 +09:00
The pkcs7_new() function currently uses the hash algorithm from the X509 certificate's signature, but the content signature may use a different hash algorithm. This adds a parameter to allow specifying what hash algorithm the content signature generation used.
1820 lines
68 KiB
C
1820 lines
68 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "alloc-util.h"
|
|
#include "ask-password-api.h"
|
|
#include "fd-util.h"
|
|
#include "fileio.h"
|
|
#include "hexdecoct.h"
|
|
#include "log.h"
|
|
#include "memory-util.h"
|
|
#include "memstream-util.h"
|
|
#include "openssl-util.h"
|
|
#include "random-util.h"
|
|
#include "string-util.h"
|
|
#include "strv.h"
|
|
|
|
#if HAVE_OPENSSL
|
|
# include <openssl/ec.h>
|
|
# include <openssl/rsa.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
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
|
|
|
|
/* 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, ...) \
|
|
({ \
|
|
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_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) {
|
|
assert(pem);
|
|
assert(ret);
|
|
|
|
if (pem_size == SIZE_MAX)
|
|
pem_size = strlen(pem);
|
|
|
|
_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, /* x= */ NULL, /* pam_password_cb= */ NULL, /* userdata= */ NULL);
|
|
if (!pkey)
|
|
return log_openssl_errors("Failed to parse PEM");
|
|
|
|
*ret = TAKE_PTR(pkey);
|
|
return 0;
|
|
}
|
|
|
|
int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret) {
|
|
assert(pkey);
|
|
assert(ret);
|
|
|
|
_cleanup_(memstream_done) MemStream m = {};
|
|
FILE *f = memstream_init(&m);
|
|
if (!f)
|
|
return -ENOMEM;
|
|
|
|
if (PEM_write_PUBKEY(f, pkey) <= 0)
|
|
return -EIO;
|
|
|
|
return memstream_finalize(&m, ret, /* ret_size= */ NULL);
|
|
}
|
|
|
|
/* 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 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 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. 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);
|
|
|
|
#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");
|
|
|
|
if (EVP_PKEY_fromdata_init(ctx) <= 0)
|
|
return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
|
|
|
|
OSSL_PARAM params[3];
|
|
|
|
#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();
|
|
|
|
_cleanup_free_ void *native_e = memdup_reverse(e, e_size);
|
|
if (!native_e)
|
|
return log_oom_debug();
|
|
|
|
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");
|
|
|
|
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 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_(erase_and_freep) 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) {
|
|
|
|
int r;
|
|
|
|
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) {
|
|
/* Distro security policies often disable support for SHA-1. Let's return a recognizable
|
|
* error for that case. */
|
|
bool invalid_digest = ERR_GET_REASON(ERR_peek_last_error()) == EVP_R_INVALID_DIGEST;
|
|
r = log_openssl_errors("Failed to initialize signature context");
|
|
return invalid_digest ? -EADDRNOTAVAIL : r;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
|
|
assert(certificate);
|
|
assert(ret_p7);
|
|
|
|
/* This function sets up a new PKCS7 signing context. If a private key is provided, the context is
|
|
* set up for "in-band" signing with PKCS7_dataFinal(). If a private key is not provided, the context
|
|
* is set up for "out-of-band" signing, meaning the signature has to be provided by the user and
|
|
* copied into the signer info's "enc_digest" field. If the signing hash algorithm is not provided,
|
|
* SHA-256 is used. */
|
|
|
|
_cleanup_(PKCS7_freep) PKCS7 *p7 = PKCS7_new();
|
|
if (!p7)
|
|
return log_oom();
|
|
|
|
if (PKCS7_set_type(p7, NID_pkcs7_signed) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 type: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
if (PKCS7_content_new(p7, NID_pkcs7_data) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 content: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
if (PKCS7_add_certificate(p7, certificate) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 certificate: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
int x509_pknid = 0;
|
|
if (X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X509 digest NID: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
const EVP_MD *md = EVP_get_digestbyname(hash_algorithm ?: "SHA256");
|
|
if (!md)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest algorithm '%s'",
|
|
hash_algorithm ?: "SHA256");
|
|
|
|
_cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = PKCS7_SIGNER_INFO_new();
|
|
if (!si)
|
|
return log_oom();
|
|
|
|
if (private_key) {
|
|
if (PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure signer info: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
} else {
|
|
if (ASN1_INTEGER_set(si->version, 1) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info version: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
if (X509_NAME_set(&si->issuer_and_serial->issuer, X509_get_issuer_name(certificate)) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info issuer: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
ASN1_INTEGER_free(si->issuer_and_serial->serial);
|
|
si->issuer_and_serial->serial = ASN1_INTEGER_dup(X509_get0_serialNumber(certificate));
|
|
if (!si->issuer_and_serial->serial)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info serial: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(EVP_MD_type(md)), V_ASN1_NULL, NULL) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info digest algorithm: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
if (X509_ALGOR_set0(si->digest_enc_alg, OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info signing algorithm: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
}
|
|
|
|
if (PKCS7_add_signer(p7, si) == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 signer info: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
*ret_p7 = TAKE_PTR(p7);
|
|
if (ret_si)
|
|
/* We do not pass ownership here, 'si' object remains owned by 'p7' object. */
|
|
*ret_si = si;
|
|
|
|
TAKE_PTR(si);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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_method=*/ NULL,
|
|
/* post_process= */ NULL,
|
|
/* post_process_data= */ NULL);
|
|
if (!store)
|
|
return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri);
|
|
|
|
if (OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0)
|
|
return log_openssl_errors("Failed to filter store by private keys");
|
|
|
|
_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
|
|
}
|
|
|
|
static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
|
|
int r;
|
|
|
|
switch(UI_get_string_type(uis)) {
|
|
case UIT_PROMPT: {
|
|
/* If no ask password request was configured use the default openssl UI. */
|
|
AskPasswordRequest *req = (AskPasswordRequest*) UI_method_get_ex_data(UI_get_method(ui), 0);
|
|
if (!req)
|
|
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
|
|
|
|
req->message = UI_get0_output_string(uis);
|
|
|
|
_cleanup_strv_free_ char **l = NULL;
|
|
r = ask_password_auto(req, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l);
|
|
if (r < 0) {
|
|
log_error_errno(r, "Failed to query for PIN: %m");
|
|
return 0;
|
|
}
|
|
|
|
if (strv_length(l) != 1) {
|
|
log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected only a single password/pin.");
|
|
return 0;
|
|
}
|
|
|
|
if (UI_set_result(ui, uis, *l) != 0) {
|
|
log_openssl_errors("Failed to set user interface result");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
default:
|
|
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
|
|
}
|
|
}
|
|
|
|
static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
|
|
_cleanup_(erase_and_freep) char *rawkey = NULL;
|
|
_cleanup_(BIO_freep) BIO *kb = NULL;
|
|
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
|
|
size_t rawkeysz;
|
|
int r;
|
|
|
|
assert(path);
|
|
assert(ret);
|
|
|
|
r = read_full_file_full(
|
|
AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
|
|
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
|
|
NULL,
|
|
&rawkey, &rawkeysz);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to read key file '%s': %m", path);
|
|
|
|
kb = BIO_new_mem_buf(rawkey, rawkeysz);
|
|
if (!kb)
|
|
return log_oom_debug();
|
|
|
|
pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
|
|
if (!pk)
|
|
return log_debug_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;
|
|
}
|
|
|
|
static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) {
|
|
assert(ret);
|
|
|
|
_cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password");
|
|
if (!method)
|
|
return log_openssl_errors("Failed to initialize openssl user interface");
|
|
|
|
if (UI_method_set_reader(method, openssl_ask_password_ui_read) != 0)
|
|
return log_openssl_errors("Failed to set openssl user interface reader");
|
|
|
|
OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1);
|
|
if (!ui)
|
|
return log_oom_debug();
|
|
|
|
*ui = (OpenSSLAskPasswordUI) {
|
|
.method = TAKE_PTR(method),
|
|
.request = *request,
|
|
};
|
|
|
|
UI_set_default_method(ui->method);
|
|
|
|
if (UI_method_set_ex_data(ui->method, 0, &ui->request) == 0)
|
|
return log_openssl_errors("Failed to set extra data for UI method");
|
|
|
|
*ret = TAKE_PTR(ui);
|
|
return 0;
|
|
}
|
|
|
|
static int load_x509_certificate_from_file(const char *path, X509 **ret) {
|
|
_cleanup_free_ char *rawcert = NULL;
|
|
_cleanup_(X509_freep) X509 *cert = NULL;
|
|
_cleanup_(BIO_freep) BIO *cb = NULL;
|
|
size_t rawcertsz;
|
|
int r;
|
|
|
|
assert(path);
|
|
assert(ret);
|
|
|
|
r = read_full_file_full(
|
|
AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
|
|
READ_FULL_FILE_CONNECT_SOCKET,
|
|
NULL,
|
|
&rawcert, &rawcertsz);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to read certificate file '%s': %m", path);
|
|
|
|
cb = BIO_new_mem_buf(rawcert, rawcertsz);
|
|
if (!cb)
|
|
return log_oom_debug();
|
|
|
|
cert = PEM_read_bio_X509(cb, NULL, NULL, NULL);
|
|
if (!cert)
|
|
return log_debug_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;
|
|
}
|
|
|
|
static int load_x509_certificate_from_provider(const char *provider, const char *certificate_uri, X509 **ret) {
|
|
assert(provider);
|
|
assert(certificate_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(
|
|
certificate_uri,
|
|
/*ui_method=*/ NULL,
|
|
/*ui_method=*/ NULL,
|
|
/* post_process= */ NULL,
|
|
/* post_process_data= */ NULL);
|
|
if (!store)
|
|
return log_openssl_errors("Failed to open OpenSSL store via '%s'", certificate_uri);
|
|
|
|
if (OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0)
|
|
return log_openssl_errors("Failed to filter store by X.509 certificates");
|
|
|
|
_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'", certificate_uri);
|
|
|
|
_cleanup_(X509_freep) X509 *cert = OSSL_STORE_INFO_get1_CERT(info);
|
|
if (!cert)
|
|
return log_openssl_errors("Failed to load certificate via '%s'", certificate_uri);
|
|
|
|
*ret = TAKE_PTR(cert);
|
|
|
|
return 0;
|
|
#else
|
|
return -EOPNOTSUPP;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
|
|
#if HAVE_OPENSSL
|
|
if (!ui)
|
|
return NULL;
|
|
|
|
assert(UI_get_default_method() == ui->method);
|
|
UI_set_default_method(UI_OpenSSL());
|
|
UI_destroy_method(ui->method);
|
|
return mfree(ui);
|
|
#else
|
|
assert(ui == NULL);
|
|
return NULL;
|
|
#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.");
|
|
#endif
|
|
}
|
|
|
|
int openssl_load_x509_certificate(
|
|
CertificateSourceType certificate_source_type,
|
|
const char *certificate_source,
|
|
const char *certificate,
|
|
X509 **ret) {
|
|
#if HAVE_OPENSSL
|
|
int r;
|
|
|
|
assert(certificate);
|
|
|
|
switch (certificate_source_type) {
|
|
|
|
case OPENSSL_CERTIFICATE_SOURCE_FILE:
|
|
r = load_x509_certificate_from_file(certificate, ret);
|
|
break;
|
|
case OPENSSL_CERTIFICATE_SOURCE_PROVIDER:
|
|
r = load_x509_certificate_from_provider(certificate_source, certificate, ret);
|
|
break;
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
if (r < 0)
|
|
return log_debug_errno(
|
|
r,
|
|
"Failed to load certificate '%s' from OpenSSL certificate source %s: %m",
|
|
certificate,
|
|
certificate_source);
|
|
|
|
return 0;
|
|
#else
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load X509 certificate.");
|
|
#endif
|
|
}
|
|
|
|
int openssl_load_private_key(
|
|
KeySourceType private_key_source_type,
|
|
const char *private_key_source,
|
|
const char *private_key,
|
|
const AskPasswordRequest *request,
|
|
EVP_PKEY **ret_private_key,
|
|
OpenSSLAskPasswordUI **ret_user_interface) {
|
|
#if HAVE_OPENSSL
|
|
int r;
|
|
|
|
assert(private_key);
|
|
assert(request);
|
|
|
|
if (private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
|
|
r = openssl_load_private_key_from_file(private_key, ret_private_key);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
*ret_user_interface = NULL;
|
|
} else {
|
|
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
|
|
r = openssl_ask_password_ui_new(request, &ui);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to allocate ask-password user interface: %m");
|
|
|
|
switch (private_key_source_type) {
|
|
|
|
case OPENSSL_KEY_SOURCE_ENGINE:
|
|
r = load_key_from_engine(private_key_source, private_key, ret_private_key);
|
|
break;
|
|
case OPENSSL_KEY_SOURCE_PROVIDER:
|
|
r = load_key_from_provider(private_key_source, private_key, ret_private_key);
|
|
break;
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
if (r < 0)
|
|
return log_debug_errno(
|
|
r,
|
|
"Failed to load key '%s' from OpenSSL private key source %s: %m",
|
|
private_key,
|
|
private_key_source);
|
|
|
|
*ret_user_interface = TAKE_PTR(ui);
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
|
|
#endif
|
|
}
|
|
|
|
int parse_openssl_certificate_source_argument(
|
|
const char *argument,
|
|
char **certificate_source,
|
|
CertificateSourceType *certificate_source_type) {
|
|
|
|
CertificateSourceType type;
|
|
const char *e = NULL;
|
|
int r;
|
|
|
|
assert(argument);
|
|
assert(certificate_source);
|
|
assert(certificate_source_type);
|
|
|
|
if (streq(argument, "file"))
|
|
type = OPENSSL_CERTIFICATE_SOURCE_FILE;
|
|
else if ((e = startswith(argument, "provider:")))
|
|
type = OPENSSL_CERTIFICATE_SOURCE_PROVIDER;
|
|
else
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid certificate source '%s'", argument);
|
|
|
|
r = free_and_strdup_warn(certificate_source, e);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
*certificate_source_type = type;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|