diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index b92c8a663..203ab789c 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -799,7 +799,7 @@ ossl_pkey_compare(VALUE self, VALUE other) * +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!" @@ -913,7 +913,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) @@ -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 @@ -1024,6 +1253,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 */ @@ -1123,7 +1491,12 @@ 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); id_private_q = rb_intern("private?"); diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index ab9ac781e..7af00eebe 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); diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index af59cfabd..1c97e9aab 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -471,57 +471,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), (int)RSTRING_LEN(sig), ec)) { - case 1: return Qtrue; - case 0: return Qfalse; - default: break; - } - - ossl_raise(eECError, "ECDSA_verify"); - - UNREACHABLE; -} - /* * OpenSSL::PKey::EC::Group */ @@ -1583,10 +1532,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); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 1c5476cdc..8ebd3ec55 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); diff --git a/lib/openssl/pkey.rb b/lib/openssl/pkey.rb index 569559e1c..f6bf5892b 100644 --- a/lib/openssl/pkey.rb +++ b/lib/openssl/pkey.rb @@ -158,12 +158,88 @@ 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. + # + # Deprecated in version 3.0. + # 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. + # + # Deprecated in version 3.0. + # 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) class EC include OpenSSL::Marshal + # :call-seq: + # key.dsa_sign_asn1(data) -> String + # + # Deprecated in version 3.0. + # 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 + # + # Deprecated in version 3.0. + # 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 # @@ -243,5 +319,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. + # + # Deprecated in version 3.0. + # 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. + # + # Deprecated in version 3.0. + # 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. + # + # Deprecated in version 3.0. + # 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. + # + # Deprecated in version 3.0. + # 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/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index 85bb6ec0a..147e50176 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 80ae9ffdf..0a460bd53 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 5f8d04e75..5e127f540 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]") { @@ -173,6 +199,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