return 0;
}
-/* Perform HKDF-SHA256 derivation, producing derive_size bytes of output.
+/* Perform HKDF (RFC 5869). 'key' is the input keying material (required, non-empty). 'salt' and 'info'
+ * are optional: pass NULL or an empty iovec to omit. The output is written to 'ret' as a fresh allocation
+ * of 'derive_size' bytes (must be ≤ 255 × HashLen).
*
- * For more details see: https://docs.openssl.org/master/man7/EVP_KDF-HKDF.html */
-int kdf_hkdf_sha256(
+ * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-HKDF.html */
+int kdf_hkdf_derive(
+ const char *digest,
const struct iovec *key,
const struct iovec *salt,
const struct iovec *info,
int r;
- assert(!key || key->iov_len > 0);
- assert(!salt || salt->iov_len > 0);
- assert(!info || info->iov_len > 0);
+ assert(digest);
+ assert(iovec_is_set(key));
+ assert(iovec_is_valid(salt));
+ assert(iovec_is_valid(info));
assert(derive_size > 0);
assert(ret);
if (r < 0)
return r;
- _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* propq= */ NULL, "HKDF", /* propq= */ NULL);
+ _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* libctx= */ NULL, "HKDF", /* properties= */ NULL);
if (!kdf)
- return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF for HKDF");
+ return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF");
_cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf);
if (!ctx)
if (!bld)
return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD");
- _cleanup_(erase_and_freep) void *buf = malloc(derive_size);
- if (!buf)
- return log_oom_debug();
+ if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, /* bsize= */ 0))
+ return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_DIGEST");
- if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, "digest", "SHA256", 0))
- return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF digest");
+ if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, key->iov_base, key->iov_len))
+ return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_KEY");
- if (key)
- if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "key", key->iov_base, key->iov_len))
- return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF key");
+ if (iovec_is_set(salt))
+ if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, salt->iov_base, salt->iov_len))
+ return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_SALT");
- if (salt)
- if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "salt", salt->iov_base, salt->iov_len))
- return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF salt");
-
- if (info)
- if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "info", info->iov_base, info->iov_len))
- return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF info");
+ if (iovec_is_set(info))
+ if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, info->iov_base, info->iov_len))
+ return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_INFO");
- _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *openssl_params = sym_OSSL_PARAM_BLD_to_param(bld);
- if (!openssl_params)
+ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld);
+ if (!params)
return log_openssl_errors(LOG_DEBUG, "Failed to build HKDF OSSL_PARAM");
- if (sym_EVP_KDF_derive(ctx, buf, derive_size, openssl_params) <= 0)
- return log_openssl_errors(LOG_DEBUG, "OpenSSL HKDF derive failed");
+ _cleanup_(erase_and_freep) void *buf = malloc(derive_size);
+ if (!buf)
+ return log_oom_debug();
- ret->iov_base = TAKE_PTR(buf);
- ret->iov_len = derive_size;
+ if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0)
+ return log_openssl_errors(LOG_DEBUG, "OpenSSL HKDF derive failed");
+ *ret = IOVEC_MAKE(TAKE_PTR(buf), derive_size);
return 0;
}
+/* Perform HKDF-SHA256 derivation, producing derive_size bytes of output. */
+int kdf_hkdf_sha256(
+ const struct iovec *key,
+ const struct iovec *salt,
+ const struct iovec *info,
+ size_t derive_size,
+ struct iovec *ret) {
+
+ return kdf_hkdf_derive("SHA256", key, salt, info, derive_size, ret);
+}
+
/* 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(
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);
+int kdf_hkdf_derive(const char *digest, const struct iovec *key, const struct iovec *salt, const struct iovec *info, size_t derive_size, struct iovec *ret);
+
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);
int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size);
"30EB1A1E9DEA7DE4DDB8F3FDF50A01E30581D606C1228D98AFF691DF743AC2EE9D99EFD2AE1946C079AA18C9524877FA65D5065F0DAED058AB3416AF80EB2B73");
}
+static void check_hkdf_derive(
+ const char *digest,
+ const char *hex_key,
+ const char *hex_salt,
+ const char *hex_info,
+ const char *hex_expected) {
+
+ DEFINE_HEX_PTR(key, hex_key);
+ DEFINE_HEX_PTR(salt, hex_salt);
+ DEFINE_HEX_PTR(info, hex_info);
+ DEFINE_HEX_PTR(expected, hex_expected);
+
+ _cleanup_(iovec_done) struct iovec derived = {};
+ assert_se(kdf_hkdf_derive(
+ digest,
+ &IOVEC_MAKE(key, key_len),
+ &IOVEC_MAKE(salt, salt_len),
+ &IOVEC_MAKE(info, info_len),
+ expected_len,
+ &derived) >= 0);
+ assert_se(memcmp_nn(derived.iov_base, derived.iov_len, expected, expected_len) == 0);
+}
+
+TEST(kdf_hkdf_derive) {
+ /* RFC 5869, Appendix A.1: basic HKDF-SHA256 test vector. */
+ check_hkdf_derive(
+ "SHA256",
+ "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+ "000102030405060708090a0b0c",
+ "f0f1f2f3f4f5f6f7f8f9",
+ "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865");
+
+ /* fscrypt v2 master-key identifier derivation. The Linux kernel uses standard HKDF-SHA512
+ * (RFC 5869) with empty salt (i.e. HashLen=64 zero bytes per RFC 5869 §2.2), but splits the
+ * info string for code organisation: fs/crypto/hkdf.c's fscrypt_hkdf_expand() hard-codes an
+ * 8-byte "fscrypt\0" prefix, then appends a context byte and the per-caller info argument
+ * before HKDF-Expand. For the key identifier the caller passes context=0x01
+ * (HKDF_CONTEXT_KEY_IDENTIFIER) with empty info, so the effective standard-HKDF info is the
+ * 9-byte string "fscrypt\0\x01". The vectors below assert that against representative IKM
+ * sizes (all-zeros, full 64-byte master key, AES-256 sized, and an arbitrary 64-byte
+ * payload). */
+ const char *fscrypt_info_hex = "667363727970740001";
+
+ check_hkdf_derive(
+ "SHA512",
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ /* hex_salt= */ NULL,
+ fscrypt_info_hex,
+ "69d7f347a3ca7bfa3e0c1d84e476d050");
+
+ check_hkdf_derive(
+ "SHA512",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
+ /* hex_salt= */ NULL,
+ fscrypt_info_hex,
+ "8699c2c53707405da5aba5ae4d8583c0");
+
+ check_hkdf_derive(
+ "SHA512",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ /* hex_salt= */ NULL,
+ fscrypt_info_hex,
+ "37d7d76a59400083289c185526730d34");
+
+ check_hkdf_derive(
+ "SHA512",
+ "73797374656d642d686f6d6564207632207465737420766563746f72206b65790000000000000000000000000000000000000000000000000000000000000000",
+ /* hex_salt= */ NULL,
+ fscrypt_info_hex,
+ "ab8550968fca25b08222de0ffb7b2986");
+
+ /* Exercise the salt=NULL path in kdf_hkdf_derive directly. DEFINE_HEX_PTR(NULL) allocates a
+ * 1-byte buffer of size 0, so check_hkdf_derive() above feeds the function an
+ * iovec_is_valid()-but-not-iovec_is_set() iovec. Passing salt=NULL outright takes the other
+ * branch. Per RFC 5869 §2.2 an absent salt is identical to HashLen zero bytes, which is also
+ * what OpenSSL substitutes for an empty-octet-string salt, so the derived output must match
+ * byte-for-byte against the third vector above. */
+ {
+ static const uint8_t ikm[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ };
+ static const uint8_t info[] = {
+ 'f', 's', 'c', 'r', 'y', 'p', 't', 0x00, 0x01,
+ };
+ DEFINE_HEX_PTR(expected, "37d7d76a59400083289c185526730d34");
+ _cleanup_(iovec_done) struct iovec derived = {};
+
+ assert_se(kdf_hkdf_derive(
+ "SHA512",
+ &IOVEC_MAKE((void*) ikm, sizeof(ikm)),
+ /* salt= */ NULL,
+ &IOVEC_MAKE((void*) info, sizeof(info)),
+ expected_len, &derived) >= 0);
+ assert_se(memcmp_nn(derived.iov_base, derived.iov_len, expected, expected_len) == 0);
+ }
+}
+
static void check_cipher(
const char *alg,
size_t bits,