Skip to content

Commit

Permalink
kdf: add HKDF support
Browse files Browse the repository at this point in the history
OpenSSL 1.1.0 supports HKDF through the EVP API. Add OpenSSL::KDF.hkdf
as a wrapper around that.

Reference: ruby#172
  • Loading branch information
rhenium committed Nov 22, 2017
1 parent e72d960 commit d8e9c54
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 0 deletions.
98 changes: 98 additions & 0 deletions ext/openssl/ossl_kdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors
*/
#include "ossl.h"
#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
# include <openssl/kdf.h>
#endif

static VALUE mKDF, eKDF;

Expand Down Expand Up @@ -138,6 +141,97 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self)
}
#endif

#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
/*
* call-seq:
* KDF.hkdf(ikm, salt:, info:, length:, hash:) -> String
*
* HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in
* {RFC 5869}[https://tools.ietf.org/html/rfc5869].
*
* New in OpenSSL 1.1.0.
*
* === Parameters
* _ikm_::
* The input keying material.
* _salt_::
* The salt.
* _info_::
* The context and application specific information.
* _length_::
* The output length in octets. Must be <= <tt>255 * HashLen</tt>, where
* HashLen is the length of the hash function output in octets.
* _hash_::
* The hash function.
*/
static VALUE
kdf_hkdf(int argc, VALUE *argv, VALUE self)
{
VALUE ikm, salt, info, opts, kwargs[4], str;
static ID kwargs_ids[4];
int saltlen, ikmlen, infolen;
size_t len;
const EVP_MD *md;
EVP_PKEY_CTX *pctx;

if (!kwargs_ids[0]) {
kwargs_ids[0] = rb_intern_const("salt");
kwargs_ids[1] = rb_intern_const("info");
kwargs_ids[2] = rb_intern_const("length");
kwargs_ids[3] = rb_intern_const("hash");
}
rb_scan_args(argc, argv, "1:", &ikm, &opts);
rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs);

StringValue(ikm);
ikmlen = RSTRING_LENINT(ikm);
salt = StringValue(kwargs[0]);
saltlen = RSTRING_LENINT(salt);
info = StringValue(kwargs[1]);
infolen = RSTRING_LENINT(info);
len = (size_t)NUM2LONG(kwargs[2]);
if (len > LONG_MAX)
rb_raise(rb_eArgError, "length must be non-negative");
md = ossl_evp_get_digestbyname(kwargs[3]);

str = rb_str_new(NULL, (long)len);
pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (!pctx)
ossl_raise(eKDF, "EVP_PKEY_CTX_new_id");
if (EVP_PKEY_derive_init(pctx) <= 0) {
EVP_PKEY_CTX_free(pctx);
ossl_raise(eKDF, "EVP_PKEY_derive_init");
}
if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) {
EVP_PKEY_CTX_free(pctx);
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md");
}
if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt),
saltlen) <= 0) {
EVP_PKEY_CTX_free(pctx);
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt");
}
if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm),
ikmlen) <= 0) {
EVP_PKEY_CTX_free(pctx);
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key");
}
if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info),
infolen) <= 0) {
EVP_PKEY_CTX_free(pctx);
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info");
}
if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) {
EVP_PKEY_CTX_free(pctx);
ossl_raise(eKDF, "EVP_PKEY_derive");
}
rb_str_set_len(str, (long)len);
EVP_PKEY_CTX_free(pctx);

return str;
}
#endif

void
Init_ossl_kdf(void)
{
Expand All @@ -162,6 +256,7 @@ Init_ossl_kdf(void)
* * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in
* combination with HMAC
* * scrypt
* * HKDF
*
* == Examples
* === Generating a 128 bit key for a Cipher (e.g. AES)
Expand Down Expand Up @@ -218,4 +313,7 @@ Init_ossl_kdf(void)
#if defined(HAVE_EVP_PBE_SCRYPT)
rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
rb_define_module_function(mKDF, "hkdf", kdf_hkdf, -1);
#endif
}
42 changes: 42 additions & 0 deletions test/test_kdf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,48 @@ def test_scrypt_rfc7914_second
assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
end

def test_hkdf_rfc5869_test_case_1
pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
hash = "sha256"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
salt = B("000102030405060708090a0b0c")
info = B("f0f1f2f3f4f5f6f7f8f9")
l = 42

okm = B("3cb25f25faacd57a90434f64d0362f2a" \
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" \
"34007208d5b887185865")
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
end

def test_hkdf_rfc5869_test_case_3
pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
hash = "sha256"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
salt = B("")
info = B("")
l = 42

okm = B("8da4e775a563c18f715f802a063c5a31" \
"b8a11f5c5ee1879ec3454e5f3c738d2d" \
"9d201395faa4b61a96c8")
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
end

def test_hkdf_rfc5869_test_case_4
pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
hash = "sha1"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b")
salt = B("000102030405060708090a0b0c")
info = B("f0f1f2f3f4f5f6f7f8f9")
l = 42

okm = B("085a01ea1b10f36933068b56efa5ad81" \
"a4f14b822f5b091568a9cdd4f155fda2" \
"c22e422478d305f3f896")
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
end

private

def B(ary)
Expand Down

0 comments on commit d8e9c54

Please sign in to comment.