Skip to content

Commit faf85d7

Browse files
committed
pkey: allow setting algorithm-specific options in #sign and #verify
Similarly to OpenSSL::PKey.generate_key and .generate_parameters, let OpenSSL::PKey::PKey#sign and #verify take an optional parameter for specifying control strings for EVP_PKEY_CTX_ctrl_str().
1 parent b2b7752 commit faf85d7

File tree

2 files changed

+89
-58
lines changed

2 files changed

+89
-58
lines changed

ext/openssl/ossl_pkey.c

+75-38
Original file line numberDiff line numberDiff line change
@@ -777,44 +777,69 @@ ossl_pkey_compare(VALUE self, VALUE other)
777777
}
778778

779779
/*
780-
* call-seq:
781-
* pkey.sign(digest, data) -> String
780+
* call-seq:
781+
* pkey.sign(digest, data [, options]) -> string
782782
*
783-
* To sign the String _data_, _digest_, an instance of OpenSSL::Digest, must
784-
* be provided. The return value is again a String containing the signature.
785-
* A PKeyError is raised should errors occur.
786-
* Any previous state of the Digest instance is irrelevant to the signature
787-
* outcome, the digest instance is reset to its initial state during the
788-
* operation.
783+
* Hashes and signs the +data+ using a message digest algorithm +digest+ and
784+
* a private key +pkey+.
789785
*
790-
* == Example
791-
* data = 'Sign me!'
792-
* digest = OpenSSL::Digest.new('SHA256')
793-
* pkey = OpenSSL::PKey::RSA.new(2048)
794-
* signature = pkey.sign(digest, data)
786+
* See #verify for the verification operation.
787+
*
788+
* See also the man page EVP_DigestSign(3).
789+
*
790+
* +digest+::
791+
* A String that represents the message digest algorithm name, or +nil+
792+
* if the PKey type requires no digest algorithm.
793+
* For backwards compatibility, this can be an instance of OpenSSL::Digest.
794+
* Its state will not affect the signature.
795+
* +data+::
796+
* A String. The data to be hashed and signed.
797+
* +options+::
798+
* A Hash that contains algorithm specific control operations to \OpenSSL.
799+
* See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details.
800+
* +options+ parameter was added in version 2.3.
801+
*
802+
* Example:
803+
* data = "Sign me!"
804+
* pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048)
805+
* signopts = { rsa_padding_mode: "pss" }
806+
* signature = pkey.sign("SHA256", data, signopts)
807+
*
808+
* # Creates a copy of the RSA key pkey, but without the private components
809+
* pub_key = pkey.public_key
810+
* puts pub_key.verify("SHA256", signature, data, signopts) # => true
795811
*/
796812
static VALUE
797-
ossl_pkey_sign(VALUE self, VALUE digest, VALUE data)
813+
ossl_pkey_sign(int argc, VALUE *argv, VALUE self)
798814
{
799815
EVP_PKEY *pkey;
816+
VALUE digest, data, options, sig;
800817
const EVP_MD *md = NULL;
801818
EVP_MD_CTX *ctx;
819+
EVP_PKEY_CTX *pctx;
802820
size_t siglen;
803821
int state;
804-
VALUE sig;
805822

806823
pkey = GetPrivPKeyPtr(self);
824+
rb_scan_args(argc, argv, "21", &digest, &data, &options);
807825
if (!NIL_P(digest))
808826
md = ossl_evp_get_digestbyname(digest);
809827
StringValue(data);
810828

811829
ctx = EVP_MD_CTX_new();
812830
if (!ctx)
813831
ossl_raise(ePKeyError, "EVP_MD_CTX_new");
814-
if (EVP_DigestSignInit(ctx, NULL, md, /* engine */NULL, pkey) < 1) {
832+
if (EVP_DigestSignInit(ctx, &pctx, md, /* engine */NULL, pkey) < 1) {
815833
EVP_MD_CTX_free(ctx);
816834
ossl_raise(ePKeyError, "EVP_DigestSignInit");
817835
}
836+
if (!NIL_P(options)) {
837+
pkey_ctx_apply_options(pctx, options, &state);
838+
if (state) {
839+
EVP_MD_CTX_free(ctx);
840+
rb_jump_tag(state);
841+
}
842+
}
818843
#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
819844
if (EVP_DigestSign(ctx, NULL, &siglen, (unsigned char *)RSTRING_PTR(data),
820845
RSTRING_LEN(data)) < 1) {
@@ -866,35 +891,40 @@ ossl_pkey_sign(VALUE self, VALUE digest, VALUE data)
866891
}
867892

868893
/*
869-
* call-seq:
870-
* pkey.verify(digest, signature, data) -> String
894+
* call-seq:
895+
* pkey.verify(digest, signature, data [, options]) -> true or false
871896
*
872-
* To verify the String _signature_, _digest_, an instance of
873-
* OpenSSL::Digest, must be provided to re-compute the message digest of the
874-
* original _data_, also a String. The return value is +true+ if the
875-
* signature is valid, +false+ otherwise. A PKeyError is raised should errors
876-
* occur.
877-
* Any previous state of the Digest instance is irrelevant to the validation
878-
* outcome, the digest instance is reset to its initial state during the
879-
* operation.
897+
* Verifies the +signature+ for the +data+ using a message digest algorithm
898+
* +digest+ and a public key +pkey+.
880899
*
881-
* == Example
882-
* data = 'Sign me!'
883-
* digest = OpenSSL::Digest.new('SHA256')
884-
* pkey = OpenSSL::PKey::RSA.new(2048)
885-
* signature = pkey.sign(digest, data)
886-
* pub_key = pkey.public_key
887-
* puts pub_key.verify(digest, signature, data) # => true
900+
* Returns +true+ if the signature is successfully verified, +false+ otherwise.
901+
* The caller must check the return value.
902+
*
903+
* See #sign for the signing operation and an example.
904+
*
905+
* See also the man page EVP_DigestVerify(3).
906+
*
907+
* +digest+::
908+
* See #sign.
909+
* +signature+::
910+
* A String containing the signature to be verified.
911+
* +data+::
912+
* See #sign.
913+
* +options+::
914+
* See #sign. +options+ parameter was added in version 2.3.
888915
*/
889916
static VALUE
890-
ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
917+
ossl_pkey_verify(int argc, VALUE *argv, VALUE self)
891918
{
892919
EVP_PKEY *pkey;
920+
VALUE digest, sig, data, options;
893921
const EVP_MD *md = NULL;
894922
EVP_MD_CTX *ctx;
895-
int ret;
923+
EVP_PKEY_CTX *pctx;
924+
int state, ret;
896925

897926
GetPKey(self, pkey);
927+
rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options);
898928
ossl_pkey_check_public_key(pkey);
899929
if (!NIL_P(digest))
900930
md = ossl_evp_get_digestbyname(digest);
@@ -904,10 +934,17 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
904934
ctx = EVP_MD_CTX_new();
905935
if (!ctx)
906936
ossl_raise(ePKeyError, "EVP_MD_CTX_new");
907-
if (EVP_DigestVerifyInit(ctx, NULL, md, /* engine */NULL, pkey) < 1) {
937+
if (EVP_DigestVerifyInit(ctx, &pctx, md, /* engine */NULL, pkey) < 1) {
908938
EVP_MD_CTX_free(ctx);
909939
ossl_raise(ePKeyError, "EVP_DigestVerifyInit");
910940
}
941+
if (!NIL_P(options)) {
942+
pkey_ctx_apply_options(pctx, options, &state);
943+
if (state) {
944+
EVP_MD_CTX_free(ctx);
945+
rb_jump_tag(state);
946+
}
947+
}
911948
#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
912949
ret = EVP_DigestVerify(ctx, (unsigned char *)RSTRING_PTR(sig),
913950
RSTRING_LEN(sig), (unsigned char *)RSTRING_PTR(data),
@@ -1081,8 +1118,8 @@ Init_ossl_pkey(void)
10811118
rb_define_method(cPKey, "public_to_pem", ossl_pkey_public_to_pem, 0);
10821119
rb_define_method(cPKey, "compare?", ossl_pkey_compare, 1);
10831120

1084-
rb_define_method(cPKey, "sign", ossl_pkey_sign, 2);
1085-
rb_define_method(cPKey, "verify", ossl_pkey_verify, 3);
1121+
rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
1122+
rb_define_method(cPKey, "verify", ossl_pkey_verify, -1);
10861123
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
10871124

10881125
id_private_q = rb_intern("private?");

test/openssl/test_pkey_rsa.rb

+14-20
Original file line numberDiff line numberDiff line change
@@ -117,27 +117,21 @@ def test_sign_verify
117117
assert_equal false, rsa1024.verify("SHA256", signature1, data)
118118
end
119119

120-
def test_digest_state_irrelevant_sign
120+
def test_sign_verify_options
121121
key = Fixtures.pkey("rsa1024")
122-
digest1 = OpenSSL::Digest.new('SHA1')
123-
digest2 = OpenSSL::Digest.new('SHA1')
124-
data = 'Sign me!'
125-
digest1 << 'Change state of digest1'
126-
sig1 = key.sign(digest1, data)
127-
sig2 = key.sign(digest2, data)
128-
assert_equal(sig1, sig2)
129-
end
130-
131-
def test_digest_state_irrelevant_verify
132-
key = Fixtures.pkey("rsa1024")
133-
digest1 = OpenSSL::Digest.new('SHA1')
134-
digest2 = OpenSSL::Digest.new('SHA1')
135-
data = 'Sign me!'
136-
sig = key.sign(digest1, data)
137-
digest1.reset
138-
digest1 << 'Change state of digest1'
139-
assert(key.verify(digest1, sig, data))
140-
assert(key.verify(digest2, sig, data))
122+
data = "Sign me!"
123+
pssopts = {
124+
"rsa_padding_mode" => "pss",
125+
"rsa_pss_saltlen" => 20,
126+
"rsa_mgf1_md" => "SHA1"
127+
}
128+
sig_pss = key.sign("SHA256", data, pssopts)
129+
assert_equal 128, sig_pss.bytesize
130+
assert_equal true, key.verify("SHA256", sig_pss, data, pssopts)
131+
assert_equal true, key.verify_pss("SHA256", sig_pss, data,
132+
salt_length: 20, mgf1_hash: "SHA1")
133+
# Defaults to PKCS #1 v1.5 padding => verification failure
134+
assert_equal false, key.verify("SHA256", sig_pss, data)
141135
end
142136

143137
def test_verify_empty_rsa

0 commit comments

Comments
 (0)