Skip to content

Commit 8e1c6e2

Browse files
committed
pkey: track whether pkey is private key or not
There are multiple places where it's necessary to know whether a pkey is a private key, a public key, or just key parameters. Unfortunately, OpenSSL doesn't expose an API for this purpose (even though it has one for its internal use). Currently, we drill down into the backing object, such as RSA, and see if the corresponding fields are set or not to determine it. This doesn't work on OpenSSL 3.0 because of the architecture changes. Let's manually track this information in an instance variable for now. This has been partly done for ENGINE-backed pkeys. Now all pkeys get this flag. PKeys are immutable on OpenSSL 3.0, so it just needs to be stored once on initialization. On OpenSSL 1.1 or before (including LibreSSL), it must be updated whenever a modification is made to the object. This comes with a slight behavior change. PKey returned by following method will be explicitly marked as "public", even if it happens to point at an EVP_PKEY struct containing private key components. I expect the effect is minimum since these methods explicitly say "public key". - OpenSSL::X509::Certificate#public_key - OpenSSL::X509::Request#public_key - OpenSSL::Netscape::SPKI#public_key
1 parent 41d0807 commit 8e1c6e2

13 files changed

+287
-222
lines changed

ext/openssl/openssl_missing.h

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ IMPL_KEY_ACCESSOR3(RSA, crt_params, dmp1, dmq1, iqmp, (dmp1 == obj->dmp1 || dmq1
140140
IMPL_PKEY_GETTER(DSA, dsa)
141141
IMPL_KEY_ACCESSOR2(DSA, key, pub_key, priv_key, (pub_key == obj->pub_key || (obj->priv_key && priv_key == obj->priv_key)))
142142
IMPL_KEY_ACCESSOR3(DSA, pqg, p, q, g, (p == obj->p || q == obj->q || g == obj->g))
143+
static inline ENGINE *DSA_get0_engine(DSA *dsa) { return dsa->engine; }
143144
#endif
144145

145146
#if !defined(OPENSSL_NO_DH)

ext/openssl/ossl_engine.c

+2-3
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,7 @@ ossl_engine_load_privkey(int argc, VALUE *argv, VALUE self)
373373
GetEngine(self, e);
374374
pkey = ENGINE_load_private_key(e, sid, NULL, sdata);
375375
if (!pkey) ossl_raise(eEngineError, NULL);
376-
obj = ossl_pkey_new(pkey);
377-
OSSL_PKEY_SET_PRIVATE(obj);
376+
obj = ossl_pkey_new(pkey, OSSL_PKEY_HAS_PRIVATE);
378377

379378
return obj;
380379
}
@@ -403,7 +402,7 @@ ossl_engine_load_pubkey(int argc, VALUE *argv, VALUE self)
403402
pkey = ENGINE_load_public_key(e, sid, NULL, sdata);
404403
if (!pkey) ossl_raise(eEngineError, NULL);
405404

406-
return ossl_pkey_new(pkey);
405+
return ossl_pkey_new(pkey, OSSL_PKEY_HAS_PUBLIC);
407406
}
408407

409408
/*

ext/openssl/ossl_ns_spki.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ ossl_spki_get_public_key(VALUE self)
190190
ossl_raise(eSPKIError, NULL);
191191
}
192192

193-
return ossl_pkey_new(pkey); /* NO DUP - OK */
193+
return ossl_pkey_new(pkey, OSSL_PKEY_HAS_PUBLIC); /* NO DUP - OK */
194194
}
195195

