19
19
VALUE mPKey ;
20
20
VALUE cPKey ;
21
21
VALUE ePKeyError ;
22
- static ID id_private_q ;
22
+ ID ossl_pkey_feature_id ;
23
23
24
24
static void
25
25
ossl_evp_pkey_free (void * ptr )
@@ -65,7 +65,7 @@ pkey_new0(VALUE arg)
65
65
}
66
66
67
67
VALUE
68
- ossl_pkey_new (EVP_PKEY * pkey )
68
+ ossl_pkey_new (EVP_PKEY * pkey , enum ossl_pkey_feature ps )
69
69
{
70
70
VALUE obj ;
71
71
int status ;
@@ -75,6 +75,7 @@ ossl_pkey_new(EVP_PKEY *pkey)
75
75
EVP_PKEY_free (pkey );
76
76
rb_jump_tag (status );
77
77
}
78
+ ossl_pkey_set (obj , ps );
78
79
79
80
return obj ;
80
81
}
@@ -83,29 +84,28 @@ ossl_pkey_new(EVP_PKEY *pkey)
83
84
# include <openssl/decoder.h>
84
85
85
86
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 )
87
88
{
88
89
void * ppass = (void * )pass ;
89
90
OSSL_DECODER_CTX * dctx ;
90
91
EVP_PKEY * pkey = NULL ;
91
92
int pos = 0 , pos2 ;
93
+ size_t i ;
92
94
93
95
dctx = OSSL_DECODER_CTX_new_for_pkey (& pkey , "DER" , NULL , input_type , 0 , NULL , NULL );
94
96
if (!dctx )
95
97
goto out ;
96
98
if (OSSL_DECODER_CTX_set_pem_password_cb (dctx , ossl_pem_passwd_cb , ppass ) != 1 )
97
99
goto out ;
98
100
99
- /* First check DER */
100
- if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
101
- goto out ;
102
- OSSL_BIO_reset (bio );
103
-
104
- /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */
105
- if (OSSL_DECODER_CTX_set_input_type (dctx , "PEM" ) != 1 )
106
- goto out ;
107
101
/*
108
- * First check for private key formats. This is to keep compatibility with
102
+ * This is very inefficient as it will try to decode the same DER/PEM
103
+ * encoding repeatedly, but OpenSSL 3.0 doesn't provide an API to return
104
+ * what kind of information an EVP_PKEY is holding.
105
+ * OpenSSL issue: https://github.com/openssl/openssl/issues/9467
106
+ *
107
+ * The attempt order of selections is important: private key formats are
108
+ * checked first. This is to keep compatibility with
109
109
* ruby/openssl < 3.0 which decoded the following as a private key.
110
110
*
111
111
* $ openssl ecparam -name prime256v1 -genkey -outform PEM
@@ -125,59 +125,94 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type)
125
125
* Note that normally, the input is supposed to contain a single decodable
126
126
* PEM block only, so this special handling should not create a new problem.
127
127
*/
128
- OSSL_DECODER_CTX_set_selection (dctx , EVP_PKEY_KEYPAIR );
129
- while (1 ) {
128
+ struct { int selection ; enum ossl_pkey_feature ps ; } selections [] = {
129
+ #if OSSL_OPENSSL_PREREQ (3 , 0 , 0 ) && !OSSL_OPENSSL_PREREQ (3 , 0 , 3 )
130
+ /*
131
+ * Public key formats are checked last, with 0 (any) selection to avoid
132
+ * segfault in OpenSSL <= 3.0.3.
133
+ * Fixed by https://github.com/openssl/openssl/pull/18462
134
+ *
135
+ * This workaround is mainly for Ubuntu 22.04, which currently has yet
136
+ * to backport it (as of 2022-07-26).
137
+ */
138
+ { EVP_PKEY_KEYPAIR , OSSL_PKEY_HAS_PRIVATE },
139
+ { EVP_PKEY_KEY_PARAMETERS , OSSL_PKEY_HAS_NONE },
140
+ { 0 , OSSL_PKEY_HAS_PUBLIC },
141
+ #else
142
+ { EVP_PKEY_KEYPAIR , OSSL_PKEY_HAS_PRIVATE },
143
+ { EVP_PKEY_PUBLIC_KEY , OSSL_PKEY_HAS_PUBLIC },
144
+ { EVP_PKEY_KEY_PARAMETERS , OSSL_PKEY_HAS_NONE },
145
+ #endif
146
+ };
147
+
148
+ /* First check DER */
149
+ for (i = 0 ; i < sizeof (selections )/sizeof (selections [0 ]); i ++ ) {
150
+ OSSL_DECODER_CTX_set_selection (dctx , selections [i ].selection );
151
+ * ps = selections [i ].ps ;
130
152
if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
131
153
goto out ;
132
- if (BIO_eof (bio ))
133
- break ;
134
- pos2 = BIO_tell (bio );
135
- if (pos2 < 0 || pos2 <= pos )
136
- break ;
137
- ossl_clear_error ();
138
- pos = pos2 ;
154
+ OSSL_BIO_reset (bio );
139
155
}
140
156
141
- OSSL_BIO_reset (bio );
142
- OSSL_DECODER_CTX_set_selection (dctx , 0 );
143
- while (1 ) {
144
- if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
145
- goto out ;
146
- if (BIO_eof (bio ))
147
- break ;
148
- pos2 = BIO_tell (bio );
149
- if (pos2 < 0 || pos2 <= pos )
150
- break ;
151
- ossl_clear_error ();
152
- pos = pos2 ;
157
+ /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */
158
+ if (OSSL_DECODER_CTX_set_input_type (dctx , "PEM" ) != 1 )
159
+ goto out ;
160
+ for (i = 0 ; i < sizeof (selections )/sizeof (selections [0 ]); i ++ ) {
161
+ OSSL_DECODER_CTX_set_selection (dctx , selections [i ].selection );
162
+ * ps = selections [i ].ps ;
163
+ while (1 ) {
164
+ if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
165
+ goto out ;
166
+ if (BIO_eof (bio ))
167
+ break ;
168
+ pos2 = BIO_tell (bio );
169
+ if (pos2 < 0 || pos2 <= pos )
170
+ break ;
171
+ ossl_clear_error ();
172
+ pos = pos2 ;
173
+ }
174
+ OSSL_BIO_reset (bio );
153
175
}
154
176
155
177
out :
156
178
OSSL_DECODER_CTX_free (dctx );
179
+ #if OSSL_OPENSSL_PREREQ (3 , 0 , 0 ) && !OSSL_OPENSSL_PREREQ (3 , 0 , 3 )
180
+ /* It also leaks an error queue entry in the success path */
181
+ if (pkey ) ossl_clear_error ();
182
+ #endif
157
183
return pkey ;
158
184
}
159
185
#else
160
186
EVP_PKEY *
161
- ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type )
187
+ ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type , enum ossl_pkey_feature * ps )
162
188
{
163
189
void * ppass = (void * )pass ;
164
190
EVP_PKEY * pkey ;
165
191
192
+ * ps = OSSL_PKEY_HAS_PRIVATE ;
166
193
if ((pkey = d2i_PrivateKey_bio (bio , NULL )))
167
194
goto out ;
168
195
OSSL_BIO_reset (bio );
169
196
if ((pkey = d2i_PKCS8PrivateKey_bio (bio , NULL , ossl_pem_passwd_cb , ppass )))
170
197
goto out ;
198
+
199
+ * ps = OSSL_PKEY_HAS_PUBLIC ;
171
200
OSSL_BIO_reset (bio );
172
201
if ((pkey = d2i_PUBKEY_bio (bio , NULL )))
173
202
goto out ;
174
- OSSL_BIO_reset ( bio );
203
+
175
204
/* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */
205
+ * ps = OSSL_PKEY_HAS_PRIVATE ;
206
+ OSSL_BIO_reset (bio );
176
207
if ((pkey = PEM_read_bio_PrivateKey (bio , NULL , ossl_pem_passwd_cb , ppass )))
177
208
goto out ;
209
+
210
+ * ps = OSSL_PKEY_HAS_PUBLIC ;
178
211
OSSL_BIO_reset (bio );
179
212
if ((pkey = PEM_read_bio_PUBKEY (bio , NULL , NULL , NULL )))
180
213
goto out ;
214
+
215
+ * ps = OSSL_PKEY_HAS_NONE ;
181
216
OSSL_BIO_reset (bio );
182
217
if ((pkey = PEM_read_bio_Parameters (bio , NULL )))
183
218
goto out ;
@@ -234,14 +269,15 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
234
269
EVP_PKEY * pkey ;
235
270
BIO * bio ;
236
271
VALUE data , pass ;
272
+ enum ossl_pkey_feature ps ;
237
273
238
274
rb_scan_args (argc , argv , "11" , & data , & pass );
239
275
bio = ossl_obj2bio (& data );
240
- pkey = ossl_pkey_read_generic (bio , ossl_pem_passwd_value (pass ), NULL );
276
+ pkey = ossl_pkey_read_generic (bio , ossl_pem_passwd_value (pass ), NULL , & ps );
241
277
BIO_free (bio );
242
278
if (!pkey )
243
279
ossl_raise (ePKeyError , "Could not parse PKey" );
244
- return ossl_pkey_new (pkey );
280
+ return ossl_pkey_new (pkey , ps );
245
281
}
246
282
247
283
static VALUE
@@ -445,7 +481,7 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
445
481
}
446
482
}
447
483
448
- return ossl_pkey_new (gen_arg .pkey );
484
+ return ossl_pkey_new (gen_arg .pkey , OSSL_PKEY_HAS_PRIVATE );
449
485
}
450
486
451
487
/*
@@ -567,18 +603,9 @@ GetPrivPKeyPtr(VALUE obj)
567
603
EVP_PKEY * pkey ;
568
604
569
605
GetPKey (obj , pkey );
570
- if (OSSL_PKEY_IS_PRIVATE (obj ))
571
- return pkey ;
572
- /*
573
- * The EVP API does not provide a way to check if the EVP_PKEY has private
574
- * components. Assuming it does...
575
- */
576
- if (!rb_respond_to (obj , id_private_q ))
577
- return pkey ;
578
- if (RTEST (rb_funcallv (obj , id_private_q , 0 , NULL )))
579
- return pkey ;
580
-
581
- rb_raise (rb_eArgError , "private key is needed" );
606
+ if (!ossl_pkey_has (obj , OSSL_PKEY_HAS_PRIVATE ))
607
+ rb_raise (rb_eArgError , "private key is needed" );
608
+ return pkey ;
582
609
}
583
610
584
611
EVP_PKEY *
@@ -654,6 +681,33 @@ ossl_pkey_oid(VALUE self)
654
681
return rb_str_new_cstr (OBJ_nid2sn (nid ));
655
682
}
656
683
684
+ /*
685
+ * call-seq:
686
+ * pkey.public? -> true | false
687
+ *
688
+ * Indicates whether this PKey instance has a public key associated with it or
689
+ * not.
690
+ */
691
+ static VALUE
692
+ ossl_pkey_is_public (VALUE self )
693
+ {
694
+ return ossl_pkey_has (self , OSSL_PKEY_HAS_PUBLIC ) ? Qtrue : Qfalse ;
695
+ }
696
+
697
+ /*
698
+ * call-seq:
699
+ * pkey.private? -> true | false
700
+ *
701
+ * Indicates whether this PKey instance has a private key associated with it or
702
+ * not.
703
+ */
704
+ static VALUE
705
+ ossl_pkey_is_private (VALUE self )
706
+ {
707
+ return ossl_pkey_has (self , OSSL_PKEY_HAS_PRIVATE ) ? Qtrue : Qfalse ;
708
+ }
709
+
710
+
657
711
/*
658
712
* call-seq:
659
713
* pkey.inspect -> string
@@ -1620,6 +1674,8 @@ Init_ossl_pkey(void)
1620
1674
rb_undef_method (cPKey , "initialize_copy" );
1621
1675
#endif
1622
1676
rb_define_method (cPKey , "oid" , ossl_pkey_oid , 0 );
1677
+ rb_define_method (cPKey , "public?" , ossl_pkey_is_public , 0 );
1678
+ rb_define_method (cPKey , "private?" , ossl_pkey_is_private , 0 );
1623
1679
rb_define_method (cPKey , "inspect" , ossl_pkey_inspect , 0 );
1624
1680
rb_define_method (cPKey , "to_text" , ossl_pkey_to_text , 0 );
1625
1681
rb_define_method (cPKey , "private_to_der" , ossl_pkey_private_to_der , -1 );
@@ -1637,7 +1693,7 @@ Init_ossl_pkey(void)
1637
1693
rb_define_method (cPKey , "encrypt" , ossl_pkey_encrypt , -1 );
1638
1694
rb_define_method (cPKey , "decrypt" , ossl_pkey_decrypt , -1 );
1639
1695
1640
- id_private_q = rb_intern ( "private? " );
1696
+ ossl_pkey_feature_id = rb_intern_const ( "state " );
1641
1697
1642
1698
/*
1643
1699
* INIT rsa, dsa, dh, ec
0 commit comments