Skip to content

Commit 417f5b8

Browse files
Peter Karmanrhenium
authored andcommitted
Add RSA sign_pss() and verify_pss() methods
Support Probabilistic Signature Scheme for RSA key signing. [ky: the patch was originally submitted as GitHub Pull Request #76. finish keyword arguments handling, update docs, and fix tests.]
1 parent e72d960 commit 417f5b8

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

ext/openssl/ossl_pkey_rsa.c

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,196 @@ ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self)
536536
return str;
537537
}
538538

539+
/*
540+
* call-seq:
541+
* rsa.sign_pss(digest, data, salt_length:, mgf1_hash:) -> String
542+
*
543+
* Signs _data_ using the Probabilistic Signature Scheme (RSA-PSS) and returns
544+
* the calculated signature.
545+
*
546+
* RSAError will be raised if an error occurs.
547+
*
548+
* See #verify_pss for the verification operation.
549+
*
550+
* === Parameters
551+
* _digest_::
552+
* A String containing the message digest algorithm name.
553+
* _data_::
554+
* A String. The data to be signed.
555+
* _salt_length_::
556+
* The length in octets of the salt. Two special values are reserved:
557+
* +:digest+ means the digest length, and +:max+ means the maximum possible
558+
* length for the combination of the private key and the selected message
559+
* digest algorithm.
560+
* _mgf1_hash_::
561+
* The hash algorithm used in MGF1 (the currently supported mask generation
562+
* function (MGF)).
563+
*
564+
* === Example
565+
* data = "Sign me!"
566+
* pkey = OpenSSL::PKey::RSA.new(2048)
567+
* signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
568+
* pub_key = pkey.public_key
569+
* puts pub_key.verify_pss("SHA256", signature, data,
570+
* salt_length: :auto, mgf1_hash: "SHA256") # => true
571+
*/
572+
static VALUE
573+
ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self)
574+
{
575+
VALUE digest, data, options, kwargs[2], signature;
576+
static ID kwargs_ids[2];
577+
EVP_PKEY *pkey;
578+
EVP_PKEY_CTX *pkey_ctx;
579+
const EVP_MD *md, *mgf1md;
580+
EVP_MD_CTX *md_ctx;
581+
size_t buf_len;
582+
int salt_len;
583+
584+
if (!kwargs_ids[0]) {
585+
kwargs_ids[0] = rb_intern_const("salt_length");
586+
kwargs_ids[1] = rb_intern_const("mgf1_hash");
587+
}
588+
rb_scan_args(argc, argv, "2:", &digest, &data, &options);
589+
rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs);
590+
if (kwargs[0] == ID2SYM(rb_intern("max")))
591+
salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */
592+
else if (kwargs[0] == ID2SYM(rb_intern("digest")))
593+
salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */
594+
else
595+
salt_len = NUM2INT(kwargs[0]);
596+
mgf1md = ossl_evp_get_digestbyname(kwargs[1]);
597+
598+
pkey = GetPrivPKeyPtr(self);
599+
buf_len = EVP_PKEY_size(pkey);
600+
md = ossl_evp_get_digestbyname(digest);
601+
StringValue(data);
602+
signature = rb_str_new(NULL, (long)buf_len);
603+
604+
md_ctx = EVP_MD_CTX_new();
605+
if (!md_ctx)
606+
goto err;
607+
608+
if (EVP_DigestSignInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1)
609+
goto err;
610+
611+
if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1)
612+
goto err;
613+
614+
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1)
615+
goto err;
616+
617+
if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1)
618+
goto err;
619+
620+
if (EVP_DigestSignUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1)
621+
goto err;
622+
623+
if (EVP_DigestSignFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), &buf_len) != 1)
624+
goto err;
625+
626+
rb_str_set_len(signature, (long)buf_len);
627+
628+
EVP_MD_CTX_free(md_ctx);
629+
return signature;
630+
631+
err:
632+
EVP_MD_CTX_free(md_ctx);
633+
ossl_raise(eRSAError, NULL);
634+
}
635+
636+
/*
637+
* call-seq:
638+
* rsa.verify_pss(digest, signature, data, salt_length:, mgf1_hash:) -> true | false
639+
*
640+
* Verifies _data_ using the Probabilistic Signature Scheme (RSA-PSS).
641+
*
642+
* The return value is +true+ if the signature is valid, +false+ otherwise.
643+
* RSAError will be raised if an error occurs.
644+
*
645+
* See #sign_pss for the signing operation and an example code.
646+
*
647+
* === Parameters
648+
* _digest_::
649+
* A String containing the message digest algorithm name.
650+
* _data_::
651+
* A String. The data to be signed.
652+
* _salt_length_::
653+
* The length in octets of the salt. Two special values are reserved:
654+
* +:digest+ means the digest length, and +:auto+ means automatically
655+
* determining the length based on the signature.
656+
* _mgf1_hash_::
657+
* The hash algorithm used in MGF1.
658+
*/
659+
static VALUE
660+
ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self)
661+
{
662+
VALUE digest, signature, data, options, kwargs[2];
663+
static ID kwargs_ids[2];
664+
EVP_PKEY *pkey;
665+
EVP_PKEY_CTX *pkey_ctx;
666+
const EVP_MD *md, *mgf1md;
667+
EVP_MD_CTX *md_ctx;
668+
int result, salt_len;
669+
670+
if (!kwargs_ids[0]) {
671+
kwargs_ids[0] = rb_intern_const("salt_length");
672+
kwargs_ids[1] = rb_intern_const("mgf1_hash");
673+
}
674+
rb_scan_args(argc, argv, "3:", &digest, &signature, &data, &options);
675+
rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs);
676+
if (kwargs[0] == ID2SYM(rb_intern("auto")))
677+
salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */
678+
else if (kwargs[0] == ID2SYM(rb_intern("digest")))
679+
salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */
680+
else
681+
salt_len = NUM2INT(kwargs[0]);
682+
mgf1md = ossl_evp_get_digestbyname(kwargs[1]);
683+
684+
GetPKey(self, pkey);
685+
md = ossl_evp_get_digestbyname(digest);
686+
StringValue(signature);
687+
StringValue(data);
688+
689+
md_ctx = EVP_MD_CTX_new();
690+
if (!md_ctx)
691+
goto err;
692+
693+
if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1)
694+
goto err;
695+
696+
if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1)
697+
goto err;
698+
699+
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1)
700+
goto err;
701+
702+
if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1)
703+
goto err;
704+
705+
if (EVP_DigestVerifyUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1)
706+
goto err;
707+
708+
result = EVP_DigestVerifyFinal(md_ctx,
709+
(unsigned char *)RSTRING_PTR(signature),
710+
RSTRING_LEN(signature));
711+
712+
switch (result) {
713+
case 0:
714+
ossl_clear_error();
715+
EVP_MD_CTX_free(md_ctx);
716+
return Qfalse;
717+
case 1:
718+
EVP_MD_CTX_free(md_ctx);
719+
return Qtrue;
720+
default:
721+
goto err;
722+
}
723+
724+
err:
725+
EVP_MD_CTX_free(md_ctx);
726+
ossl_raise(eRSAError, NULL);
727+
}
728+
539729
/*
540730
* call-seq:
541731
* rsa.params => hash
@@ -731,6 +921,8 @@ Init_ossl_rsa(void)
731921
rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1);
732922
rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1);
733923
rb_define_method(cRSA, "private_decrypt", ossl_rsa_private_decrypt, -1);
924+
rb_define_method(cRSA, "sign_pss", ossl_rsa_sign_pss, -1);
925+
rb_define_method(cRSA, "verify_pss", ossl_rsa_verify_pss, -1);
734926

735927
DEF_OSSL_PKEY_BN(cRSA, rsa, n);
736928
DEF_OSSL_PKEY_BN(cRSA, rsa, e);

test/test_pkey_rsa.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,39 @@ def test_verify_empty_rsa
113113
}
114114
end
115115

116+
def test_sign_verify_pss
117+
key = Fixtures.pkey("rsa1024")
118+
data = "Sign me!"
119+
invalid_data = "Sign me?"
120+
121+
signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA1")
122+
assert_equal 128, signature.bytesize
123+
assert_equal true,
124+
key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1")
125+
assert_equal true,
126+
key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1")
127+
assert_equal false,
128+
key.verify_pss("SHA256", signature, invalid_data, salt_length: 20, mgf1_hash: "SHA1")
129+
130+
signature = key.sign_pss("SHA256", data, salt_length: :digest, mgf1_hash: "SHA1")
131+
assert_equal true,
132+
key.verify_pss("SHA256", signature, data, salt_length: 32, mgf1_hash: "SHA1")
133+
assert_equal true,
134+
key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1")
135+
assert_equal false,
136+
key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1")
137+
138+
signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA1")
139+
assert_equal true,
140+
key.verify_pss("SHA256", signature, data, salt_length: 94, mgf1_hash: "SHA1")
141+
assert_equal true,
142+
key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1")
143+
144+
assert_raise(OpenSSL::PKey::RSAError) {
145+
key.sign_pss("SHA256", data, salt_length: 95, mgf1_hash: "SHA1")
146+
}
147+
end
148+
116149
def test_RSAPrivateKey
117150
rsa1024 = Fixtures.pkey("rsa1024")
118151
asn1 = OpenSSL::ASN1::Sequence([

0 commit comments

Comments
 (0)