196196
/*

ext/openssl/ossl_pkcs12.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ ossl_pkcs12_s_create(int argc, VALUE *argv, VALUE self)
152152
static VALUE
153153
ossl_pkey_new_i(VALUE arg)
154154
{
155-
return ossl_pkey_new((EVP_PKEY *)arg);
155+
return ossl_pkey_new((EVP_PKEY *)arg, OSSL_PKEY_HAS_PRIVATE);
156156
}
157157

158158
static VALUE

ext/openssl/ossl_pkey.c

+90-32
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
VALUE mPKey;
2020
VALUE cPKey;
2121
VALUE ePKeyError;
22-
static ID id_private_q;
22+
ID ossl_pkey_feature_id;
2323

2424
static void
2525
ossl_evp_pkey_free(void *ptr)
@@ -65,7 +65,7 @@ pkey_new0(VALUE arg)
6565
}
6666

6767
VALUE
68-
ossl_pkey_new(EVP_PKEY *pkey)
68+
ossl_pkey_new(EVP_PKEY *pkey, enum ossl_pkey_feature ps)
6969
{
7070
VALUE obj;
7171
int status;
@@ -75,6 +75,7 @@ ossl_pkey_new(EVP_PKEY *pkey)
7575
EVP_PKEY_free(pkey);
7676
rb_jump_tag(status);
7777
}
78+
ossl_pkey_set(obj, ps);
7879

7980
return obj;
8081
}
@@ -83,34 +84,61 @@ ossl_pkey_new(EVP_PKEY *pkey)
8384
# include <openssl/decoder.h>
8485

8586
EVP_PKEY *
86-
ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type)
87+
ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type, enum ossl_pkey_feature *ps)
8788
{
8889
void *ppass = (void *)pass;
8990
OSSL_DECODER_CTX *dctx;
9091
EVP_PKEY *pkey = NULL;
9192
int pos = 0, pos2;
93+
size_t i;
9294

9395
dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", NULL, input_type, 0, NULL, NULL);
9496
if (!dctx)
9597
goto out;
9698
if (OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, ppass) != 1)
9799
goto out;
98100

101+
/*
102+
* This is inefficient as it will parse the same DER/PEM encoding
103+
* repeatedly, but OpenSSL 3.0 doesn't provide an API to return what
104+
* information an EVP_PKEY is holding.
105+
* OpenSSL issue: https://github.com/openssl/openssl/issues/9467
106+
*/
107+
struct { int selection; enum ossl_pkey_feature ps; } selections[] = {
108+
{ EVP_PKEY_KEYPAIR, OSSL_PKEY_HAS_PRIVATE },
109+
{ EVP_PKEY_PUBLIC_KEY, OSSL_PKEY_HAS_PUBLIC },
110+
{ EVP_PKEY_KEY_PARAMETERS, OSSL_PKEY_HAS_NONE },
111+
};
112+
99113
/* First check DER */
100-
if (OSSL_DECODER_from_bio(dctx, bio) == 1)
101-
goto out;
114+
for (i = 0; i < sizeof(selections)/sizeof(selections[0]); i++) {
115+
OSSL_DECODER_CTX_set_selection(dctx, selections[i].selection);
116+
*ps = selections[i].ps;
117+
if (OSSL_DECODER_from_bio(dctx, bio) == 1)
118+
goto out;
119+
OSSL_BIO_reset(bio);
120+
}
102121

103-
/* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */
104-
OSSL_BIO_reset(bio);
122+
/*
123+
* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed for
124+
* each selection in case of stacked PEM structures
125+
*/
105126
if (OSSL_DECODER_CTX_set_input_type(dctx, "PEM") != 1)
106127
goto out;
107-
while (OSSL_DECODER_from_bio(dctx, bio) != 1) {
108-
if (BIO_eof(bio))
109-
goto out;
110-
pos2 = BIO_tell(bio);
111-
if (pos2 < 0 || pos2 <= pos)
112-
goto out;
113-
pos = pos2;
128+
for (i = 0; i < sizeof(selections)/sizeof(selections[0]); i++) {
129+
OSSL_DECODER_CTX_set_selection(dctx, selections[i].selection);
130+
*ps = selections[i].ps;
131+
while (true) {
132+
if (OSSL_DECODER_from_bio(dctx, bio) == 1)
133+
goto out;
134+
if (BIO_eof(bio))
135+
break;
136+
pos2 = BIO_tell(bio);
137+
if (pos2 < 0 || pos2 <= pos)
138+
break;
139+
pos = pos2;
140+
}
141+
OSSL_BIO_reset(bio);
114142
}
115143

116144
out:
@@ -119,26 +147,35 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type)
119147
}
120148
#else
121149
EVP_PKEY *
122-
ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type)
150+
ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type, enum ossl_pkey_feature *ps)
123151
{
124152
void *ppass = (void *)pass;
125153
EVP_PKEY *pkey;
126154

155+
*ps = OSSL_PKEY_HAS_PRIVATE;
127156
if ((pkey = d2i_PrivateKey_bio(bio, NULL)))
128157
goto out;
129158
OSSL_BIO_reset(bio);
130159
if ((pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, ossl_pem_passwd_cb, ppass)))
131160
goto out;
161+
162+
*ps = OSSL_PKEY_HAS_PUBLIC;
132163
OSSL_BIO_reset(bio);
133164
if ((pkey = d2i_PUBKEY_bio(bio, NULL)))
134165
goto out;
135-
OSSL_BIO_reset(bio);
166+
136167
/* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */
168+
*ps = OSSL_PKEY_HAS_PRIVATE;
169+
OSSL_BIO_reset(bio);
137170
if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, ppass)))
138171
goto out;
172+
173+
*ps = OSSL_PKEY_HAS_PUBLIC;
139174
OSSL_BIO_reset(bio);
140175
if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)))
141176
goto out;
177+
178+
*ps = OSSL_PKEY_HAS_NONE;
142179
OSSL_BIO_reset(bio);
143180
if ((pkey = PEM_read_bio_Parameters(bio, NULL)))
144181
goto out;
@@ -195,14 +232,15 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
195232
EVP_PKEY *pkey;
196233
BIO *bio;
197234
VALUE data, pass;
235+
enum ossl_pkey_feature ps;
198236

