From 6bbdaef1a578fdbfc6a5bf82402ba4d3fd733d4a Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 21 Mar 2017 18:23:53 +0900 Subject: [PATCH 1/6] pkey: assume generic PKeys contain private components The EVP interface cannot tell whether if a pkey contains the private components or not. Assume it does if it does not respond to #private?. This fixes the NoMethodError on calling #sign on a generic PKey. --- ext/openssl/ossl_pkey.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 610a83fd2d..8d41623e80 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -252,12 +252,19 @@ GetPrivPKeyPtr(VALUE obj) { EVP_PKEY *pkey; - if (rb_funcallv(obj, id_private_q, 0, NULL) != Qtrue) { - ossl_raise(rb_eArgError, "Private key is needed."); - } GetPKey(obj, pkey); + if (OSSL_PKEY_IS_PRIVATE(obj)) + return pkey; + /* + * The EVP API does not provide a way to check if the EVP_PKEY has private + * components. Assuming it does... + */ + if (!rb_respond_to(obj, id_private_q)) + return pkey; + if (RTEST(rb_funcallv(obj, id_private_q, 0, NULL))) + return pkey; - return pkey; + rb_raise(rb_eArgError, "private key is needed"); } EVP_PKEY * -- 2.32.0 From 86508f74b3d41166ed6091b7b31f18a26478c347 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 20 Mar 2017 23:18:26 +0900 Subject: [PATCH 2/6] pkey: add PKey.generate_parameters and .generate_key Add two methods to create a PKey using the generic EVP interface. This is useful for the PKey types we don't have a dedicated class. --- ext/openssl/ossl_pkey.c | 222 ++++++++++++++++++++++++++++++++++++++ test/openssl/test_pkey.rb | 43 ++++++++ 2 files changed, 265 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 8d41623e80..1f3dd39b9b 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -197,6 +197,226 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) return ossl_pkey_new(pkey); } +static VALUE +pkey_gen_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v)) +{ + VALUE key = rb_ary_entry(i, 0), value = rb_ary_entry(i, 1); + EVP_PKEY_CTX *ctx = (EVP_PKEY_CTX *)ctx_v; + + if (SYMBOL_P(key)) + key = rb_sym2str(key); + value = rb_String(value); + + if (EVP_PKEY_CTX_ctrl_str(ctx, StringValueCStr(key), StringValueCStr(value)) <= 0) + ossl_raise(ePKeyError, "EVP_PKEY_CTX_ctrl_str(ctx, %+"PRIsVALUE", %+"PRIsVALUE")", + key, value); + return Qnil; +} + +static VALUE +pkey_gen_apply_options0(VALUE args_v) +{ + VALUE *args = (VALUE *)args_v; + + rb_block_call(args[1], rb_intern("each"), 0, NULL, + pkey_gen_apply_options_i, args[0]); + return Qnil; +} + +struct pkey_blocking_generate_arg { + EVP_PKEY_CTX *ctx; + EVP_PKEY *pkey; + int state; + int yield: 1; + int genparam: 1; + int stop: 1; +}; + +static VALUE +pkey_gen_cb_yield(VALUE ctx_v) +{ + EVP_PKEY_CTX *ctx = (void *)ctx_v; + int i, info_num; + VALUE *argv; + + info_num = EVP_PKEY_CTX_get_keygen_info(ctx, -1); + argv = ALLOCA_N(VALUE, info_num); + for (i = 0; i < info_num; i++) + argv[i] = INT2NUM(EVP_PKEY_CTX_get_keygen_info(ctx, i)); + + return rb_yield_values2(info_num, argv); +} + +static int +pkey_gen_cb(EVP_PKEY_CTX *ctx) +{ + struct pkey_blocking_generate_arg *arg = EVP_PKEY_CTX_get_app_data(ctx); + + if (arg->yield) { + int state; + rb_protect(pkey_gen_cb_yield, (VALUE)ctx, &state); + if (state) { + arg->stop = 1; + arg->state = state; + } + } + return !arg->stop; +} + +static void +pkey_blocking_gen_stop(void *ptr) +{ + struct pkey_blocking_generate_arg *arg = ptr; + arg->stop = 1; +} + +static void * +pkey_blocking_gen(void *ptr) +{ + struct pkey_blocking_generate_arg *arg = ptr; + + if (arg->genparam && EVP_PKEY_paramgen(arg->ctx, &arg->pkey) <= 0) + return NULL; + if (!arg->genparam && EVP_PKEY_keygen(arg->ctx, &arg->pkey) <= 0) + return NULL; + return arg->pkey; +} + +static VALUE +pkey_generate(int argc, VALUE *argv, VALUE self, int genparam) +{ + EVP_PKEY_CTX *ctx; + VALUE alg, options; + struct pkey_blocking_generate_arg gen_arg = { 0 }; + int state; + + rb_scan_args(argc, argv, "11", &alg, &options); + if (rb_obj_is_kind_of(alg, cPKey)) { + EVP_PKEY *base_pkey; + + GetPKey(alg, base_pkey); + ctx = EVP_PKEY_CTX_new(base_pkey, NULL/* engine */); + if (!ctx) + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); + } + else { + const EVP_PKEY_ASN1_METHOD *ameth; + ENGINE *tmpeng; + int pkey_id; + + StringValue(alg); + ameth = EVP_PKEY_asn1_find_str(&tmpeng, RSTRING_PTR(alg), + RSTRING_LENINT(alg)); + if (!ameth) + ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", alg); + EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); +#if !defined(OPENSSL_NO_ENGINE) + if (tmpeng) + ENGINE_finish(tmpeng); +#endif + + ctx = EVP_PKEY_CTX_new_id(pkey_id, NULL/* engine */); + if (!ctx) + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_id"); + } + + if (genparam && EVP_PKEY_paramgen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_paramgen_init"); + } + if (!genparam && EVP_PKEY_keygen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_keygen_init"); + } + + if (!NIL_P(options)) { + VALUE args[2]; + + args[0] = (VALUE)ctx; + args[1] = options; + rb_protect(pkey_gen_apply_options0, (VALUE)args, &state); + if (state) { + EVP_PKEY_CTX_free(ctx); + rb_jump_tag(state); + } + } + + gen_arg.genparam = genparam; + gen_arg.ctx = ctx; + gen_arg.yield = rb_block_given_p(); + EVP_PKEY_CTX_set_app_data(ctx, &gen_arg); + EVP_PKEY_CTX_set_cb(ctx, pkey_gen_cb); + if (gen_arg.yield) + pkey_blocking_gen(&gen_arg); + else + rb_thread_call_without_gvl(pkey_blocking_gen, &gen_arg, + pkey_blocking_gen_stop, &gen_arg); + EVP_PKEY_CTX_free(ctx); + if (!gen_arg.pkey) { + if (gen_arg.state) { + ossl_clear_error(); + rb_jump_tag(gen_arg.state); + } + else { + ossl_raise(ePKeyError, genparam ? "EVP_PKEY_paramgen" : "EVP_PKEY_keygen"); + } + } + + return ossl_pkey_new(gen_arg.pkey); +} + +/* + * call-seq: + * OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey + * + * Generates new parameters for the algorithm. _algo_name_ is a String that + * represents the algorithm. The optional argument _options_ is a Hash that + * specifies the options specific to the algorithm. The order of the options + * can be important. + * + * A block can be passed optionally. The meaning of the arguments passed to + * the block varies depending on the implementation of the algorithm. The block + * may be called once or multiple times, or may not even be called. + * + * For the supported options, see the documentation for the 'openssl genpkey' + * utility command. + * + * == Example + * pkey = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048) + * p pkey.p.num_bits #=> 2048 + */ +static VALUE +ossl_pkey_s_generate_parameters(int argc, VALUE *argv, VALUE self) +{ + return pkey_generate(argc, argv, self, 1); +} + +/* + * call-seq: + * OpenSSL::PKey.generate_key(algo_name [, options]) -> pkey + * OpenSSL::PKey.generate_key(pkey [, options]) -> pkey + * + * Generates a new key (pair). + * + * If a String is given as the first argument, it generates a new random key + * for the algorithm specified by the name just as ::generate_parameters does. + * If an OpenSSL::PKey::PKey is given instead, it generates a new random key + * for the same algorithm as the key, using the parameters the key contains. + * + * See ::generate_parameters for the details of _options_ and the given block. + * + * == Example + * pkey_params = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048) + * pkey_params.priv_key #=> nil + * pkey = OpenSSL::PKey.generate_key(pkey_params) + * pkey.priv_key #=> # 512, + "dsa_paramgen_q_bits" => 256, + }) + assert_instance_of OpenSSL::PKey::DSA, pkey + assert_equal 512, pkey.p.num_bits + assert_equal 256, pkey.q.num_bits + assert_equal nil, pkey.priv_key + + # Invalid options are checked + assert_raise(OpenSSL::PKey::PKeyError) { + OpenSSL::PKey.generate_parameters("DSA", "invalid" => "option") + } + + # Parameter generation callback is called + cb_called = [] + assert_raise(RuntimeError) { + OpenSSL::PKey.generate_parameters("DSA") { |*args| + cb_called << args + raise "exit!" if cb_called.size == 3 + } + } + assert_not_empty cb_called + end + + def test_s_generate_key + assert_raise(OpenSSL::PKey::PKeyError) { + # DSA key pair cannot be generated without parameters + OpenSSL::PKey.generate_key("DSA") + } + pkey_params = OpenSSL::PKey.generate_parameters("DSA", { + "dsa_paramgen_bits" => 512, + "dsa_paramgen_q_bits" => 256, + }) + pkey = OpenSSL::PKey.generate_key(pkey_params) + assert_instance_of OpenSSL::PKey::DSA, pkey + assert_equal 512, pkey.p.num_bits + assert_not_equal nil, pkey.priv_key + end end -- 2.32.0 From 5713605e70c96e3215aab0e0341548af29b5088e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 15 May 2017 23:47:47 +0900 Subject: [PATCH 3/6] pkey: port PKey::PKey#sign and #verify to the EVP_Digest* interface Use EVP_DigestSign*() and EVP_DigestVerify*() interface instead of the old EVP_Sign*() and EVP_Verify*() functions. They were added in OpenSSL 1.0.0. Also, allow the digest to be specified as nil, as certain EVP_PKEY types don't expect a digest algorithm. --- ext/openssl/ossl_pkey.c | 90 ++++++++++++++++++++++----------------- test/openssl/test_pkey.rb | 12 ++++++ 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 1f3dd39b9b..a0d73f5821 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -753,35 +753,47 @@ static VALUE ossl_pkey_sign(VALUE self, VALUE digest, VALUE data) { EVP_PKEY *pkey; - const EVP_MD *md; + const EVP_MD *md = NULL; EVP_MD_CTX *ctx; - unsigned int buf_len; - VALUE str; - int result; + size_t siglen; + int state; + VALUE sig; pkey = GetPrivPKeyPtr(self); - md = ossl_evp_get_digestbyname(digest); + if (!NIL_P(digest)) + md = ossl_evp_get_digestbyname(digest); StringValue(data); - str = rb_str_new(0, EVP_PKEY_size(pkey)); ctx = EVP_MD_CTX_new(); if (!ctx) - ossl_raise(ePKeyError, "EVP_MD_CTX_new"); - if (!EVP_SignInit_ex(ctx, md, NULL)) { - EVP_MD_CTX_free(ctx); - ossl_raise(ePKeyError, "EVP_SignInit_ex"); + ossl_raise(ePKeyError, "EVP_MD_CTX_new"); + if (EVP_DigestSignInit(ctx, NULL, md, /* engine */NULL, pkey) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestSignInit"); + } + if (EVP_DigestSignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestSignUpdate"); + } + if (EVP_DigestSignFinal(ctx, NULL, &siglen) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestSignFinal"); } - if (!EVP_SignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) { - EVP_MD_CTX_free(ctx); - ossl_raise(ePKeyError, "EVP_SignUpdate"); + if (siglen > LONG_MAX) + rb_raise(ePKeyError, "signature would be too large"); + sig = ossl_str_new(NULL, (long)siglen, &state); + if (state) { + EVP_MD_CTX_free(ctx); + rb_jump_tag(state); + } + if (EVP_DigestSignFinal(ctx, (unsigned char *)RSTRING_PTR(sig), + &siglen) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestSignFinal"); } - result = EVP_SignFinal(ctx, (unsigned char *)RSTRING_PTR(str), &buf_len, pkey); EVP_MD_CTX_free(ctx); - if (!result) - ossl_raise(ePKeyError, "EVP_SignFinal"); - rb_str_set_len(str, buf_len); - - return str; + rb_str_set_len(sig, siglen); + return sig; } /* @@ -809,38 +821,38 @@ static VALUE ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) { EVP_PKEY *pkey; - const EVP_MD *md; + const EVP_MD *md = NULL; EVP_MD_CTX *ctx; - int siglen, result; + int ret; GetPKey(self, pkey); ossl_pkey_check_public_key(pkey); - md = ossl_evp_get_digestbyname(digest); + if (!NIL_P(digest)) + md = ossl_evp_get_digestbyname(digest); StringValue(sig); - siglen = RSTRING_LENINT(sig); StringValue(data); ctx = EVP_MD_CTX_new(); if (!ctx) - ossl_raise(ePKeyError, "EVP_MD_CTX_new"); - if (!EVP_VerifyInit_ex(ctx, md, NULL)) { - EVP_MD_CTX_free(ctx); - ossl_raise(ePKeyError, "EVP_VerifyInit_ex"); + ossl_raise(ePKeyError, "EVP_MD_CTX_new"); + if (EVP_DigestVerifyInit(ctx, NULL, md, /* engine */NULL, pkey) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestVerifyInit"); } - if (!EVP_VerifyUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) { - EVP_MD_CTX_free(ctx); - ossl_raise(ePKeyError, "EVP_VerifyUpdate"); + if (EVP_DigestVerifyUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestVerifyUpdate"); } - result = EVP_VerifyFinal(ctx, (unsigned char *)RSTRING_PTR(sig), siglen, pkey); + ret = EVP_DigestVerifyFinal(ctx, (unsigned char *)RSTRING_PTR(sig), + RSTRING_LEN(sig)); EVP_MD_CTX_free(ctx); - switch (result) { - case 0: - ossl_clear_error(); - return Qfalse; - case 1: - return Qtrue; - default: - ossl_raise(ePKeyError, "EVP_VerifyFinal"); + if (ret < 0) + ossl_raise(ePKeyError, "EVP_DigestVerifyFinal"); + if (ret) + return Qtrue; + else { + ossl_clear_error(); + return Qfalse; } } diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index a325a1ea2b..247ba84f83 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -68,4 +68,16 @@ def test_s_generate_key assert_equal 512, pkey.p.num_bits assert_not_equal nil, pkey.priv_key end + + def test_hmac_sign_verify + pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" }) + + hmac = OpenSSL::HMAC.new("abcd", "SHA256").update("data").digest + assert_equal hmac, pkey.sign("SHA256", "data") + + # EVP_PKEY_HMAC does not support verify + assert_raise(OpenSSL::PKey::PKeyError) { + pkey.verify("SHA256", "data", hmac) + } + end end -- 2.32.0 From 76566a2e1bab42a2e1587ecbec23779ee00020fc Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 15 May 2017 23:47:47 +0900 Subject: [PATCH 4/6] pkey: support 'one-shot' signing and verification OpenSSL 1.1.1 added EVP_DigestSign() and EVP_DigestVerify() functions to the interface. Some EVP_PKEY methods such as PureEdDSA algorithms do not support the streaming mechanism and require us to use them. --- ext/openssl/ossl_pkey.c | 30 ++++++++++++++++++++++++++ test/openssl/test_pkey.rb | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index a0d73f5821..19544ec7f0 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -771,6 +771,26 @@ ossl_pkey_sign(VALUE self, VALUE digest, VALUE data) EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestSignInit"); } +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) + if (EVP_DigestSign(ctx, NULL, &siglen, (unsigned char *)RSTRING_PTR(data), + RSTRING_LEN(data)) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestSign"); + } + if (siglen > LONG_MAX) + rb_raise(ePKeyError, "signature would be too large"); + sig = ossl_str_new(NULL, (long)siglen, &state); + if (state) { + EVP_MD_CTX_free(ctx); + rb_jump_tag(state); + } + if (EVP_DigestSign(ctx, (unsigned char *)RSTRING_PTR(sig), &siglen, + (unsigned char *)RSTRING_PTR(data), + RSTRING_LEN(data)) < 1) { + EVP_MD_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_DigestSign"); + } +#else if (EVP_DigestSignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestSignUpdate"); @@ -791,6 +811,7 @@ ossl_pkey_sign(VALUE self, VALUE digest, VALUE data) EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestSignFinal"); } +#endif EVP_MD_CTX_free(ctx); rb_str_set_len(sig, siglen); return sig; @@ -839,6 +860,14 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestVerifyInit"); } +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) + ret = EVP_DigestVerify(ctx, (unsigned char *)RSTRING_PTR(sig), + RSTRING_LEN(sig), (unsigned char *)RSTRING_PTR(data), + RSTRING_LEN(data)); + EVP_MD_CTX_free(ctx); + if (ret < 0) + ossl_raise(ePKeyError, "EVP_DigestVerify"); +#else if (EVP_DigestVerifyUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestVerifyUpdate"); @@ -848,6 +877,7 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) EVP_MD_CTX_free(ctx); if (ret < 0) ossl_raise(ePKeyError, "EVP_DigestVerifyFinal"); +#endif if (ret) return Qtrue; else { diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 247ba84f83..d811b9c75f 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -80,4 +80,49 @@ def test_hmac_sign_verify pkey.verify("SHA256", "data", hmac) } end + + def test_ed25519 + # Test vector from RFC 8032 Section 7.1 TEST 2 + priv_pem = <<~EOF + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIEzNCJso/5banbbDRuwRTg9bijGfNaumJNqM9u1PuKb7 + -----END PRIVATE KEY----- + EOF + pub_pem = <<~EOF + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VwAyEAPUAXw+hDiVqStwqnTRt+vJyYLM8uxJaMwM1V8Sr0Zgw= + -----END PUBLIC KEY----- + EOF + begin + priv = OpenSSL::PKey.read(priv_pem) + pub = OpenSSL::PKey.read(pub_pem) + rescue OpenSSL::PKey::PKeyError + # OpenSSL < 1.1.1 + pend "Ed25519 is not implemented" + end + assert_instance_of OpenSSL::PKey::PKey, priv + assert_instance_of OpenSSL::PKey::PKey, pub + assert_equal priv_pem, priv.private_to_pem + assert_equal pub_pem, priv.public_to_pem + assert_equal pub_pem, pub.public_to_pem + + sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*") + 92a009a9f0d4cab8720e820b5f642540 + a2b27b5416503f8fb3762223ebdb69da + 085ac1e43e15996e458f3613d0f11d8c + 387b2eaeb4302aeeb00d291612bb0c00 + EOF + data = ["72"].pack("H*") + assert_equal sig, priv.sign(nil, data) + assert_equal true, priv.verify(nil, sig, data) + assert_equal true, pub.verify(nil, sig, data) + assert_equal false, pub.verify(nil, sig, data.succ) + + # PureEdDSA wants nil as the message digest + assert_raise(OpenSSL::PKey::PKeyError) { priv.sign("SHA512", data) } + assert_raise(OpenSSL::PKey::PKeyError) { pub.verify("SHA512", sig, data) } + + # Ed25519 pkey type does not support key derivation + assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) } + end end -- 2.32.0 From fabdd22bddc572ba3342ec0b09e3fef8ed6245b8 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 18 Mar 2017 21:58:46 +0900 Subject: [PATCH 5/6] pkey: add PKey::PKey#derive Add OpenSSL::PKey::PKey#derive as the wrapper for EVP_PKEY_CTX_derive(). This is useful for pkey types that we don't have dedicated classes, such as X25519. --- ext/openssl/ossl_pkey.c | 52 ++++++++++++++++++++++++++++++++++++ test/openssl/test_pkey.rb | 26 ++++++++++++++++++ test/openssl/test_pkey_dh.rb | 13 +++++++++ test/openssl/test_pkey_ec.rb | 16 +++++++++++ 4 files changed, 107 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 19544ec7f0..df8b425a0f 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -886,6 +886,57 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) } } +/* + * call-seq: + * pkey.derive(peer_pkey) -> string + * + * Derives a shared secret from _pkey_ and _peer_pkey_. _pkey_ must contain + * the private components, _peer_pkey_ must contain the public components. + */ +static VALUE +ossl_pkey_derive(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey, *peer_pkey; + EVP_PKEY_CTX *ctx; + VALUE peer_pkey_obj, str; + size_t keylen; + int state; + + GetPKey(self, pkey); + rb_scan_args(argc, argv, "1", &peer_pkey_obj); + GetPKey(peer_pkey_obj, peer_pkey); + + ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); + if (!ctx) + ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); + if (EVP_PKEY_derive_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive_init"); + } + if (EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive_set_peer"); + } + if (EVP_PKEY_derive(ctx, NULL, &keylen) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive"); + } + if (keylen > LONG_MAX) + rb_raise(ePKeyError, "derived key would be too large"); + str = ossl_str_new(NULL, (long)keylen, &state); + if (state) { + EVP_PKEY_CTX_free(ctx); + rb_jump_tag(state); + } + if (EVP_PKEY_derive(ctx, (unsigned char *)RSTRING_PTR(str), &keylen) <= 0) { + EVP_PKEY_CTX_free(ctx); + ossl_raise(ePKeyError, "EVP_PKEY_derive"); + } + EVP_PKEY_CTX_free(ctx); + rb_str_set_len(str, keylen); + return str; +} + /* * INIT */ @@ -983,6 +1034,7 @@ Init_ossl_pkey(void) rb_define_method(cPKey, "sign", ossl_pkey_sign, 2); rb_define_method(cPKey, "verify", ossl_pkey_verify, 3); + rb_define_method(cPKey, "derive", ossl_pkey_derive, -1); id_private_q = rb_intern("private?"); diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index d811b9c75f..5307fe5b08 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -125,4 +125,30 @@ def test_ed25519 # Ed25519 pkey type does not support key derivation assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) } end + + def test_x25519 + # Test vector from RFC 7748 Section 6.1 + alice_pem = <<~EOF + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq + -----END PRIVATE KEY----- + EOF + bob_pem = <<~EOF + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VuAyEA3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08= + -----END PUBLIC KEY----- + EOF + shared_secret = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742" + begin + alice = OpenSSL::PKey.read(alice_pem) + bob = OpenSSL::PKey.read(bob_pem) + rescue OpenSSL::PKey::PKeyError + # OpenSSL < 1.1.0 + pend "X25519 is not implemented" + end + assert_instance_of OpenSSL::PKey::PKey, alice + assert_equal alice_pem, alice.private_to_pem + assert_equal bob_pem, bob.public_to_pem + assert_equal [shared_secret].pack("H*"), alice.derive(bob) + end end diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb index 4a05626a12..9efc3ba68d 100644 --- a/test/openssl/test_pkey_dh.rb +++ b/test/openssl/test_pkey_dh.rb @@ -18,6 +18,19 @@ def test_new_break end end + def test_derive_key + dh1 = Fixtures.pkey("dh1024").generate_key! + dh2 = Fixtures.pkey("dh1024").generate_key! + dh1_pub = OpenSSL::PKey.read(dh1.public_to_der) + dh2_pub = OpenSSL::PKey.read(dh2.public_to_der) + z = dh1.g.mod_exp(dh1.priv_key, dh1.p).mod_exp(dh2.priv_key, dh1.p).to_s(2) + assert_equal z, dh1.derive(dh2_pub) + assert_equal z, dh2.derive(dh1_pub) + + assert_equal z, dh1.compute_key(dh2.pub_key) + assert_equal z, dh2.compute_key(dh1.pub_key) + end + def test_DHparams dh1024 = Fixtures.pkey("dh1024") asn1 = OpenSSL::ASN1::Sequence([ diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index a0e6a23ff8..95d4338a51 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -93,6 +93,22 @@ def test_sign_verify assert_equal false, p256.verify("SHA256", signature1, data) end + def test_derive_key + # NIST CAVP, KAS_ECC_CDH_PrimitiveTest.txt, P-256 COUNT = 0 + qCAVSx = "700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287" + qCAVSy = "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac" + dIUT = "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534" + zIUT = "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b" + a = OpenSSL::PKey::EC.new("prime256v1") + a.private_key = OpenSSL::BN.new(dIUT, 16) + b = OpenSSL::PKey::EC.new("prime256v1") + uncompressed = OpenSSL::BN.new("04" + qCAVSx + qCAVSy, 16) + b.public_key = OpenSSL::PKey::EC::Point.new(b.group, uncompressed) + assert_equal [zIUT].pack("H*"), a.derive(b) + + assert_equal a.derive(b), a.dh_compute_key(b.public_key) + end + def test_dsa_sign_verify data1 = "foo" data2 = "bar" -- 2.32.0 From 97078c7fa8a724c7c71f9850d31fc401239da228 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sat, 18 Mar 2017 22:34:19 +0900 Subject: [PATCH 6/6] pkey: reimplement PKey::DH#compute_key and PKey::EC#dh_compute_key Use the new OpenSSL::PKey::PKey#derive instead of the raw {EC,}DH_compute_key(), mainly to reduce amount of the C code. --- ext/openssl/lib/openssl/pkey.rb | 33 +++++++++++++++++++++++++++++++ ext/openssl/ossl_pkey_dh.c | 35 --------------------------------- ext/openssl/ossl_pkey_ec.c | 32 ------------------------------ 3 files changed, 33 insertions(+), 67 deletions(-) diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb index 9cc3276356..be60ac2beb 100644 --- a/ext/openssl/lib/openssl/pkey.rb +++ b/ext/openssl/lib/openssl/pkey.rb @@ -9,6 +9,24 @@ module OpenSSL::PKey class DH include OpenSSL::Marshal + + # :call-seq: + # dh.compute_key(pub_bn) -> string + # + # Returns a String containing a shared secret computed from the other + # party's public value. + # + # This method is provided for backwards compatibility, and calls #derive + # internally. + # + # === Parameters + # * _pub_bn_ is a OpenSSL::BN, *not* the DH instance returned by + # DH#public_key as that contains the DH parameters only. + def compute_key(pub_bn) + peer = dup + peer.set_key(pub_bn, nil) + derive(peer) + end end class DSA @@ -18,7 +36,22 @@ class DSA if defined?(EC) class EC include OpenSSL::Marshal + + # :call-seq: + # ec.dh_compute_key(pubkey) -> string + # + # Derives a shared secret by ECDH. _pubkey_ must be an instance of + # OpenSSL::PKey::EC::Point and must belong to the same group. + # + # This method is provided for backwards compatibility, and calls #derive + # internally. + def dh_compute_key(pubkey) + peer = OpenSSL::PKey::EC.new(group) + peer.public_key = pubkey + derive(peer) + end end + class EC::Point # :call-seq: # point.to_bn([conversion_form]) -> OpenSSL::BN diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index bc50e5566b..5bc1c49ca1 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -476,40 +476,6 @@ ossl_dh_generate_key(VALUE self) return self; } -/* - * call-seq: - * dh.compute_key(pub_bn) -> aString - * - * Returns a String containing a shared secret computed from the other party's public value. - * See DH_compute_key() for further information. - * - * === Parameters - * * _pub_bn_ is a OpenSSL::BN, *not* the DH instance returned by - * DH#public_key as that contains the DH parameters only. - */ -static VALUE -ossl_dh_compute_key(VALUE self, VALUE pub) -{ - DH *dh; - const BIGNUM *pub_key, *dh_p; - VALUE str; - int len; - - GetDH(self, dh); - DH_get0_pqg(dh, &dh_p, NULL, NULL); - if (!dh_p) - ossl_raise(eDHError, "incomplete DH"); - pub_key = GetBNPtr(pub); - len = DH_size(dh); - str = rb_str_new(0, len); - if ((len = DH_compute_key((unsigned char *)RSTRING_PTR(str), pub_key, dh)) < 0) { - ossl_raise(eDHError, NULL); - } - rb_str_set_len(str, len); - - return str; -} - /* * Document-method: OpenSSL::PKey::DH#set_pqg * call-seq: @@ -587,7 +553,6 @@ Init_ossl_dh(void) rb_define_method(cDH, "public_key", ossl_dh_to_public_key, 0); rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0); rb_define_method(cDH, "generate_key!", ossl_dh_generate_key, 0); - rb_define_method(cDH, "compute_key", ossl_dh_compute_key, 1); DEF_OSSL_PKEY_BN(cDH, dh, p); DEF_OSSL_PKEY_BN(cDH, dh, q); diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 6fe2533e2a..c2534251c3 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -487,37 +487,6 @@ static VALUE ossl_ec_key_check_key(VALUE self) return Qtrue; } -/* - * call-seq: - * key.dh_compute_key(pubkey) => String - * - * See the OpenSSL documentation for ECDH_compute_key() - */ -static VALUE ossl_ec_key_dh_compute_key(VALUE self, VALUE pubkey) -{ - EC_KEY *ec; - EC_POINT *point; - int buf_len; - VALUE str; - - GetEC(self, ec); - GetECPoint(pubkey, point); - -/* BUG: need a way to figure out the maximum string size */ - buf_len = 1024; - str = rb_str_new(0, buf_len); -/* BUG: take KDF as a block */ - buf_len = ECDH_compute_key(RSTRING_PTR(str), buf_len, point, ec, NULL); - if (buf_len < 0) - ossl_raise(eECError, "ECDH_compute_key"); - - rb_str_resize(str, buf_len); - - return str; -} - -/* sign_setup */ - /* * call-seq: * key.dsa_sign_asn1(data) => String @@ -1657,7 +1626,6 @@ void Init_ossl_ec(void) rb_define_alias(cEC, "generate_key", "generate_key!"); rb_define_method(cEC, "check_key", ossl_ec_key_check_key, 0); - rb_define_method(cEC, "dh_compute_key", ossl_ec_key_dh_compute_key, 1); rb_define_method(cEC, "dsa_sign_asn1", ossl_ec_key_dsa_sign_asn1, 1); rb_define_method(cEC, "dsa_verify_asn1", ossl_ec_key_dsa_verify_asn1, 2); /* do_sign/do_verify */ -- 2.32.0