From 3e6cd88c621d8834712d2279f925b9af83c2bb34 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Mon, 18 May 2020 20:06:16 +0900
Subject: [PATCH 1/6] pkey: implement PKey#encrypt and #decrypt

Support public key encryption and decryption operations using the EVP
API.
---
 ext/openssl/ossl_pkey.c       | 141 ++++++++++++++++++++++++++++++++++
 test/openssl/test_pkey_rsa.rb |  34 ++++++++
 2 files changed, 175 insertions(+)

diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index 21cd4b2cda..baf8ce9f20 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -986,6 +986,145 @@ ossl_pkey_derive(int argc, VALUE *argv, VALUE self)
     return str;
 }
 
+/*
+ * call-seq:
+ *    pkey.encrypt(data [, options]) -> string
+ *
+ * Performs a public key encryption operation using +pkey+.
+ *
+ * See #decrypt for the reverse operation.
+ *
+ * Added in version 3.0. See also the man page EVP_PKEY_encrypt(3).
+ *
+ * +data+::
+ *   A String to be encrypted.
+ * +options+::
+ *   A Hash that contains algorithm specific control operations to \OpenSSL.
+ *   See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details.
+ *
+ * Example:
+ *   pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048)
+ *   data = "secret data"
+ *   encrypted = pkey.encrypt(data, rsa_padding_mode: "oaep")
+ *   decrypted = pkey.decrypt(data, rsa_padding_mode: "oaep")
+ *   p decrypted #=> "secret data"
+ */
+static VALUE
+ossl_pkey_encrypt(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    EVP_PKEY_CTX *ctx;
+    VALUE data, options, str;
+    size_t outlen;
+    int state;
+
+    GetPKey(self, pkey);
+    rb_scan_args(argc, argv, "11", &data, &options);
+    StringValue(data);
+
+    ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
+    if (!ctx)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
+    if (EVP_PKEY_encrypt_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_encrypt_init");
+    }
+    if (!NIL_P(options)) {
+        pkey_ctx_apply_options(ctx, options, &state);
+        if (state) {
+            EVP_PKEY_CTX_free(ctx);
+            rb_jump_tag(state);
+        }
+    }
+    if (EVP_PKEY_encrypt(ctx, NULL, &outlen,
+                         (unsigned char *)RSTRING_PTR(data),
+                         RSTRING_LEN(data)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_encrypt");
+    }
+    if (outlen > LONG_MAX) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_raise(ePKeyError, "encrypted data would be too large");
+    }
+    str = ossl_str_new(NULL, (long)outlen, &state);
+    if (state) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_jump_tag(state);
+    }
+    if (EVP_PKEY_encrypt(ctx, (unsigned char *)RSTRING_PTR(str), &outlen,
+                         (unsigned char *)RSTRING_PTR(data),
+                         RSTRING_LEN(data)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_encrypt");
+    }
+    EVP_PKEY_CTX_free(ctx);
+    rb_str_set_len(str, outlen);
+    return str;
+}
+
+/*
+ * call-seq:
+ *    pkey.decrypt(data [, options]) -> string
+ *
+ * Performs a public key decryption operation using +pkey+.
+ *
+ * See #encrypt for a description of the parameters and an example.
+ *
+ * Added in version 3.0. See also the man page EVP_PKEY_decrypt(3).
+ */
+static VALUE
+ossl_pkey_decrypt(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    EVP_PKEY_CTX *ctx;
+    VALUE data, options, str;
+    size_t outlen;
+    int state;
+
+    GetPKey(self, pkey);
+    rb_scan_args(argc, argv, "11", &data, &options);
+    StringValue(data);
+
+    ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
+    if (!ctx)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
+    if (EVP_PKEY_decrypt_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_decrypt_init");
+    }
+    if (!NIL_P(options)) {
+        pkey_ctx_apply_options(ctx, options, &state);
+        if (state) {
+            EVP_PKEY_CTX_free(ctx);
+            rb_jump_tag(state);
+        }
+    }
+    if (EVP_PKEY_decrypt(ctx, NULL, &outlen,
+                         (unsigned char *)RSTRING_PTR(data),
+                         RSTRING_LEN(data)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_decrypt");
+    }
+    if (outlen > LONG_MAX) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_raise(ePKeyError, "decrypted data would be too large");
+    }
+    str = ossl_str_new(NULL, (long)outlen, &state);
+    if (state) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_jump_tag(state);
+    }
+    if (EVP_PKEY_decrypt(ctx, (unsigned char *)RSTRING_PTR(str), &outlen,
+                         (unsigned char *)RSTRING_PTR(data),
+                         RSTRING_LEN(data)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_decrypt");
+    }
+    EVP_PKEY_CTX_free(ctx);
+    rb_str_set_len(str, outlen);
+    return str;
+}
+
 /*
  * INIT
  */
@@ -1085,6 +1224,8 @@ Init_ossl_pkey(void)
     rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
     rb_define_method(cPKey, "verify", ossl_pkey_verify, -1);
     rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
+    rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
+    rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);
 
     id_private_q = rb_intern("private?");
 
diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb
index 5f8d04e754..d6bfca3ac5 100644
--- a/test/openssl/test_pkey_rsa.rb
+++ b/test/openssl/test_pkey_rsa.rb
@@ -173,6 +173,40 @@ def test_sign_verify_pss
     }
   end
 
