Skip to content

Commit 122ecba

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 334341d commit 122ecba

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
@@ -971,6 +971,235 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self)
971971
}
972972
}
973973

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

12601489
rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
12611490
rb_define_method(cPKey, "verify", ossl_pkey_verify, -1);
1491+
rb_define_method(cPKey, "sign_raw", ossl_pkey_sign_raw, -1);
1492+
rb_define_method(cPKey, "verify_raw", ossl_pkey_verify_raw, -1);
1493+
rb_define_method(cPKey, "verify_recover", ossl_pkey_verify_recover, -1);
12621494
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
12631495
rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
12641496
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
@@ -53,12 +53,31 @@ def test_sign_verify
5353
assert_equal false, dsa512.verify("SHA256", signature1, data)
5454
end
5555

56-
def test_sys_sign_verify
57-
key = Fixtures.pkey("dsa256")
56+
def test_sign_verify_raw
57+
key = Fixtures.pkey("dsa512")
5858
data = 'Sign me!'
5959
digest = OpenSSL::Digest.digest('SHA1', data)
60+
61+
invalid_sig = key.sign_raw(nil, digest.succ)
62+
malformed_sig = "*" * invalid_sig.bytesize
63+
64+
# Sign by #syssign
6065
sig = key.syssign(digest)
61-
assert(key.sysverify(digest, sig))
66+
assert_equal true, key.sysverify(digest, sig)
67+
assert_equal false, key.sysverify(digest, invalid_sig)
68+
assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
69+
assert_equal true, key.verify_raw(nil, sig, digest)
70+
assert_equal false, key.verify_raw(nil, invalid_sig, digest)
71+
assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
72+
73+
# Sign by #sign_raw
74+
sig = key.sign_raw(nil, digest)
75+
assert_equal true, key.sysverify(digest, sig)
76+
assert_equal false, key.sysverify(digest, invalid_sig)
77+
assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
78+
assert_equal true, key.verify_raw(nil, sig, digest)
79+
assert_equal false, key.verify_raw(nil, invalid_sig, digest)
80+
assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
6281
end
6382

6483
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)