Skip to content

Commit 641bafa

Browse files
committed
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.
1 parent b8a434e commit 641bafa

File tree

4 files changed

+325
-31
lines changed

4 files changed

+325
-31
lines changed

ext/openssl/ossl_pkey.c

+232
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,235 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self)
973973
}
974974
}
975975

976+
/*
977+
* call-seq:
978+
* pkey.sign_raw(digest, data [, options]) -> string
979+
*
980+
* Signs +data+ using a private key +pkey+. Unlike #sign, +data+ will not be
981+
* hashed by +digest+ automatically.
982+
*
983+
* See #verify_raw for the verification operation.
984+
*
985+
* Added in version 3.0. See also the man page EVP_PKEY_sign(3).
986+
*
987+
* +digest+::
988+
* A String that represents the message digest algorithm name, or +nil+
989+
* if the PKey type requires no digest algorithm.
990+
* Although this method will not hash +data+ with it, this parameter may still
991+
* be required depending on the signature algorithm.
992+
* +data+::
993+
* A String. The data to be signed.
994+
* +options+::
995+
* A Hash that contains algorithm specific control operations to \OpenSSL.
996+
* See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details.
997+
*
998+
* Example:
999+
* data = "Sign me!"
1000+
* hash = OpenSSL::Digest.digest("SHA256", data)
1001+
* pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048)
1002+
* signopts = { rsa_padding_mode: "pss" }
1003+
* signature = pkey.sign_raw("SHA256", hash, signopts)
1004+
*
1005+
* # Creates a copy of the RSA key pkey, but without the private components
1006+
* pub_key = pkey.public_key
1007+
* puts pub_key.verify_raw("SHA256", signature, hash, signopts) # => true
1008+
*/
1009+
static VALUE
1010+
ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self)
1011+
{
1012+
EVP_PKEY *pkey;
1013+
VALUE digest, data, options, sig;
1014+
const EVP_MD *md = NULL;
1015+
EVP_PKEY_CTX *ctx;
1016+
size_t outlen;
1017+
int state;
1018+
1019+
GetPKey(self, pkey);
1020+
rb_scan_args(argc, argv, "21", &digest, &data, &options);
1021+
if (!NIL_P(digest))
1022+
md = ossl_evp_get_digestbyname(digest);
1023+
StringValue(data);
1024+
1025+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1026+
if (!ctx)
1027+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1028+
if (EVP_PKEY_sign_init(ctx) <= 0) {
1029+
EVP_PKEY_CTX_free(ctx);
1030+
ossl_raise(ePKeyError, "EVP_PKEY_sign_init");
1031+
}
1032+
if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) {
1033+
EVP_PKEY_CTX_free(ctx);
1034+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md");
1035+
}
1036+
if (!NIL_P(options)) {
1037+
pkey_ctx_apply_options(ctx, options, &state);
1038+
if (state) {
1039+
EVP_PKEY_CTX_free(ctx);
1040+
rb_jump_tag(state);
1041+
}
1042+
}
1043+
if (EVP_PKEY_sign(ctx, NULL, &outlen, (unsigned char *)RSTRING_PTR(data),
1044+
RSTRING_LEN(data)) <= 0) {
1045+
EVP_PKEY_CTX_free(ctx);
1046+
ossl_raise(ePKeyError, "EVP_PKEY_sign");
1047+
}
1048+
if (outlen > LONG_MAX) {
1049+
EVP_PKEY_CTX_free(ctx);
1050+
rb_raise(ePKeyError, "signature would be too large");
1051+
}
1052+
sig = ossl_str_new(NULL, (long)outlen, &state);
1053+
if (state) {
1054+
EVP_PKEY_CTX_free(ctx);
1055+
rb_jump_tag(state);
1056+
}
1057+
if (EVP_PKEY_sign(ctx, (unsigned char *)RSTRING_PTR(sig), &outlen,
1058+
(unsigned char *)RSTRING_PTR(data),
1059+
RSTRING_LEN(data)) <= 0) {
1060+
EVP_PKEY_CTX_free(ctx);
1061+
ossl_raise(ePKeyError, "EVP_PKEY_sign");
1062+
}
1063+
EVP_PKEY_CTX_free(ctx);
1064+
rb_str_set_len(sig, outlen);
1065+
return sig;
1066+
}
1067+
1068+
/*
1069+
* call-seq:
1070+
* pkey.verify_raw(digest, signature, data [, options]) -> true or false
1071+
*
1072+
* Verifies the +signature+ for the +data+ using a public key +pkey+. Unlike
1073+
* #verify, this method will not hash +data+ with +digest+ automatically.
1074+
*
1075+
* Returns +true+ if the signature is successfully verified, +false+ otherwise.
1076+
* The caller must check the return value.
1077+
*
1078+
* See #sign_raw for the signing operation and an example.
1079+
*
1080+
* Added in version 3.0. See also the man page EVP_PKEY_verify(3).
1081+
*
1082+
* +signature+::
1083+
* A String containing the signature to be verified.
1084+
*/
1085+
static VALUE
1086+
ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self)
1087+
{
1088+
EVP_PKEY *pkey;
1089+
VALUE digest, sig, data, options;
1090+
const EVP_MD *md = NULL;
1091+
EVP_PKEY_CTX *ctx;
1092+
int state, ret;
1093+
1094+
GetPKey(self, pkey);
1095+
rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options);
1096+
ossl_pkey_check_public_key(pkey);
1097+
if (!NIL_P(digest))
1098+
md = ossl_evp_get_digestbyname(digest);
1099+
StringValue(sig);
1100+
StringValue(data);
1101+
1102+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1103+
if (!ctx)
1104+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1105+
if (EVP_PKEY_verify_init(ctx) <= 0) {
1106+
EVP_PKEY_CTX_free(ctx);
1107+
ossl_raise(ePKeyError, "EVP_PKEY_verify_init");
1108+
}
1109+
if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) {
1110+
EVP_PKEY_CTX_free(ctx);
1111+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md");
1112+
}
1113+
if (!NIL_P(options)) {
1114+
pkey_ctx_apply_options(ctx, options, &state);
1115+
if (state) {
1116+
EVP_PKEY_CTX_free(ctx);
1117+
rb_jump_tag(state);
1118+
}
1119+
}
1120+
ret = EVP_PKEY_verify(ctx, (unsigned char *)RSTRING_PTR(sig),
1121+
RSTRING_LEN(sig),
1122+
(unsigned char *)RSTRING_PTR(data),
1123+
RSTRING_LEN(data));
1124+
EVP_PKEY_CTX_free(ctx);
1125+
if (ret < 0)
1126+
ossl_raise(ePKeyError, "EVP_PKEY_verify");
1127+
1128+
if (ret)
1129+
return Qtrue;
1130+
else {
1131+
ossl_clear_error();
1132+
return Qfalse;
1133+
}
1134+
}
1135+
1136+
/*
1137+
* call-seq:
1138+
* pkey.verify_recover(digest, signature [, options]) -> string
1139+
*
1140+
* Recovers the signed data from +signature+ using a public key +pkey+. Not all
1141+
* signature algorithm supports this operation.
1142+
*
1143+
* Added in version 3.0. See also the man page EVP_PKEY_verify_recover(3).
1144+
*
1145+
* +signature+::
1146+
* A String containing the signature to be verified.
1147+
*/
1148+
static VALUE
1149+
ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self)
1150+
{
1151+
EVP_PKEY *pkey;
1152+
VALUE digest, sig, options, out;
1153+
const EVP_MD *md = NULL;
1154+
EVP_PKEY_CTX *ctx;
1155+
int state;
1156+
size_t outlen;
1157+
1158+
GetPKey(self, pkey);
1159+
rb_scan_args(argc, argv, "21", &digest, &sig, &options);
1160+
ossl_pkey_check_public_key(pkey);
1161+
if (!NIL_P(digest))
1162+
md = ossl_evp_get_digestbyname(digest);
1163+
StringValue(sig);
1164+
1165+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1166+
if (!ctx)
1167+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1168+
if (EVP_PKEY_verify_recover_init(ctx) <= 0) {
1169+
EVP_PKEY_CTX_free(ctx);
1170+
ossl_raise(ePKeyError, "EVP_PKEY_verify_recover_init");
1171+
}
1172+
if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) {
1173+
EVP_PKEY_CTX_free(ctx);
1174+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md");
1175+
}
1176+
if (!NIL_P(options)) {
1177+
pkey_ctx_apply_options(ctx, options, &state);
1178+
if (state) {
1179+
EVP_PKEY_CTX_free(ctx);
1180+
rb_jump_tag(state);
1181+
}
1182+
}
1183+
if (EVP_PKEY_verify_recover(ctx, NULL, &outlen,
1184+
(unsigned char *)RSTRING_PTR(sig),
1185+
RSTRING_LEN(sig)) <= 0) {
1186+
EVP_PKEY_CTX_free(ctx);
1187+
ossl_raise(ePKeyError, "EVP_PKEY_verify_recover");
1188+
}
1189+
out = ossl_str_new(NULL, (long)outlen, &state);
1190+
if (state) {
1191+
EVP_PKEY_CTX_free(ctx);
1192+
rb_jump_tag(state);
1193+
}
1194+
if (EVP_PKEY_verify_recover(ctx, (unsigned char *)RSTRING_PTR(out), &outlen,
1195+
(unsigned char *)RSTRING_PTR(sig),
1196+
RSTRING_LEN(sig)) <= 0) {
1197+
EVP_PKEY_CTX_free(ctx);
1198+
ossl_raise(ePKeyError, "EVP_PKEY_verify_recover");
1199+
}
1200+
EVP_PKEY_CTX_free(ctx);
1201+
rb_str_set_len(out, outlen);
1202+
return out;
1203+
}
1204+
9761205
/*
9771206
* call-seq:
9781207
* pkey.derive(peer_pkey) -> string
@@ -1262,6 +1491,9 @@ Init_ossl_pkey(void)
12621491

12631492
rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
12641493
rb_define_method(cPKey, "verify", ossl_pkey_verify, -1);
1494+
rb_define_method(cPKey, "sign_raw", ossl_pkey_sign_raw, -1);
1495+
rb_define_method(cPKey, "verify_raw", ossl_pkey_verify_raw, -1);
1496+
rb_define_method(cPKey, "verify_recover", ossl_pkey_verify_recover, -1);
12651497
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
12661498
rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
12671499
rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);

test/openssl/test_pkey_dsa.rb

+22-3
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,31 @@ def test_sign_verify
4848
assert_equal false, dsa512.verify("SHA256", signature1, data)
4949
end
5050

51-
def test_sys_sign_verify
52-
key = Fixtures.pkey("dsa256")
51+
def test_sign_verify_raw
52+
key = Fixtures.pkey("dsa512")
5353
data = 'Sign me!'
5454
digest = OpenSSL::Digest.digest('SHA1', data)
55+
56+
invalid_sig = key.sign_raw(nil, digest.succ)
57+
malformed_sig = "*" * invalid_sig.bytesize
58+
59+
# Sign by #syssign
5560
sig = key.syssign(digest)
56-
assert(key.sysverify(digest, sig))
61+
assert_equal true, key.sysverify(digest, sig)
62+
assert_equal false, key.sysverify(digest, invalid_sig)
63+
assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
64+
assert_equal true, key.verify_raw(nil, sig, digest)
65+
assert_equal false, key.verify_raw(nil, invalid_sig, digest)
66+
assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
67+
68+
# Sign by #sign_raw
69+
sig = key.sign_raw(nil, digest)
70+
assert_equal true, key.sysverify(digest, sig)
71+
assert_equal false, key.sysverify(digest, invalid_sig)
72+
assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
73+
assert_equal true, key.verify_raw(nil, sig, digest)
74+
assert_equal false, key.verify_raw(nil, invalid_sig, digest)
75+
assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
5776
end
5877

5978
def test_DSAPrivateKey

test/openssl/test_pkey_ec.rb

+19-2
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,30 @@ def test_derive_key
109109
assert_equal a.derive(b), a.dh_compute_key(b.public_key)
110110
end
111111

112-
def test_dsa_sign_verify
112+
def test_sign_verify_raw
113+
key = Fixtures.pkey("p256")
113114
data1 = "foo"
114115
data2 = "bar"
115-
key = OpenSSL::PKey::EC.new("prime256v1").generate_key!
116+
117+
malformed_sig = "*" * 30
118+
119+
# Sign by #dsa_sign_asn1
116120
sig = key.dsa_sign_asn1(data1)
117121
assert_equal true, key.dsa_verify_asn1(data1, sig)
118122
assert_equal false, key.dsa_verify_asn1(data2, sig)
123+
assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
124+
assert_equal true, key.verify_raw(nil, sig, data1)
125+
assert_equal false, key.verify_raw(nil, sig, data2)
126+
assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, data1) }
127+
128+
# Sign by #sign_raw
129+
sig = key.sign_raw(nil, data1)
130+
assert_equal true, key.dsa_verify_asn1(data1, sig)
131+
assert_equal false, key.dsa_verify_asn1(data2, sig)
132+
assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
133+
assert_equal true, key.verify_raw(nil, sig, data1)
134+
assert_equal false, key.verify_raw(nil, sig, data2)
135+
assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, data1) }
119136
end
120137

121138
def test_dsa_sign_asn1_FIPS186_3

0 commit comments

Comments
 (0)