+  def test_encrypt_decrypt
+    rsapriv = Fixtures.pkey("rsa-1")
+    rsapub = dup_public(rsapriv)
+
+    # Defaults to PKCS #1 v1.5
+    raw = "data"
+    enc = rsapub.encrypt(raw)
+    assert_equal raw, rsapriv.decrypt(enc)
+
+    # Invalid options
+    assert_raise(OpenSSL::PKey::PKeyError) {
+      rsapub.encrypt(raw, { "nonexistent" => "option" })
+    }
+  end
+
+  def test_encrypt_decrypt_legacy
+    rsapriv = Fixtures.pkey("rsa-1")
+    rsapub = dup_public(rsapriv)
+
+    # Defaults to PKCS #1 v1.5
+    raw = "data"
+    enc_legacy = rsapub.public_encrypt(raw)
+    assert_equal raw, rsapriv.decrypt(enc_legacy)
+    enc_new = rsapub.encrypt(raw)
+    assert_equal raw, rsapriv.private_decrypt(enc_new)
+
+    # OAEP with default parameters
+    raw = "data"
+    enc_legacy = rsapub.public_encrypt(raw, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
+    assert_equal raw, rsapriv.decrypt(enc_legacy, { "rsa_padding_mode" => "oaep" })
+    enc_new = rsapub.encrypt(raw, { "rsa_padding_mode" => "oaep" })
+    assert_equal raw, rsapriv.private_decrypt(enc_legacy, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
+  end
+
   def test_export
     rsa1024 = Fixtures.pkey("rsa1024")
     key = OpenSSL::PKey::RSA.new
-- 
2.32.0


From 6f5c75b06967b5b2db1d13646d74310e1cdc563e Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Tue, 25 May 2021 18:43:29 +0900
Subject: [PATCH 2/6] pkey: update version reference in #sign and #verify
 documentation

The next release is decided to be 3.0 rather than 2.3.
---
 ext/openssl/ossl_pkey.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index baf8ce9f20..d08f6f7e60 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -761,7 +761,7 @@ ossl_pkey_public_to_pem(VALUE self)
  * +options+::
  *   A Hash that contains algorithm specific control operations to \OpenSSL.
  *   See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details.
- *   +options+ parameter was added in version 2.3.
+ *   +options+ parameter was added in version 3.0.
  *
  * Example:
  *   data = "Sign me!"
@@ -875,7 +875,7 @@ ossl_pkey_sign(int argc, VALUE *argv, VALUE self)
  * +data+::
  *   See #sign.
  * +options+::
- *   See #sign. +options+ parameter was added in version 2.3.
+ *   See #sign. +options+ parameter was added in version 3.0.
  */
 static VALUE
 ossl_pkey_verify(int argc, VALUE *argv, VALUE self)
-- 
2.32.0


From 99fc31a9b4843b7f8923b5ce8b36115b2c66f4db Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Fri, 22 May 2020 16:10:35 +0900
Subject: [PATCH 3/6] pkey: implement PKey#sign_raw, #verify_raw, and
 #verify_recover

Add a variant of PKey#sign and #verify that do not hash the data
automatically.

Sometimes the caller has the hashed data only, but not the plaintext
to be signed. In that case, users would have to use the low-level API
such as RSA#private_encrypt or #public_decrypt directly.

OpenSSL 1.0.0 and later supports EVP_PKEY_sign() and EVP_PKEY_verify()
which provide the same functionality as part of the EVP API. This patch
adds wrappers for them.
---
 ext/openssl/ossl_pkey.c       | 232 ++++++++++++++++++++++++++++++++++
 test/openssl/test_pkey_dsa.rb |  25 +++-
 test/openssl/test_pkey_ec.rb  |  21 ++-
 test/openssl/test_pkey_rsa.rb |  78 ++++++++----
 4 files changed, 325 insertions(+), 31 deletions(-)

diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index d08f6f7e60..ba909c7632 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -935,6 +935,235 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self)
     }
 }
 