199237
rb_scan_args(argc, argv, "11", &data, &pass);
200238
bio = ossl_obj2bio(&data);
201-
pkey = ossl_pkey_read_generic(bio, ossl_pem_passwd_value(pass), NULL);
239+
pkey = ossl_pkey_read_generic(bio, ossl_pem_passwd_value(pass), NULL, &ps);
202240
BIO_free(bio);
203241
if (!pkey)
204242
ossl_raise(ePKeyError, "Could not parse PKey");
205-
return ossl_pkey_new(pkey);
243+
return ossl_pkey_new(pkey, ps);
206244
}
207245

208246
static VALUE
@@ -405,7 +443,7 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
405443
}
406444
}
407445

408-
return ossl_pkey_new(gen_arg.pkey);
446+
return ossl_pkey_new(gen_arg.pkey, OSSL_PKEY_HAS_PRIVATE);
409447
}
410448

411449
/*
@@ -527,18 +565,9 @@ GetPrivPKeyPtr(VALUE obj)
527565
EVP_PKEY *pkey;
528566

529567
GetPKey(obj, pkey);
530-
if (OSSL_PKEY_IS_PRIVATE(obj))
531-
return pkey;
532-
/*
533-
* The EVP API does not provide a way to check if the EVP_PKEY has private
534-
* components. Assuming it does...
535-
*/
536-
if (!rb_respond_to(obj, id_private_q))
537-
return pkey;
538-
if (RTEST(rb_funcallv(obj, id_private_q, 0, NULL)))
539-
return pkey;
540-
541-
rb_raise(rb_eArgError, "private key is needed");
568+
if (!ossl_pkey_has(obj, OSSL_PKEY_HAS_PRIVATE))
569+
rb_raise(rb_eArgError, "private key is needed");
570+
return pkey;
542571
}
543572

544573
EVP_PKEY *
@@ -614,6 +643,33 @@ ossl_pkey_oid(VALUE self)
614643
return rb_str_new_cstr(OBJ_nid2sn(nid));
615644
}
616645

