Skip to content

pkey: allow setting algorithm-specific options in #sign and #verify #374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 95 additions & 48 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
}

static VALUE
pkey_gen_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v))
pkey_ctx_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;
Expand All @@ -214,15 +214,25 @@ pkey_gen_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v))
}

static VALUE
pkey_gen_apply_options0(VALUE args_v)
pkey_ctx_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]);
pkey_ctx_apply_options_i, args[0]);
return Qnil;
}

static void
pkey_ctx_apply_options(EVP_PKEY_CTX *ctx, VALUE options, int *state)
{
VALUE args[2];
args[0] = (VALUE)ctx;
args[1] = options;

rb_protect(pkey_ctx_apply_options0, (VALUE)args, state);
}

struct pkey_blocking_generate_arg {
EVP_PKEY_CTX *ctx;
EVP_PKEY *pkey;
Expand Down Expand Up @@ -330,11 +340,7 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
}

if (!NIL_P(options)) {
VALUE args[2];

args[0] = (VALUE)ctx;
args[1] = options;
rb_protect(pkey_gen_apply_options0, (VALUE)args, &state);
pkey_ctx_apply_options(ctx, options, &state);
if (state) {
EVP_PKEY_CTX_free(ctx);
rb_jump_tag(state);
Expand Down Expand Up @@ -771,52 +777,79 @@ ossl_pkey_compare(VALUE self, VALUE other)
}

/*
* call-seq:
* pkey.sign(digest, data) -> String
* call-seq:
* pkey.sign(digest, data [, options]) -> string
*
* To sign the String _data_, _digest_, an instance of OpenSSL::Digest, must
* be provided. The return value is again a String containing the signature.
* A PKeyError is raised should errors occur.
* Any previous state of the Digest instance is irrelevant to the signature
* outcome, the digest instance is reset to its initial state during the
* operation.
* Hashes and signs the +data+ using a message digest algorithm +digest+ and
* a private key +pkey+.
*
* == Example
* data = 'Sign me!'
* digest = OpenSSL::Digest.new('SHA256')
* pkey = OpenSSL::PKey::RSA.new(2048)
* signature = pkey.sign(digest, data)
* See #verify for the verification operation.
*
* See also the man page EVP_DigestSign(3).
*
* +digest+::
* A String that represents the message digest algorithm name, or +nil+
* if the PKey type requires no digest algorithm.
* For backwards compatibility, this can be an instance of OpenSSL::Digest.
* Its state will not affect the signature.
* +data+::
* A String. The data to be hashed and 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.
* +options+ parameter was added in version 2.3.
*
* Example:
* data = "Sign me!"
* pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048)
* signopts = { rsa_padding_mode: "pss" }
* signature = pkey.sign("SHA256", data, signopts)
*
* # Creates a copy of the RSA key pkey, but without the private components
* pub_key = pkey.public_key
* puts pub_key.verify("SHA256", signature, data, signopts) # => true
*/
static VALUE
ossl_pkey_sign(VALUE self, VALUE digest, VALUE data)
ossl_pkey_sign(int argc, VALUE *argv, VALUE self)
{
EVP_PKEY *pkey;
VALUE digest, data, options, sig;
const EVP_MD *md = NULL;
EVP_MD_CTX *ctx;
EVP_PKEY_CTX *pctx;
size_t siglen;
int state;
VALUE sig;

pkey = GetPrivPKeyPtr(self);
rb_scan_args(argc, argv, "21", &digest, &data, &options);
if (!NIL_P(digest))
md = ossl_evp_get_digestbyname(digest);
StringValue(data);

ctx = EVP_MD_CTX_new();
if (!ctx)
ossl_raise(ePKeyError, "EVP_MD_CTX_new");
if (EVP_DigestSignInit(ctx, NULL, md, /* engine */NULL, pkey) < 1) {
if (EVP_DigestSignInit(ctx, &pctx, md, /* engine */NULL, pkey) < 1) {
EVP_MD_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_DigestSignInit");
}
if (!NIL_P(options)) {
pkey_ctx_apply_options(pctx, options, &state);
if (state) {
EVP_MD_CTX_free(ctx);
rb_jump_tag(state);
}
}
#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)
if (siglen > LONG_MAX) {
EVP_MD_CTX_free(ctx);
rb_raise(ePKeyError, "signature would be too large");
}
sig = ossl_str_new(NULL, (long)siglen, &state);
if (state) {
EVP_MD_CTX_free(ctx);
Expand All @@ -837,8 +870,10 @@ ossl_pkey_sign(VALUE self, VALUE digest, VALUE data)
EVP_MD_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_DigestSignFinal");
}
if (siglen > LONG_MAX)
if (siglen > LONG_MAX) {
EVP_MD_CTX_free(ctx);
rb_raise(ePKeyError, "signature would be too large");
}
sig = ossl_str_new(NULL, (long)siglen, &state);
if (state) {
EVP_MD_CTX_free(ctx);
Expand All @@ -856,35 +891,40 @@ ossl_pkey_sign(VALUE self, VALUE digest, VALUE data)
}

/*
* call-seq:
* pkey.verify(digest, signature, data) -> String
* call-seq:
* pkey.verify(digest, signature, data [, options]) -> true or false
*
* To verify the String _signature_, _digest_, an instance of
* OpenSSL::Digest, must be provided to re-compute the message digest of the
* original _data_, also a String. The return value is +true+ if the
* signature is valid, +false+ otherwise. A PKeyError is raised should errors
* occur.
* Any previous state of the Digest instance is irrelevant to the validation
* outcome, the digest instance is reset to its initial state during the
* operation.
* Verifies the +signature+ for the +data+ using a message digest algorithm
* +digest+ and a public key +pkey+.
*
* == Example
* data = 'Sign me!'
* digest = OpenSSL::Digest.new('SHA256')
* pkey = OpenSSL::PKey::RSA.new(2048)
* signature = pkey.sign(digest, data)
* pub_key = pkey.public_key
* puts pub_key.verify(digest, signature, data) # => true
* Returns +true+ if the signature is successfully verified, +false+ otherwise.
* The caller must check the return value.
*
* See #sign for the signing operation and an example.
*
* See also the man page EVP_DigestVerify(3).
*
* +digest+::
* See #sign.
* +signature+::
* A String containing the signature to be verified.
* +data+::
* See #sign.
* +options+::
* See #sign. +options+ parameter was added in version 2.3.
*/
static VALUE
ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
ossl_pkey_verify(int argc, VALUE *argv, VALUE self)
{
EVP_PKEY *pkey;
VALUE digest, sig, data, options;
const EVP_MD *md = NULL;
EVP_MD_CTX *ctx;
int ret;
EVP_PKEY_CTX *pctx;
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);
Expand All @@ -894,10 +934,17 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
ctx = EVP_MD_CTX_new();
if (!ctx)
ossl_raise(ePKeyError, "EVP_MD_CTX_new");
if (EVP_DigestVerifyInit(ctx, NULL, md, /* engine */NULL, pkey) < 1) {
if (EVP_DigestVerifyInit(ctx, &pctx, md, /* engine */NULL, pkey) < 1) {
EVP_MD_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_DigestVerifyInit");
}
if (!NIL_P(options)) {
pkey_ctx_apply_options(pctx, options, &state);
if (state) {
EVP_MD_CTX_free(ctx);
rb_jump_tag(state);
}
}
#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),
Expand Down Expand Up @@ -1071,8 +1118,8 @@ Init_ossl_pkey(void)
rb_define_method(cPKey, "public_to_pem", ossl_pkey_public_to_pem, 0);
rb_define_method(cPKey, "compare?", ossl_pkey_compare, 1);

rb_define_method(cPKey, "sign", ossl_pkey_sign, 2);
rb_define_method(cPKey, "verify", ossl_pkey_verify, 3);
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);

id_private_q = rb_intern("private?");
Expand Down
34 changes: 14 additions & 20 deletions test/openssl/test_pkey_rsa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,27 +117,21 @@ def test_sign_verify
assert_equal false, rsa1024.verify("SHA256", signature1, data)
end

def test_digest_state_irrelevant_sign
def test_sign_verify_options
key = Fixtures.pkey("rsa1024")
digest1 = OpenSSL::Digest.new('SHA1')
digest2 = OpenSSL::Digest.new('SHA1')
data = 'Sign me!'
digest1 << 'Change state of digest1'
sig1 = key.sign(digest1, data)
sig2 = key.sign(digest2, data)
assert_equal(sig1, sig2)
end

def test_digest_state_irrelevant_verify
key = Fixtures.pkey("rsa1024")
digest1 = OpenSSL::Digest.new('SHA1')
digest2 = OpenSSL::Digest.new('SHA1')
data = 'Sign me!'
sig = key.sign(digest1, data)
digest1.reset
digest1 << 'Change state of digest1'
assert(key.verify(digest1, sig, data))
assert(key.verify(digest2, sig, data))
data = "Sign me!"
pssopts = {
"rsa_padding_mode" => "pss",
"rsa_pss_saltlen" => 20,
"rsa_mgf1_md" => "SHA1"
}
sig_pss = key.sign("SHA256", data, pssopts)
assert_equal 128, sig_pss.bytesize
assert_equal true, key.verify("SHA256", sig_pss, data, pssopts)
assert_equal true, key.verify_pss("SHA256", sig_pss, data,
salt_length: 20, mgf1_hash: "SHA1")
# Defaults to PKCS #1 v1.5 padding => verification failure
assert_equal false, key.verify("SHA256", sig_pss, data)
end

def test_verify_empty_rsa
Expand Down