+/*
+ * call-seq:
+ *    pkey.sign_raw(digest, data [, options]) -> string
+ *
+ * Signs +data+ using a private key +pkey+. Unlike #sign, +data+ will not be
+ * hashed by +digest+ automatically.
+ *
+ * See #verify_raw for the verification operation.
+ *
+ * Added in version 3.0. See also the man page EVP_PKEY_sign(3).
+ *
+ * +digest+::
+ *   A String that represents the message digest algorithm name, or +nil+
+ *   if the PKey type requires no digest algorithm.
+ *   Although this method will not hash +data+ with it, this parameter may still
+ *   be required depending on the signature algorithm.
+ * +data+::
+ *   A String. The data to be signed.
+ * +options+::
+ *   A Hash that contains algorithm specific control operations to \OpenSSL.
+ *   See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details.
+ *
+ * Example:
+ *   data = "Sign me!"
+ *   hash = OpenSSL::Digest.digest("SHA256", data)
+ *   pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048)
+ *   signopts = { rsa_padding_mode: "pss" }
+ *   signature = pkey.sign_raw("SHA256", hash, signopts)
+ *
+ *   # Creates a copy of the RSA key pkey, but without the private components
+ *   pub_key = pkey.public_key
+ *   puts pub_key.verify_raw("SHA256", signature, hash, signopts) # => true
+ */
+static VALUE
+ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    VALUE digest, data, options, sig;
+    const EVP_MD *md = NULL;
+    EVP_PKEY_CTX *ctx;
+    size_t outlen;
+    int state;
+
+    GetPKey(self, pkey);
+    rb_scan_args(argc, argv, "21", &digest, &data, &options);
+    if (!NIL_P(digest))
+        md = ossl_evp_get_digestbyname(digest);
+    StringValue(data);
+
+    ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
+    if (!ctx)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
+    if (EVP_PKEY_sign_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_sign_init");
+    }
+    if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md");
+    }
+    if (!NIL_P(options)) {
+        pkey_ctx_apply_options(ctx, options, &state);
+        if (state) {
+            EVP_PKEY_CTX_free(ctx);
+            rb_jump_tag(state);
+        }
+    }
+    if (EVP_PKEY_sign(ctx, NULL, &outlen, (unsigned char *)RSTRING_PTR(data),
+                      RSTRING_LEN(data)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_sign");
+    }
+    if (outlen > LONG_MAX) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_raise(ePKeyError, "signature would be too large");
+    }
+    sig = ossl_str_new(NULL, (long)outlen, &state);
+    if (state) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_jump_tag(state);
+    }
+    if (EVP_PKEY_sign(ctx, (unsigned char *)RSTRING_PTR(sig), &outlen,
+                      (unsigned char *)RSTRING_PTR(data),
+                      RSTRING_LEN(data)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_sign");
+    }
+    EVP_PKEY_CTX_free(ctx);
+    rb_str_set_len(sig, outlen);
+    return sig;
+}
+
+/*
+ * call-seq:
+ *    pkey.verify_raw(digest, signature, data [, options]) -> true or false
+ *
+ * Verifies the +signature+ for the +data+ using a public key +pkey+. Unlike
+ * #verify, this method will not hash +data+ with +digest+ automatically.
+ *
+ * Returns +true+ if the signature is successfully verified, +false+ otherwise.
+ * The caller must check the return value.
+ *
+ * See #sign_raw for the signing operation and an example code.
+ *
+ * Added in version 3.0. See also the man page EVP_PKEY_verify(3).
+ *
+ * +signature+::
+ *   A String containing the signature to be verified.
+ */
+static VALUE
+ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    VALUE digest, sig, data, options;
+    const EVP_MD *md = NULL;
+    EVP_PKEY_CTX *ctx;
+    int state, ret;
+
+    GetPKey(self, pkey);
+    rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options);
+    ossl_pkey_check_public_key(pkey);
+    if (!NIL_P(digest))
+        md = ossl_evp_get_digestbyname(digest);
+    StringValue(sig);
+    StringValue(data);
+
+    ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
+    if (!ctx)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
+    if (EVP_PKEY_verify_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_verify_init");
+    }
+    if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md");
+    }
+    if (!NIL_P(options)) {
+        pkey_ctx_apply_options(ctx, options, &state);
+        if (state) {
+            EVP_PKEY_CTX_free(ctx);
+            rb_jump_tag(state);
+        }
+    }
+    ret = EVP_PKEY_verify(ctx, (unsigned char *)RSTRING_PTR(sig),
+                          RSTRING_LEN(sig),
+                          (unsigned char *)RSTRING_PTR(data),
+                          RSTRING_LEN(data));
+    EVP_PKEY_CTX_free(ctx);
+    if (ret < 0)
+        ossl_raise(ePKeyError, "EVP_PKEY_verify");
+
+    if (ret)
+        return Qtrue;
+    else {
+        ossl_clear_error();
+        return Qfalse;
+    }
+}
+
+/*
+ * call-seq:
+ *    pkey.verify_recover(digest, signature [, options]) -> string
+ *
+ * Recovers the signed data from +signature+ using a public key +pkey+. Not all
+ * signature algorithms support this operation.
+ *
+ * Added in version 3.0. See also the man page EVP_PKEY_verify_recover(3).
+ *
+ * +signature+::
+ *   A String containing the signature to be verified.
+ */
+static VALUE
+ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    VALUE digest, sig, options, out;
+    const EVP_MD *md = NULL;
+    EVP_PKEY_CTX *ctx;
+    int state;
+    size_t outlen;
+
+    GetPKey(self, pkey);
+    rb_scan_args(argc, argv, "21", &digest, &sig, &options);
+    ossl_pkey_check_public_key(pkey);
+    if (!NIL_P(digest))
+        md = ossl_evp_get_digestbyname(digest);
+    StringValue(sig);
+
+    ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
+    if (!ctx)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
+    if (EVP_PKEY_verify_recover_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_verify_recover_init");
+    }
+    if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md");
+    }
+    if (!NIL_P(options)) {
+        pkey_ctx_apply_options(ctx, options, &state);
+        if (state) {
+            EVP_PKEY_CTX_free(ctx);
+            rb_jump_tag(state);
+        }
+    }
+    if (EVP_PKEY_verify_recover(ctx, NULL, &outlen,
+                                (unsigned char *)RSTRING_PTR(sig),
+                                RSTRING_LEN(sig)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_verify_recover");
+    }
+    out = ossl_str_new(NULL, (long)outlen, &state);
+    if (state) {
+        EVP_PKEY_CTX_free(ctx);
+        rb_jump_tag(state);
+    }
+    if (EVP_PKEY_verify_recover(ctx, (unsigned char *)RSTRING_PTR(out), &outlen,
+                                (unsigned char *)RSTRING_PTR(sig),
+                                RSTRING_LEN(sig)) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_verify_recover");
+    }
+    EVP_PKEY_CTX_free(ctx);
+    rb_str_set_len(out, outlen);
+    return out;
+}
+
 /*
  * call-seq:
  *    pkey.derive(peer_pkey) -> string
@@ -1223,6 +1452,9 @@ Init_ossl_pkey(void)
 
     rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
     rb_define_method(cPKey, "verify", ossl_pkey_verify, -1);
+    rb_define_method(cPKey, "sign_raw", ossl_pkey_sign_raw, -1);
+    rb_define_method(cPKey, "verify_raw", ossl_pkey_verify_raw, -1);
+    rb_define_method(cPKey, "verify_recover", ossl_pkey_verify_recover, -1);
     rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
     rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
     rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);
diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb
index 85bb6ec0ae..147e50176b 100644
--- a/test/openssl/test_pkey_dsa.rb
+++ b/test/openssl/test_pkey_dsa.rb
@@ -48,12 +48,31 @@ def test_sign_verify
     assert_equal false, dsa512.verify("SHA256", signature1, data)
   end
 
-  def test_sys_sign_verify
-    key = Fixtures.pkey("dsa256")
+  def test_sign_verify_raw
+    key = Fixtures.pkey("dsa512")
     data = 'Sign me!'
     digest = OpenSSL::Digest.digest('SHA1', data)
+
+    invalid_sig = key.sign_raw(nil, digest.succ)
+    malformed_sig = "*" * invalid_sig.bytesize
+
+    # Sign by #syssign
     sig = key.syssign(digest)
-    assert(key.sysverify(digest, sig))
+    assert_equal true, key.sysverify(digest, sig)
+    assert_equal false, key.sysverify(digest, invalid_sig)
+    assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
+    assert_equal true, key.verify_raw(nil, sig, digest)
+    assert_equal false, key.verify_raw(nil, invalid_sig, digest)
+    assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
+
+    # Sign by #sign_raw
+    sig = key.sign_raw(nil, digest)
+    assert_equal true, key.sysverify(digest, sig)
+    assert_equal false, key.sysverify(digest, invalid_sig)
+    assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
+    assert_equal true, key.verify_raw(nil, sig, digest)
+    assert_equal false, key.verify_raw(nil, invalid_sig, digest)
+    assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
   end
 
   def test_DSAPrivateKey
diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb
index 95d4338a51..4b6df0290f 100644
--- a/test/openssl/test_pkey_ec.rb
+++ b/test/openssl/test_pkey_ec.rb
@@ -109,13 +109,30 @@ def test_derive_key
     assert_equal a.derive(b), a.dh_compute_key(b.public_key)
   end
 
-  def test_dsa_sign_verify
+  def test_sign_verify_raw
+    key = Fixtures.pkey("p256")
     data1 = "foo"
     data2 = "bar"
-    key = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+
+    malformed_sig = "*" * 30
+
+    # Sign by #dsa_sign_asn1
     sig = key.dsa_sign_asn1(data1)
     assert_equal true, key.dsa_verify_asn1(data1, sig)
     assert_equal false, key.dsa_verify_asn1(data2, sig)
+    assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
+    assert_equal true, key.verify_raw(nil, sig, data1)
+    assert_equal false, key.verify_raw(nil, sig, data2)
+    assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, data1) }
+
+    # Sign by #sign_raw
+    sig = key.sign_raw(nil, data1)
+    assert_equal true, key.dsa_verify_asn1(data1, sig)
+    assert_equal false, key.dsa_verify_asn1(data2, sig)
+    assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
+    assert_equal true, key.verify_raw(nil, sig, data1)
+    assert_equal false, key.verify_raw(nil, sig, data2)
+    assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, data1) }
   end
 
   def test_dsa_sign_asn1_FIPS186_3
diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb
index d6bfca3ac5..5e127f5407 100644
--- a/test/openssl/test_pkey_rsa.rb
+++ b/test/openssl/test_pkey_rsa.rb
@@ -13,32 +13,6 @@ def test_no_private_exp
     assert_raise(OpenSSL::PKey::RSAError){ key.private_decrypt("foo") }
   end
 
-  def test_padding
-    key = OpenSSL::PKey::RSA.new(512, 3)
-
-    # Need right size for raw mode
-    plain0 = "x" * (512/8)
-    cipher = key.private_encrypt(plain0, OpenSSL::PKey::RSA::NO_PADDING)
-    plain1 = key.public_decrypt(cipher, OpenSSL::PKey::RSA::NO_PADDING)
-    assert_equal(plain0, plain1)
-
-    # Need smaller size for pkcs1 mode
-    plain0 = "x" * (512/8 - 11)
-    cipher1 = key.private_encrypt(plain0, OpenSSL::PKey::RSA::PKCS1_PADDING)
-    plain1 = key.public_decrypt(cipher1, OpenSSL::PKey::RSA::PKCS1_PADDING)
-    assert_equal(plain0, plain1)
-
-    cipherdef = key.private_encrypt(plain0) # PKCS1_PADDING is default
-    plain1 = key.public_decrypt(cipherdef)
-    assert_equal(plain0, plain1)
-    assert_equal(cipher1, cipherdef)
-
-    # Failure cases
-    assert_raise(ArgumentError){ key.private_encrypt() }
-    assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) }
-    assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) }
-  end
-
   def test_private
     # Generated by key size and public exponent
     key = OpenSSL::PKey::RSA.new(512, 3)
@@ -133,6 +107,58 @@ def test_sign_verify_options
     assert_equal false, key.verify("SHA256", sig_pss, data)
   end
 
+  def test_sign_verify_raw
+    key = Fixtures.pkey("rsa-1")
+    data = "Sign me!"
+    hash = OpenSSL::Digest.digest("SHA1", data)
+    signature = key.sign_raw("SHA1", hash)
+    assert_equal true, key.verify_raw("SHA1", signature, hash)
+    assert_equal true, key.verify("SHA1", signature, data)
+
+    # Too long data
+    assert_raise(OpenSSL::PKey::PKeyError) {
+      key.sign_raw("SHA1", "x" * (key.n.num_bytes + 1))
+    }
+
+    # With options
+    pssopts = {
+      "rsa_padding_mode" => "pss",
+      "rsa_pss_saltlen" => 20,
+      "rsa_mgf1_md" => "SHA256"
+    }
+    sig_pss = key.sign_raw("SHA1", hash, pssopts)
+    assert_equal true, key.verify("SHA1", sig_pss, data, pssopts)
+    assert_equal true, key.verify_raw("SHA1", sig_pss, hash, pssopts)
+  end
+
+  def test_sign_verify_raw_legacy
+    key = Fixtures.pkey("rsa-1")
+    bits = key.n.num_bits
+
+    # Need right size for raw mode
+    plain0 = "x" * (bits/8)
+    cipher = key.private_encrypt(plain0, OpenSSL::PKey::RSA::NO_PADDING)
+    plain1 = key.public_decrypt(cipher, OpenSSL::PKey::RSA::NO_PADDING)
+    assert_equal(plain0, plain1)
+
+    # Need smaller size for pkcs1 mode
+    plain0 = "x" * (bits/8 - 11)
+    cipher1 = key.private_encrypt(plain0, OpenSSL::PKey::RSA::PKCS1_PADDING)
+    plain1 = key.public_decrypt(cipher1, OpenSSL::PKey::RSA::PKCS1_PADDING)
+    assert_equal(plain0, plain1)
+
+    cipherdef = key.private_encrypt(plain0) # PKCS1_PADDING is default
+    plain1 = key.public_decrypt(cipherdef)
+    assert_equal(plain0, plain1)
+    assert_equal(cipher1, cipherdef)
+
+    # Failure cases
+    assert_raise(ArgumentError){ key.private_encrypt() }
+    assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) }
+    assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) }
+  end
+
+
   def test_verify_empty_rsa
     rsa = OpenSSL::PKey::RSA.new
     assert_raise(OpenSSL::PKey::PKeyError, "[Bug #12783]") {
-- 
2.32.0


From 4330b1b9661fcab1172473f4fdd9986602c1e78c Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Mon, 18 May 2020 20:24:08 +0900
Subject: [PATCH 4/6] pkey/rsa: port RSA#{private,public}_{encrypt,decrypt} to
 the EVP API

Implement these methods using the new OpenSSL::PKey::PKey#{encrypt,sign}
family. The definitions are now in lib/openssl/pkey.rb.

Also, recommend using those generic methods in the documentation.
---
 ext/openssl/lib/openssl/pkey.rb | 106 ++++++++++++++++++++++++
 ext/openssl/ossl_pkey_rsa.c     | 141 --------------------------------
 2 files changed, 106 insertions(+), 141 deletions(-)

diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
index 569559e1ce..dd8c7c0b09 100644
--- a/ext/openssl/lib/openssl/pkey.rb
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -243,5 +243,111 @@ def new(*args, &blk) # :nodoc:
         end
       end
     end
+
+    # :call-seq:
+    #    rsa.private_encrypt(string)          -> String
+    #    rsa.private_encrypt(string, padding) -> String
+    #
+    # Encrypt +string+ with the private key.  +padding+ defaults to
+    # PKCS1_PADDING. The encrypted string output can be decrypted using
+    # #public_decrypt.
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and
+    # PKey::PKey#verify_recover instead.
+    def private_encrypt(string, padding = PKCS1_PADDING)
+      n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
+      private? or raise OpenSSL::PKey::RSAError, "private key needed."
+      begin
+        sign_raw(nil, string, {
+          "rsa_padding_mode" => translate_padding_mode(padding),
+        })
+      rescue OpenSSL::PKey::PKeyError
+        raise OpenSSL::PKey::RSAError, $!.message
+      end
+    end
+
+    # :call-seq:
+    #    rsa.public_decrypt(string)          -> String
+    #    rsa.public_decrypt(string, padding) -> String
+    #
+    # Decrypt +string+, which has been encrypted with the private key, with the
+    # public key.  +padding+ defaults to PKCS1_PADDING.
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and
+    # PKey::PKey#verify_recover instead.
+    def public_decrypt(string, padding = PKCS1_PADDING)
+      n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
+      begin
+        verify_recover(nil, string, {
+          "rsa_padding_mode" => translate_padding_mode(padding),
+        })
+      rescue OpenSSL::PKey::PKeyError
+        raise OpenSSL::PKey::RSAError, $!.message
+      end
+    end
+
+    # :call-seq:
+    #    rsa.public_encrypt(string)          -> String
+    #    rsa.public_encrypt(string, padding) -> String
+    #
+    # Encrypt +string+ with the public key.  +padding+ defaults to
+    # PKCS1_PADDING. The encrypted string output can be decrypted using
+    # #private_decrypt.
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead.
+    def public_encrypt(data, padding = PKCS1_PADDING)
+      n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
+      begin
+        encrypt(data, {
+          "rsa_padding_mode" => translate_padding_mode(padding),
+        })
+      rescue OpenSSL::PKey::PKeyError
+        raise OpenSSL::PKey::RSAError, $!.message
+      end
+    end
+
+    # :call-seq:
+    #    rsa.private_decrypt(string)          -> String
+    #    rsa.private_decrypt(string, padding) -> String
+    #
+    # Decrypt +string+, which has been encrypted with the public key, with the
+    # private key. +padding+ defaults to PKCS1_PADDING.
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead.
+    def private_decrypt(data, padding = PKCS1_PADDING)
+      n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
+      private? or raise OpenSSL::PKey::RSAError, "private key needed."
+      begin
+        decrypt(data, {
+          "rsa_padding_mode" => translate_padding_mode(padding),
+        })
+      rescue OpenSSL::PKey::PKeyError
+        raise OpenSSL::PKey::RSAError, $!.message
+      end
+    end
+
+    PKCS1_PADDING = 1
+    SSLV23_PADDING = 2
+    NO_PADDING = 3
+    PKCS1_OAEP_PADDING = 4
+
+    private def translate_padding_mode(num)
+      case num
+      when PKCS1_PADDING
+        "pkcs1"
+      when SSLV23_PADDING
+        "sslv23"
+      when NO_PADDING
+        "none"
+      when PKCS1_OAEP_PADDING
+        "oaep"
+      else
+        raise OpenSSL::PKey::PKeyError, "unsupported padding mode"
+      end
+    end
   end
 end
diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c
index 1c5476cdcd..8ebd3ec559 100644
--- a/ext/openssl/ossl_pkey_rsa.c
+++ b/ext/openssl/ossl_pkey_rsa.c
@@ -229,138 +229,6 @@ ossl_rsa_to_der(VALUE self)
         return ossl_pkey_export_spki(self, 1);
 }
 
-/*
- * call-seq:
- *   rsa.public_encrypt(string)          => String
- *   rsa.public_encrypt(string, padding) => String
- *
- * Encrypt _string_ with the public key.  _padding_ defaults to PKCS1_PADDING.
- * The encrypted string output can be decrypted using #private_decrypt.
- */
-static VALUE
-ossl_rsa_public_encrypt(int argc, VALUE *argv, VALUE self)
-{
-    RSA *rsa;
-    const BIGNUM *rsa_n;
-    int buf_len, pad;
-    VALUE str, buffer, padding;
-
-    GetRSA(self, rsa);
-    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
-    if (!rsa_n)
-	ossl_raise(eRSAError, "incomplete RSA");
-    rb_scan_args(argc, argv, "11", &buffer, &padding);
-    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
-    StringValue(buffer);
-    str = rb_str_new(0, RSA_size(rsa));
-    buf_len = RSA_public_encrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
-				 (unsigned char *)RSTRING_PTR(str), rsa, pad);
-    if (buf_len < 0) ossl_raise(eRSAError, NULL);
-    rb_str_set_len(str, buf_len);
-
-    return str;
-}
-
-/*
- * call-seq:
- *   rsa.public_decrypt(string)          => String
- *   rsa.public_decrypt(string, padding) => String
- *
- * Decrypt _string_, which has been encrypted with the private key, with the
- * public key.  _padding_ defaults to PKCS1_PADDING.
- */
-static VALUE
-ossl_rsa_public_decrypt(int argc, VALUE *argv, VALUE self)
-{
-    RSA *rsa;
-    const BIGNUM *rsa_n;
-    int buf_len, pad;
-    VALUE str, buffer, padding;
-
-    GetRSA(self, rsa);
-    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
-    if (!rsa_n)
-	ossl_raise(eRSAError, "incomplete RSA");
-    rb_scan_args(argc, argv, "11", &buffer, &padding);
-    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
-    StringValue(buffer);
-    str = rb_str_new(0, RSA_size(rsa));
-    buf_len = RSA_public_decrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
-				 (unsigned char *)RSTRING_PTR(str), rsa, pad);
-    if (buf_len < 0) ossl_raise(eRSAError, NULL);
-    rb_str_set_len(str, buf_len);
-
-    return str;
-}
-
-/*
- * call-seq:
- *   rsa.private_encrypt(string)          => String
- *   rsa.private_encrypt(string, padding) => String
- *
- * Encrypt _string_ with the private key.  _padding_ defaults to PKCS1_PADDING.
- * The encrypted string output can be decrypted using #public_decrypt.
- */
-static VALUE
-ossl_rsa_private_encrypt(int argc, VALUE *argv, VALUE self)
-{
-    RSA *rsa;
-    const BIGNUM *rsa_n;
-    int buf_len, pad;
-    VALUE str, buffer, padding;
-
-    GetRSA(self, rsa);
-    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
-    if (!rsa_n)
-	ossl_raise(eRSAError, "incomplete RSA");
-    if (!RSA_PRIVATE(self, rsa))
-	ossl_raise(eRSAError, "private key needed.");
-    rb_scan_args(argc, argv, "11", &buffer, &padding);
-    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
-    StringValue(buffer);
-    str = rb_str_new(0, RSA_size(rsa));
-    buf_len = RSA_private_encrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
-				  (unsigned char *)RSTRING_PTR(str), rsa, pad);
-    if (buf_len < 0) ossl_raise(eRSAError, NULL);
-    rb_str_set_len(str, buf_len);
-
-    return str;
-}
-
-/*
- * call-seq:
- *   rsa.private_decrypt(string)          => String
- *   rsa.private_decrypt(string, padding) => String
- *
- * Decrypt _string_, which has been encrypted with the public key, with the
- * private key.  _padding_ defaults to PKCS1_PADDING.
- */
-static VALUE
-ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self)
-{
-    RSA *rsa;
-    const BIGNUM *rsa_n;
-    int buf_len, pad;
-    VALUE str, buffer, padding;
-
-    GetRSA(self, rsa);
-    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
-    if (!rsa_n)
-	ossl_raise(eRSAError, "incomplete RSA");
-    if (!RSA_PRIVATE(self, rsa))
-	ossl_raise(eRSAError, "private key needed.");
-    rb_scan_args(argc, argv, "11", &buffer, &padding);
-    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
-    StringValue(buffer);
-    str = rb_str_new(0, RSA_size(rsa));
-    buf_len = RSA_private_decrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
-				  (unsigned char *)RSTRING_PTR(str), rsa, pad);
-    if (buf_len < 0) ossl_raise(eRSAError, NULL);
-    rb_str_set_len(str, buf_len);
-
-    return str;
-}
-
 /*
  * call-seq:
  *    rsa.sign_pss(digest, data, salt_length:, mgf1_hash:) -> String
@@ -657,10 +525,6 @@ Init_ossl_rsa(void)
     rb_define_alias(cRSA, "to_pem", "export");
     rb_define_alias(cRSA, "to_s", "export");
     rb_define_method(cRSA, "to_der", ossl_rsa_to_der, 0);
-    rb_define_method(cRSA, "public_encrypt", ossl_rsa_public_encrypt, -1);
-    rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1);
-    rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1);
-    rb_define_method(cRSA, "private_decrypt", ossl_rsa_private_decrypt, -1);
     rb_define_method(cRSA, "sign_pss", ossl_rsa_sign_pss, -1);
     rb_define_method(cRSA, "verify_pss", ossl_rsa_verify_pss, -1);
 
@@ -678,11 +542,6 @@ Init_ossl_rsa(void)
 
     rb_define_method(cRSA, "params", ossl_rsa_get_params, 0);
 
-    DefRSAConst(PKCS1_PADDING);
-    DefRSAConst(SSLV23_PADDING);
-    DefRSAConst(NO_PADDING);
-    DefRSAConst(PKCS1_OAEP_PADDING);
-
 /*
  * TODO: Test it
     rb_define_method(cRSA, "blinding_on!", ossl_rsa_blinding_on, 0);
-- 
2.32.0


From d45a31cf70f5a55d7f6cf5082efc4dbb68d1169d Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Fri, 10 Jul 2020 13:43:20 +0900
Subject: [PATCH 5/6] pkey/ec: refactor EC#dsa_{sign,verify}_asn1 with
 PKey#{sign,verify}_raw

With the newly added OpenSSL::PKey::PKey#{sign,verify}_raw,
OpenSSL::PKey::EC's low level signing operation methods can be
implemented in Ruby. The definitions are now in lib/openssl/pkey.rb.
---
 ext/openssl/lib/openssl/pkey.rb | 22 +++++++++++++
 ext/openssl/ossl_pkey_ec.c      | 55 ---------------------------------
 2 files changed, 22 insertions(+), 55 deletions(-)

diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
index dd8c7c0b09..e587109694 100644
--- a/ext/openssl/lib/openssl/pkey.rb
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -164,6 +164,28 @@ def new(*args, &blk) # :nodoc:
   class EC
     include OpenSSL::Marshal
 
+    # :call-seq:
+    #    key.dsa_sign_asn1(data) -> String
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+    def dsa_sign_asn1(data)
+      sign_raw(nil, data)
+    rescue OpenSSL::PKey::PKeyError
+      raise OpenSSL::PKey::ECError, $!.message
+    end
+
+    # :call-seq:
+    #    key.dsa_verify_asn1(data, sig) -> true | false
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+    def dsa_verify_asn1(data, sig)
+      verify_raw(nil, sig, data)
+    rescue OpenSSL::PKey::PKeyError
+      raise OpenSSL::PKey::ECError, $!.message
+    end
+
     # :call-seq:
     #    ec.dh_compute_key(pubkey) -> string
     #
diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c
index 829529d4b9..f52e67079d 100644
--- a/ext/openssl/ossl_pkey_ec.c
+++ b/ext/openssl/ossl_pkey_ec.c
@@ -476,57 +476,6 @@ static VALUE ossl_ec_key_check_key(VALUE self)
     return Qtrue;
 }
 
-/*
- *  call-seq:
- *     key.dsa_sign_asn1(data)   => String
- *
- *  See the OpenSSL documentation for ECDSA_sign()
- */
-static VALUE ossl_ec_key_dsa_sign_asn1(VALUE self, VALUE data)
-{
-    EC_KEY *ec;
-    unsigned int buf_len;
-    VALUE str;
-
-    GetEC(self, ec);
-    StringValue(data);
-
-    if (EC_KEY_get0_private_key(ec) == NULL)
-	ossl_raise(eECError, "Private EC key needed!");
-
-    str = rb_str_new(0, ECDSA_size(ec));
-    if (ECDSA_sign(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(str), &buf_len, ec) != 1)
-	ossl_raise(eECError, "ECDSA_sign");
-    rb_str_set_len(str, buf_len);
-
-    return str;
-}
-
-/*
- *  call-seq:
- *     key.dsa_verify_asn1(data, sig)   => true or false
- *
- *  See the OpenSSL documentation for ECDSA_verify()
- */
-static VALUE ossl_ec_key_dsa_verify_asn1(VALUE self, VALUE data, VALUE sig)
-{
-    EC_KEY *ec;
-
-    GetEC(self, ec);
-    StringValue(data);
-    StringValue(sig);
-
-    switch (ECDSA_verify(0, (unsigned char *)RSTRING_PTR(data), RSTRING_LENINT(data),
-                         (unsigned char *)RSTRING_PTR(sig), RSTRING_LENINT(sig), ec)) {
-      case 1:
-        return Qtrue;
-      case 0:
-        return Qfalse;
-      default:
-        ossl_raise(eECError, "ECDSA_verify");
-    }
-}
-
 /*
  * OpenSSL::PKey::EC::Group
  */