646+
/*
647+
* call-seq:
648+
* pkey.public? -> true | false
649+
*
650+
* Indicates whether this PKey instance has a public key associated with it or
651+
* not.
652+
*/
653+
static VALUE
654+
ossl_pkey_is_public(VALUE self)
655+
{
656+
return ossl_pkey_has(self, OSSL_PKEY_HAS_PUBLIC) ? Qtrue : Qfalse;
657+
}
658+
659+
/*
660+
* call-seq:
661+
* pkey.private? -> true | false
662+
*
663+
* Indicates whether this PKey instance has a private key associated with it or
664+
* not.
665+
*/
666+
static VALUE
667+
ossl_pkey_is_private(VALUE self)
668+
{
669+
return ossl_pkey_has(self, OSSL_PKEY_HAS_PRIVATE) ? Qtrue : Qfalse;
670+
}
671+
672+
617673
/*
618674
* call-seq:
619675
* pkey.inspect -> string
@@ -1580,6 +1636,8 @@ Init_ossl_pkey(void)
15801636
rb_undef_method(cPKey, "initialize_copy");
15811637
#endif
15821638
rb_define_method(cPKey, "oid", ossl_pkey_oid, 0);
1639+
rb_define_method(cPKey, "public?", ossl_pkey_is_public, 0);
1640+
rb_define_method(cPKey, "private?", ossl_pkey_is_private, 0);
15831641
rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0);
15841642
rb_define_method(cPKey, "to_text", ossl_pkey_to_text, 0);
15851643
rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1);
@@ -1597,7 +1655,7 @@ Init_ossl_pkey(void)
15971655
rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
15981656
rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);
15991657

1600-
id_private_q = rb_intern("private?");
1658+
ossl_pkey_feature_id = rb_intern_const("state");
16011659

16021660
/*
16031661
* INIT rsa, dsa, dh, ec

ext/openssl/ossl_pkey.h

+25-6
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ extern VALUE mPKey;
1414
extern VALUE cPKey;
1515
extern VALUE ePKeyError;
1616
extern const rb_data_type_t ossl_evp_pkey_type;
17-
18-
/* For ENGINE */
19-
#define OSSL_PKEY_SET_PRIVATE(obj) rb_ivar_set((obj), rb_intern("private"), Qtrue)
20-
#define OSSL_PKEY_IS_PRIVATE(obj) (rb_attr_get((obj), rb_intern("private")) == Qtrue)
17+
extern ID ossl_pkey_feature_id;
2118

2219
#define GetPKey(obj, pkey) do {\
2320
TypedData_Get_Struct((obj), EVP_PKEY, &ossl_evp_pkey_type, (pkey)); \
@@ -26,10 +23,30 @@ extern const rb_data_type_t ossl_evp_pkey_type;
2623
} \
2724
} while (0)
2825

26+
/*
27+
* Store whether pkey contains public key, private key, or neither of them.
28+
* This is ugly, but OpenSSL currently (3.0) doesn't provide a public API for
29+
* this purpose.
30+
*/
31+
enum ossl_pkey_feature {
32+
OSSL_PKEY_HAS_NONE, /* or parameters only */
33+
OSSL_PKEY_HAS_PUBLIC,
34+
OSSL_PKEY_HAS_PRIVATE,
35+
};
36+
static inline void ossl_pkey_set(VALUE self, enum ossl_pkey_feature state)
37+
{
38+
rb_ivar_set(self, ossl_pkey_feature_id, INT2FIX(state));
39+
}
40+
static inline int ossl_pkey_has(VALUE self, enum ossl_pkey_feature state)
41+
{
42+
return FIX2INT(rb_attr_get(self, ossl_pkey_feature_id)) >= (int)state;
43+
}
44+
2945
/* Takes ownership of the EVP_PKEY */
30-
VALUE ossl_pkey_new(EVP_PKEY *);
46+
VALUE ossl_pkey_new(EVP_PKEY *pkey, enum ossl_pkey_feature ps);
3147
void ossl_pkey_check_public_key(const EVP_PKEY *);
32-
EVP_PKEY *ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type);
48+
EVP_PKEY *ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type,
49+
enum ossl_pkey_feature *ps);
3350
EVP_PKEY *GetPKeyPtr(VALUE);
3451
EVP_PKEY *DupPKeyPtr(VALUE);
3552
EVP_PKEY *GetPrivPKeyPtr(VALUE);
@@ -145,6 +162,7 @@ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALU
145162
BN_clear_free(bn3); \
146163
ossl_raise(ePKeyError, #_type"_set0_"#_group); \
147164
} \
165+
_keytype##_fix_selection(self, obj); \
148166
return self; \
149167
}
150168

@@ -172,6 +190,7 @@ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \
172190
BN_clear_free(bn2); \
173191
ossl_raise(ePKeyError, #_type"_set0_"#_group); \
174192
} \
193+
_keytype##_fix_selection(self, obj); \
175194
return self; \
176195
}
177196
#else /* OSSL_HAVE_PROVIDER */

0 commit comments

Comments
 (0)