[ruby/openssl] 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.

https://github.com/ruby/openssl/commit/16cca4e0c4
This commit is contained in:
Kazuki Yamaguchi 2020-05-22 16:10:35 +09:00
Родитель cbc560e38f
Коммит 4ebff35971
4 изменённых файлов: 325 добавлений и 31 удалений

Просмотреть файл

@ -973,6 +973,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
@ -1262,6 +1491,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);

Просмотреть файл

@ -48,12 +48,31 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
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

Просмотреть файл

@ -109,13 +109,30 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
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

Просмотреть файл

@ -13,32 +13,6 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
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 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
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]") {