@@ -1615,10 +1564,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, "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 */
-
     rb_define_method(cEC, "export", ossl_ec_key_export, -1);
     rb_define_alias(cEC, "to_pem", "export");
     rb_define_method(cEC, "to_der", ossl_ec_key_to_der, 0);
-- 
2.32.0


From 2494043e302c920e90e06cce443c5cd428e183f7 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Fri, 10 Jul 2020 13:51:18 +0900
Subject: [PATCH 6/6] pkey/dsa: refactor DSA#sys{sign,verify} with
 PKey#{sign,verify}_raw

With the newly added OpenSSL::PKey::PKey#{sign,verify}_raw,
OpenSSL::PKey::DSA's low level signing operation methods can be
implemented in Ruby. The definitions are now in lib/openssl/pkey.rb.
---
 ext/openssl/lib/openssl/pkey.rb | 54 ++++++++++++++++++++
 ext/openssl/ossl_pkey_dsa.c     | 88 ---------------------------------
 2 files changed, 54 insertions(+), 88 deletions(-)

diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
index e587109694..f6bf5892b0 100644
--- a/ext/openssl/lib/openssl/pkey.rb
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -158,6 +158,60 @@ def new(*args, &blk) # :nodoc:
         end
       end
     end
+
+    # :call-seq:
+    #    dsa.syssign(string) -> string
+    #
+    # Computes and returns the \DSA signature of +string+, where +string+ is
+    # expected to be an already-computed message digest of the original input
+    # data. The signature is issued using the private key of this DSA instance.
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+    #
+    # +string+::
+    #   A message digest of the original input data to be signed.
+    #
+    # Example:
+    #   dsa = OpenSSL::PKey::DSA.new(2048)
+    #   doc = "Sign me"
+    #   digest = OpenSSL::Digest.digest('SHA1', doc)
+    #
+    #   # With legacy #syssign and #sysverify:
+    #   sig = dsa.syssign(digest)
+    #   p dsa.sysverify(digest, sig) #=> true
+    #
+    #   # With #sign_raw and #verify_raw:
+    #   sig = dsa.sign_raw(nil, digest)
+    #   p dsa.verify_raw(nil, sig, digest) #=> true
+    def syssign(string)
+      q or raise OpenSSL::PKey::DSAError, "incomplete DSA"
+      private? or raise OpenSSL::PKey::DSAError, "Private DSA key needed!"
+      begin
+        sign_raw(nil, string)
+      rescue OpenSSL::PKey::PKeyError
+        raise OpenSSL::PKey::DSAError, $!.message
+      end
+    end
+
+    # :call-seq:
+    #    dsa.sysverify(digest, sig) -> true | false
+    #
+    # Verifies whether the signature is valid given the message digest input.
+    # It does so by validating +sig+ using the public key of this DSA instance.
+    #
+    # <b>Deprecated in version 3.0</b>.
+    # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+    #
+    # +digest+::
+    #   A message digest of the original input data to be signed.
+    # +sig+::
+    #   A \DSA signature value.
+    def sysverify(digest, sig)
+      verify_raw(nil, sig, digest)
+    rescue OpenSSL::PKey::PKeyError
+      raise OpenSSL::PKey::DSAError, $!.message
+    end
   end
 
   if defined?(EC)
diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c
index ab9ac781e8..7af00eebec 100644
--- a/ext/openssl/ossl_pkey_dsa.c
+++ b/ext/openssl/ossl_pkey_dsa.c
@@ -264,92 +264,6 @@ ossl_dsa_get_params(VALUE self)
     return hash;
 }
 
-/*
- *  call-seq:
- *    dsa.syssign(string) -> aString
- *
- * Computes and returns the DSA signature of _string_, where _string_ is
- * expected to be an already-computed message digest of the original input
- * data. The signature is issued using the private key of this DSA instance.
- *
- * === Parameters
- * * _string_ is a message digest of the original input data to be signed.
- *
- * === Example
- *  dsa = OpenSSL::PKey::DSA.new(2048)
- *  doc = "Sign me"
- *  digest = OpenSSL::Digest.digest('SHA1', doc)
- *  sig = dsa.syssign(digest)
- *
- *
- */
-static VALUE
-ossl_dsa_sign(VALUE self, VALUE data)
-{
-    DSA *dsa;
-    const BIGNUM *dsa_q;
-    unsigned int buf_len;
-    VALUE str;
-
-    GetDSA(self, dsa);
-    DSA_get0_pqg(dsa, NULL, &dsa_q, NULL);
-    if (!dsa_q)
-	ossl_raise(eDSAError, "incomplete DSA");
-    if (!DSA_PRIVATE(self, dsa))
-	ossl_raise(eDSAError, "Private DSA key needed!");
-    StringValue(data);
-    str = rb_str_new(0, DSA_size(dsa));
-    if (!DSA_sign(0, (unsigned char *)RSTRING_PTR(data), RSTRING_LENINT(data),
-		  (unsigned char *)RSTRING_PTR(str),
-		  &buf_len, dsa)) { /* type is ignored (0) */
-	ossl_raise(eDSAError, NULL);
-    }
-    rb_str_set_len(str, buf_len);
-
-    return str;
-}
-
-/*
- *  call-seq:
- *    dsa.sysverify(digest, sig) -> true | false
- *
- * Verifies whether the signature is valid given the message digest input. It
- * does so by validating _sig_ using the public key of this DSA instance.
- *
- * === Parameters
- * * _digest_ is a message digest of the original input data to be signed
- * * _sig_ is a DSA signature value
- *
- * === Example
- *  dsa = OpenSSL::PKey::DSA.new(2048)
- *  doc = "Sign me"
- *  digest = OpenSSL::Digest.digest('SHA1', doc)
- *  sig = dsa.syssign(digest)
- *  puts dsa.sysverify(digest, sig) # => true
- *
- */
-static VALUE
-ossl_dsa_verify(VALUE self, VALUE digest, VALUE sig)
-{
-    DSA *dsa;
-    int ret;
-
-    GetDSA(self, dsa);
-    StringValue(digest);
-    StringValue(sig);
-    /* type is ignored (0) */
-    ret = DSA_verify(0, (unsigned char *)RSTRING_PTR(digest), RSTRING_LENINT(digest),
-		     (unsigned char *)RSTRING_PTR(sig), RSTRING_LENINT(sig), dsa);
-    if (ret < 0) {
-	ossl_raise(eDSAError, NULL);
-    }
-    else if (ret == 1) {
-	return Qtrue;
-    }
-
-    return Qfalse;
-}
-
 /*
  * Document-method: OpenSSL::PKey::DSA#set_pqg
  * call-seq:
@@ -404,8 +318,6 @@ Init_ossl_dsa(void)
     rb_define_alias(cDSA, "to_pem", "export");
     rb_define_alias(cDSA, "to_s", "export");
     rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0);
-    rb_define_method(cDSA, "syssign", ossl_dsa_sign, 1);
-    rb_define_method(cDSA, "sysverify", ossl_dsa_verify, 2);
 
     DEF_OSSL_PKEY_BN(cDSA, dsa, p);
     DEF_OSSL_PKEY_BN(cDSA, dsa, q);
-- 
2.32.0

